Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
6e0e390daf
|
|
@ -43,6 +43,16 @@ aliases:
|
|||
- npm run dev --prefix client &
|
||||
- cd client
|
||||
- /node_modules/.bin/cypress run
|
||||
- &jest-test
|
||||
name: run jest tests
|
||||
caches:
|
||||
- node
|
||||
script:
|
||||
- echo "This pipeline rules!"
|
||||
- *setup-tests
|
||||
- npm install --prefix client
|
||||
- cd client
|
||||
- npm run test:unit
|
||||
- &deploy-prod
|
||||
name: deploy to prod on Heroku
|
||||
script:
|
||||
|
|
@ -52,15 +62,18 @@ pipelines:
|
|||
default:
|
||||
- step: *unittest-python
|
||||
- step: *cypress-test
|
||||
- step: *jest-test
|
||||
|
||||
branches:
|
||||
master:
|
||||
- step: *unittest-python
|
||||
- step: *cypress-test
|
||||
- step: *jest-test
|
||||
|
||||
develop:
|
||||
- step: *unittest-python
|
||||
- step: *cypress-test
|
||||
- step: *jest-test
|
||||
- step:
|
||||
name: deploy to stage on Heroku
|
||||
script:
|
||||
|
|
@ -70,4 +83,5 @@ pipelines:
|
|||
prod:
|
||||
- step: *unittest-python
|
||||
- step: *cypress-test
|
||||
- step: *jest-test
|
||||
- step: *deploy-prod
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false,
|
||||
["@babel/preset-env", {
|
||||
"useBuiltIns": false,
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
}
|
||||
}],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": ["transform-vue-jsx", "transform-runtime"]
|
||||
"plugins": [
|
||||
"transform-vue-jsx",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ module.exports = {
|
|||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env']
|
||||
},
|
||||
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'vue'
|
||||
],
|
||||
transform: {
|
||||
"\\.(gql|graphql)$": "<rootDir>/node_modules/jest-transform-graphql",
|
||||
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
|
||||
'^.+\\.vue$': '<rootDir>/node_modules/vue-jest',
|
||||
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub'
|
||||
},
|
||||
modulePaths: [
|
||||
"<rootDir>/src",
|
||||
"<rootDir>/node_modules"
|
||||
],
|
||||
transformIgnorePatterns: [
|
||||
'/node_modules/'
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1'
|
||||
},
|
||||
snapshotSerializers: [
|
||||
'<rootDir>/node_modules/jest-serializer-vue'
|
||||
],
|
||||
testMatch: [
|
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||
],
|
||||
testURL: 'http://localhost/',
|
||||
watchPlugins: [
|
||||
'jest-watch-typeahead/filename',
|
||||
'jest-watch-typeahead/testname'
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,9 +11,16 @@
|
|||
"build": "node build/build.js",
|
||||
"open:cypress": "cypress open",
|
||||
"test:cypress": "cypress run",
|
||||
"install:cypress": "cypress install"
|
||||
"install:cypress": "cypress install",
|
||||
"test:unit": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.5.4",
|
||||
"@babel/plugin-transform-runtime": "^7.5.0",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/preset-env": "^7.5.4",
|
||||
"@babel/preset-stage-2": "^7.0.0",
|
||||
"@babel/runtime": "^7.5.4",
|
||||
"apollo-cache-inmemory": "^1.2.2",
|
||||
"apollo-client": "^2.3.2",
|
||||
"apollo-link": "^1.2.2",
|
||||
|
|
@ -21,16 +28,11 @@
|
|||
"appolo": "^6.0.19",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"axios": "^0.18.0",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"chalk": "^2.0.1",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
|
|
@ -99,6 +101,17 @@
|
|||
"not ie <= 8"
|
||||
],
|
||||
"devDependencies": {
|
||||
"cypress": "^3.1.5"
|
||||
"@vue/test-utils": "^1.0.0-beta.29",
|
||||
"babel-bridge": "^1.12.11",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^24.8.0",
|
||||
"canvas": "^2.5.0",
|
||||
"cypress": "^3.4.0",
|
||||
"jest": "^24.8.0",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"jest-transform-graphql": "^2.1.0",
|
||||
"jest-transform-stub": "^2.0.0",
|
||||
"jest-watch-typeahead": "^0.3.1",
|
||||
"vue-jest": "^3.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
import EditObjectiveGroupWizard from '@/components/objective-groups/EditObjectiveGroupWizard';
|
||||
import NewProjectEntryWizard from '@/components/portfolio/NewProjectEntryWizard';
|
||||
import EditProjectEntryWizard from '@/components/portfolio/EditProjectEntryWizard';
|
||||
import NewObjectiveWizard from '@/components/objective-groups/NewObjectiveWizard';
|
||||
import FullscreenImage from '@/components/FullscreenImage';
|
||||
import FullscreenInfographic from '@/components/FullscreenInfographic';
|
||||
import FullscreenVideo from '@/components/FullscreenVideo';
|
||||
|
|
@ -41,10 +42,12 @@
|
|||
EditContentBlockWizard,
|
||||
NewRoomEntryWizard,
|
||||
EditRoomEntryWizard,
|
||||
// todo: remove
|
||||
NewObjectiveGroupWizard,
|
||||
EditObjectiveGroupWizard,
|
||||
NewProjectEntryWizard,
|
||||
EditProjectEntryWizard,
|
||||
NewObjectiveWizard,
|
||||
FullscreenImage,
|
||||
FullscreenInfographic,
|
||||
FullscreenVideo
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="add-content">
|
||||
<a class="add-content__button" v-on:click="addContentBlock">
|
||||
<a class="add-content__button" v-on:click="addContent">
|
||||
<add-pointer class="add-content__icon"></add-pointer>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -17,11 +17,15 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
addContentBlock() {
|
||||
this.$store.dispatch('addContentBlock', {
|
||||
after: this.after,
|
||||
parent: this.parent
|
||||
});
|
||||
addContent() {
|
||||
if (this.parent && this.parent.__typename === 'ObjectiveGroupNode') {
|
||||
this.$store.dispatch('addObjective', this.parent.id);
|
||||
} else {
|
||||
this.$store.dispatch('addContentBlock', {
|
||||
after: this.after ? this.after.id : undefined,
|
||||
parent: this.parent ? this.parent.id : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
{{chapter.description}}
|
||||
</p>
|
||||
|
||||
<add-content-block-button :parent="chapter.id" v-if="editModule"></add-content-block-button>
|
||||
<add-content-button :parent="chapter" v-if="editModule"></add-content-button>
|
||||
|
||||
<content-block :contentBlock="contentBlock"
|
||||
:parent="chapter.id"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<script>
|
||||
import ContentBlock from '@/components/ContentBlock';
|
||||
import AddContentBlockButton from '@/components/AddContentBlockButton';
|
||||
import AddContentButton from '@/components/AddContentButton';
|
||||
|
||||
import {mapGetters} from 'vuex';
|
||||
import {isHidden} from '@/helpers/content-block';
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
components: {
|
||||
ContentBlock,
|
||||
AddContentBlockButton
|
||||
AddContentButton
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,17 @@
|
|||
<template>
|
||||
<div class="content-block__container" :class="{'content-block__container--hidden': hidden}">
|
||||
<div class="content-block__container hideable-element" :class="{'hideable-element--hidden': hidden}">
|
||||
<div class="content-block" :class="specialClass">
|
||||
<div class="content-block__actions" v-if="canEditContentBlock && editModule">
|
||||
<user-widget v-bind="me" class="content-block__user-widget"></user-widget>
|
||||
<div class="block-actions" v-if="canEditContentBlock && editModule">
|
||||
<user-widget v-bind="me" class="block-actions__user-widget content-block__user-widget"></user-widget>
|
||||
<more-options-widget>
|
||||
<li class="popover-links__link"><a @click="deleteContentBlock(contentBlock)">Löschen</a></li>
|
||||
<li class="popover-links__link"><a @click="editContentBlock(contentBlock)">Bearbeiten</a></li>
|
||||
</more-options-widget>
|
||||
</div>
|
||||
<div class="content-block__visibility" v-if="editModule">
|
||||
<div class="content-block__visibility">
|
||||
<visibility-action
|
||||
v-if="!contentBlock.indent"
|
||||
v-if="canEditModule"
|
||||
:block="contentBlock"></visibility-action>
|
||||
<!--<a @click="editContentBlock()" v-if="canEditContentBlock" class="content-block__action-button">-->
|
||||
<!--<pen-icon class="content-block__action-icon action-icon"></pen-icon>-->
|
||||
<!--</a>-->
|
||||
<!--<a @click="deleteContentBlock(contentBlock.id)" v-if="canEditContentBlock" class="content-block__action-button">-->
|
||||
<!--<trash-icon class="content-block__action-icon action-icon"></trash-icon>-->
|
||||
<!--</a>-->
|
||||
</div>
|
||||
|
||||
<h3 v-if="instrumentLabel !== ''" class="content-block__instrument-label">{{instrumentLabel}}</h3>
|
||||
|
|
@ -31,8 +25,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
<add-content-block-button :after="contentBlock.id"
|
||||
v-if="!contentBlock.indent && editModule"></add-content-block-button>
|
||||
<add-content-button :after="contentBlock" v-if="canEditModule"></add-content-button>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -54,7 +47,7 @@
|
|||
import Assignment from '@/components/content-blocks/assignment/Assignment';
|
||||
import Survey from '@/components/content-blocks/SurveyBlock';
|
||||
import Solution from '@/components/content-blocks/Solution';
|
||||
import AddContentBlockButton from '@/components/AddContentBlockButton';
|
||||
import AddContentButton from '@/components/AddContentButton';
|
||||
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
|
||||
import UserWidget from '@/components/UserWidget';
|
||||
import VisibilityAction from '@/components/visibility/VisibilityAction';
|
||||
|
|
@ -99,7 +92,7 @@
|
|||
Solution,
|
||||
Assignment,
|
||||
Task,
|
||||
AddContentBlockButton,
|
||||
AddContentButton,
|
||||
VisibilityAction,
|
||||
EyeIcon,
|
||||
PenIcon,
|
||||
|
|
@ -110,16 +103,19 @@
|
|||
|
||||
computed: {
|
||||
...mapGetters(['editModule']),
|
||||
canEditModule() {
|
||||
return !this.contentBlock.indent && this.editModule;
|
||||
},
|
||||
specialClass() {
|
||||
return `content-block--${this.contentBlock.type.toLowerCase()}`
|
||||
return `content-block--${this.contentBlock.type.toLowerCase()}`;
|
||||
},
|
||||
instrumentLabel() {
|
||||
const contentType = this.contentBlock.type.toLowerCase()
|
||||
const contentType = this.contentBlock.type.toLowerCase();
|
||||
if (!(contentType in instruments)) {
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
|
||||
return `Instrument - ${instruments[contentType]}`
|
||||
return `Instrument - ${instruments[contentType]}`;
|
||||
},
|
||||
canEditContentBlock() {
|
||||
return this.contentBlock.mine && !this.contentBlock.indent;
|
||||
|
|
@ -165,7 +161,7 @@
|
|||
contentList = [];
|
||||
return newContents;
|
||||
} else {
|
||||
return [...newContents, content]
|
||||
return [...newContents, content];
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -249,19 +245,6 @@
|
|||
|
||||
&__container {
|
||||
position: relative;
|
||||
|
||||
&--hidden {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
|
|
@ -273,29 +256,12 @@
|
|||
@include regular-text();
|
||||
}
|
||||
|
||||
&__visibility {
|
||||
position: absolute;
|
||||
left: -70px;
|
||||
top: -4px;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -85px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
&__action-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__user-widget {
|
||||
margin-right: 0;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
|
||||
&__action-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&--base_communication {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
// todo: do we need the margin right always? just do it where needed --> content block actions and objecives override this
|
||||
margin-right: $medium-spacing;
|
||||
|
||||
&__popover {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,8 @@
|
|||
<h3 id="objectives">Lernziele</h3>
|
||||
|
||||
<objective-groups :groups="languageCommunicationObjectiveGroups"></objective-groups>
|
||||
<add-objective-group-button v-if="!isStudent" type="languageCommunication"
|
||||
:module="module.id"></add-objective-group-button>
|
||||
|
||||
<objective-groups :groups="societyObjectiveGroups"></objective-groups>
|
||||
<add-objective-group-button v-if="!isStudent" type="society" :module="module.id"></add-objective-group-button>
|
||||
|
||||
<chapter :chapter="chapter" :index="index" v-for="(chapter, index) in module.chapters" :key="chapter.id"></chapter>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<modal :hide-header="true">
|
||||
<modal-input
|
||||
:placeholder="'Lernziel'"
|
||||
:value="text"
|
||||
@input="text = $event"
|
||||
></modal-input>
|
||||
|
||||
<div slot="footer">
|
||||
<a class="button button--primary" data-cy="modal-save-button" :class="{'button--disabled': disableSave}"
|
||||
v-on:click="save(text)">Speichern</a>
|
||||
<a class="button" v-on:click="hide()">Abbrechen</a>
|
||||
</div>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '@/components/Modal';
|
||||
import ModalInput from '@/components/ModalInput';
|
||||
|
||||
import NEW_OBJECTIVE_MUTATION from '@/graphql/gql/mutations/addObjective.gql';
|
||||
import OBJECTIVE_GROUP_QUERY from '@/graphql/gql/objectiveGroupQuery.gql';
|
||||
|
||||
import {mapGetters} from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Modal,
|
||||
ModalInput
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
objectiveGroup: 'currentObjectiveGroup'
|
||||
}),
|
||||
disableSave() {
|
||||
return this.saving;
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
text: '',
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
save(entry) {
|
||||
this.saving = true;
|
||||
this.$apollo.mutate({
|
||||
mutation: NEW_OBJECTIVE_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
objective: Object.assign({}, {
|
||||
objectiveGroup: this.objectiveGroup,
|
||||
text: entry
|
||||
})
|
||||
}
|
||||
},
|
||||
update: (store, {data: {addObjective: {objective}}}) => {
|
||||
try {
|
||||
const query = OBJECTIVE_GROUP_QUERY;
|
||||
const variables = {id: this.objectiveGroup};
|
||||
const data = store.readQuery({query, variables});
|
||||
if (data.objectiveGroup && data.objectiveGroup.objectives) {
|
||||
data.objectiveGroup.objectives.edges.push({
|
||||
node: objective,
|
||||
__typename: 'ObjectiveNode'
|
||||
});
|
||||
store.writeQuery({query, variables, data});
|
||||
}
|
||||
} catch (e) {
|
||||
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
this.saving = false;
|
||||
this.hide();
|
||||
});
|
||||
},
|
||||
hide() {
|
||||
this.$store.dispatch('hideModal');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<li class="objective hideable-element" :class="{'hideable-element--hidden': hidden}" v-if="editModule || !hidden">
|
||||
<visibility-action
|
||||
v-if="editModule"
|
||||
:block="objective"></visibility-action>
|
||||
<div class="block-actions" v-if="editModule && canEdit">
|
||||
<user-widget class="block-actions__user-widget objective__user-widget" v-bind="me"></user-widget>
|
||||
<more-options-widget>
|
||||
<div class="popover-links__link"><a @click="deleteObjective(objective)">Löschen</a></div>
|
||||
</more-options-widget>
|
||||
</div>
|
||||
<div>
|
||||
{{objective.text}}
|
||||
</div>
|
||||
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VisibilityAction from '@/components/visibility/VisibilityAction';
|
||||
import UserWidget from '@/components/UserWidget';
|
||||
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
|
||||
|
||||
import {mapGetters} from 'vuex';
|
||||
import {isHidden} from '@/helpers/content-block';
|
||||
import {meQuery} from '@/graphql/queries';
|
||||
import DELETE_OBJECTIVE_MUTATION from '@/graphql/gql/mutations/deleteObjective.gql';
|
||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
|
||||
|
||||
export default {
|
||||
props: ['objective', 'schoolClass'],
|
||||
|
||||
components: {
|
||||
MoreOptionsWidget,
|
||||
VisibilityAction,
|
||||
UserWidget
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['editModule']),
|
||||
hidden() {
|
||||
return isHidden(this.objective, this.schoolClass)
|
||||
},
|
||||
canEdit() {
|
||||
return this.objective.mine;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
deleteObjective(objective) {
|
||||
this.$apollo.mutate({
|
||||
mutation: DELETE_OBJECTIVE_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
id: objective.id
|
||||
}
|
||||
},
|
||||
refetchQueries: [{
|
||||
query: MODULE_DETAILS_QUERY,
|
||||
variables: {
|
||||
slug: this.$route.params.slug
|
||||
}
|
||||
}]
|
||||
// todo: make update work here also
|
||||
// update(store, {data: {deleteObjective: {success}}}) {
|
||||
// if (success) {
|
||||
// const query = MODULE_DETAILS_QUERY;
|
||||
// const variables = {slug: this.$route.params.slug};
|
||||
// const data = store.readQuery({query, variables});
|
||||
// if (data) {
|
||||
// data.module.objectiveGroups.edges.
|
||||
// data.rooms.edges.splice(data.rooms.edges.findIndex(edge => edge.node.id === theId), 1);
|
||||
// store.writeQuery({query, variables, data});
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
me: meQuery
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
me: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.objective {
|
||||
min-height: 50px;
|
||||
&__user-widget {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,31 +1,29 @@
|
|||
<template>
|
||||
<div class="objective-group">
|
||||
<div class="objective-group__actions">
|
||||
<!--visibility-action :block="group">
|
||||
</visibility-action-->
|
||||
<a @click="editObjectiveGroup()" v-if="group.mine" class="objective-group__action-button">
|
||||
<pen-icon class="objective-group__action-icon action-icon"></pen-icon>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<h4>{{group.displayTitle}}</h4>
|
||||
|
||||
<ul class="objective-group__objective-list">
|
||||
<li class="objective-group__objective" v-for="objective in group.objectives" :key="objective.id">
|
||||
{{objective.text}}
|
||||
</li>
|
||||
<objective class="objective-group__objective" v-for="objective in group.objectives" :key="objective.id"
|
||||
:objective="objective" :school-class="currentFilter">
|
||||
</objective>
|
||||
</ul>
|
||||
<add-content-button :parent="group" v-if="editModule">
|
||||
</add-content-button>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VisibilityAction from '@/components/visibility/VisibilityAction';
|
||||
import Objective from '@/components/objective-groups/Objective';
|
||||
import EyeIcon from '@/components/icons/EyeIcon';
|
||||
import PenIcon from '@/components/icons/PenIcon';
|
||||
|
||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||
import AddContentButton from '@/components/AddContentButton';
|
||||
|
||||
import {mapGetters} from 'vuex';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -36,7 +34,9 @@
|
|||
},
|
||||
|
||||
components: {
|
||||
AddContentButton,
|
||||
VisibilityAction,
|
||||
Objective,
|
||||
EyeIcon,
|
||||
PenIcon
|
||||
},
|
||||
|
|
@ -48,9 +48,13 @@
|
|||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['editModule']),
|
||||
canManageContent() {
|
||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
||||
}
|
||||
},
|
||||
currentFilter() {
|
||||
return this.me.selectedClass;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
|
||||
// import UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroupVisibility.gql';
|
||||
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
|
||||
|
||||
export default {
|
||||
props: ['block'],
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
},
|
||||
hidden() {
|
||||
// is this content block / objective group user created?
|
||||
return (this.isContentBlock ? this.block.userCreated : !!this.block.owner)
|
||||
return this.block.userCreated
|
||||
// if so, is visibility not explicitly set for this school class?
|
||||
? this.block.visibleFor.findIndex(el => el.id === this.schoolClass.id) === -1
|
||||
// otherwise, is it explicitly hidden for this school class?
|
||||
|
|
@ -66,17 +66,15 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mutation = UPDATE_OBJECTIVE_VISIBILITY_MUTATION;
|
||||
variables = {
|
||||
input: {
|
||||
id,
|
||||
visibility
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo: refactor for single objectives when concept is clear
|
||||
// else {
|
||||
// mutation = UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION;
|
||||
// variables = {
|
||||
// input: {
|
||||
// id,
|
||||
// visibility
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
this.$apollo.mutate({
|
||||
mutation,
|
||||
|
|
@ -106,6 +104,11 @@
|
|||
.visibility-action {
|
||||
margin-top: 9px;
|
||||
|
||||
position: absolute;
|
||||
left: -70px;
|
||||
top: 0px;
|
||||
display: grid;
|
||||
|
||||
&__visibility-menu {
|
||||
top: 40px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
fragment ObjectiveParts on ObjectiveNode {
|
||||
id
|
||||
text
|
||||
mine
|
||||
userCreated
|
||||
hiddenFor {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
visibleFor {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
objectiveProgress {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#import "./fragments/chapterParts.gql"
|
||||
#import "./fragments/assignmentParts.gql"
|
||||
#import "./fragments/objectiveGroupParts.gql"
|
||||
#import "./fragments/objectiveParts.gql"
|
||||
#import "./fragments/moduleParts.gql"
|
||||
query ModulesQuery($slug: String!) {
|
||||
module(slug: $slug) {
|
||||
|
|
@ -19,16 +20,7 @@ query ModulesQuery($slug: String!) {
|
|||
objectives {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
text
|
||||
objectiveProgress {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
done
|
||||
}
|
||||
}
|
||||
}
|
||||
...ObjectiveParts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#import "../fragments/objectiveParts.gql"
|
||||
mutation AddObjective($input: AddObjectiveInput!){
|
||||
addObjective(input: $input) {
|
||||
objective {
|
||||
...ObjectiveParts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#{"input": {
|
||||
# "objective": {
|
||||
# "objectiveGroup": "asdas",
|
||||
# "text": "Lern etwas"
|
||||
# }}
|
||||
#}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
mutation DeleteObjective($input: DeleteObjectiveInput!) {
|
||||
deleteObjective(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
#{
|
||||
# "input": {
|
||||
# "id": "Um9vbU5vZGU6MjY="
|
||||
# }
|
||||
#}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#import "../fragments/objectiveGroupParts.gql"
|
||||
mutation UpdateObjectiveGroupVisibility($input: UpdateObjectiveGroupVisibilityInput!) {
|
||||
updateObjectiveGroupVisibility(input: $input) {
|
||||
objectiveGroup {
|
||||
...ObjectiveGroupParts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#import "../fragments/objectiveParts.gql"
|
||||
mutation UpdateObjectiveVisibility($input: UpdateObjectiveVisibilityInput!) {
|
||||
updateObjectiveVisibility(input: $input) {
|
||||
objective {
|
||||
...ObjectiveParts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,6 +7,22 @@ query ObjectiveGroupQuery($id: ID!) {
|
|||
node {
|
||||
id
|
||||
text
|
||||
hiddenFor {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
visibleFor {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import 'babel-polyfill'
|
||||
import '@babel/polyfill'
|
||||
import Vue from 'vue'
|
||||
import axios from 'axios'
|
||||
import VueAxios from 'vue-axios'
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ export default new Vuex.Store({
|
|||
scrollToAssignmentReady: state => state.scrollToAssignmentReady,
|
||||
scrollingToAssignment: state => state.scrollingToAssignment,
|
||||
currentProjectEntry: state => state.currentProjectEntry,
|
||||
editModule: state => state.editModule
|
||||
editModule: state => state.editModule,
|
||||
currentObjectiveGroup: state => state.currentObjectiveGroup
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
@ -61,6 +62,7 @@ export default new Vuex.Store({
|
|||
commit('setContentBlockPosition', {});
|
||||
commit('setParentRoom', null);
|
||||
commit('setParentModule', '');
|
||||
// todo: remove
|
||||
commit('setObjectiveGroupType', '');
|
||||
commit('setCurrentObjectiveGroup', '');
|
||||
commit('setParentProject', null);
|
||||
|
|
@ -86,6 +88,10 @@ export default new Vuex.Store({
|
|||
commit('setContentBlockPosition', payload);
|
||||
dispatch('showModal', 'new-content-block-wizard');
|
||||
},
|
||||
addObjective({commit, dispatch}, payload) {
|
||||
commit('setCurrentObjectiveGroup', payload);
|
||||
dispatch('showModal', 'new-objective-wizard');
|
||||
},
|
||||
addRoomEntry({commit, dispatch}, payload) {
|
||||
commit('setParentRoom', payload);
|
||||
dispatch('showModal', 'new-room-entry-wizard');
|
||||
|
|
@ -94,6 +100,7 @@ export default new Vuex.Store({
|
|||
commit('setCurrentRoomEntry', payload);
|
||||
dispatch('showModal', 'edit-room-entry-wizard');
|
||||
},
|
||||
// todo: remove
|
||||
addObjectiveGroup({commit, dispatch}, {module, type}) {
|
||||
commit('setParentModule', module);
|
||||
commit('setObjectiveGroupType', type);
|
||||
|
|
@ -179,6 +186,7 @@ export default new Vuex.Store({
|
|||
setParentModule(state, payload) {
|
||||
state.parentModule = payload;
|
||||
},
|
||||
// todo: remove
|
||||
setObjectiveGroupType(state, payload) {
|
||||
state.objectiveGroupType = payload;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,3 +2,16 @@
|
|||
width: 30px;
|
||||
fill: $color-silver-dark;
|
||||
}
|
||||
|
||||
.block-actions {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -85px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__user-widget {
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
.hideable-element {
|
||||
position: relative;
|
||||
|
||||
&--hidden {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -16,3 +16,4 @@
|
|||
@import "actions";
|
||||
@import "navigation";
|
||||
@import "survey";
|
||||
@import "visibility";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { shallowMount } from '@vue/test-utils'
|
||||
import ModuleNavigation from '@/components/modules/ModuleNavigation'
|
||||
|
||||
describe('ModuleNavigation.vue', () => {
|
||||
it('should flatten an array', () => {
|
||||
const props = {
|
||||
modules: [],
|
||||
me: {}
|
||||
};
|
||||
const wrapper = shallowMount(ModuleNavigation, {
|
||||
propsData: props
|
||||
});
|
||||
|
||||
let arrayToFlatten = [[1], [2, 3], [4, 5, 6], [7]]
|
||||
let flattenedArray = wrapper.vm.flattenArray(arrayToFlatten);
|
||||
expect(flattenedArray).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
||||
});
|
||||
|
||||
it('should find top level assignment', () => {
|
||||
const props = {
|
||||
modules: [],
|
||||
me: {}
|
||||
};
|
||||
const wrapper = shallowMount(ModuleNavigation, {
|
||||
propsData: props
|
||||
});
|
||||
|
||||
let nodeData = {
|
||||
type: 'assignment',
|
||||
id: 1
|
||||
}
|
||||
|
||||
let assignment = wrapper.vm.findAssignment(nodeData);
|
||||
expect(assignment).toEqual([nodeData]);
|
||||
});
|
||||
|
||||
it('should find content list assignments', () => {
|
||||
const props = {
|
||||
modules: [],
|
||||
me: {}
|
||||
};
|
||||
const wrapper = shallowMount(ModuleNavigation, {
|
||||
propsData: props
|
||||
});
|
||||
|
||||
let assignments = [
|
||||
{
|
||||
type: 'assignment',
|
||||
id: 2
|
||||
},
|
||||
{
|
||||
type: 'assignment',
|
||||
id: 3
|
||||
}
|
||||
];
|
||||
|
||||
let nodeData = {
|
||||
type: 'content_list_item',
|
||||
id: 1,
|
||||
value: assignments
|
||||
}
|
||||
|
||||
let foundAssignments = wrapper.vm.findAssignment(nodeData);
|
||||
expect(foundAssignments).toEqual(assignments);
|
||||
});
|
||||
|
||||
})
|
||||
|
|
@ -122,22 +122,6 @@ class ModuleNode(DjangoObjectType):
|
|||
def resolve_solutions_enabled(self, info, **kwargs):
|
||||
return self.solutions_enabled_by.filter(pk=info.context.user.pk).exists()
|
||||
|
||||
def resolve_objective_groups(self, info, **kwargs):
|
||||
user = info.context.user
|
||||
school_classes = user.school_classes.values_list('pk')
|
||||
|
||||
if user.has_perm('users.can_manage_school_class_content'): # teacher
|
||||
publisher_objective_groups = self.objective_groups.filter(owner=None)
|
||||
user_created_objective_groups = self.objective_groups.filter(owner=user)
|
||||
else: # student
|
||||
publisher_objective_groups = self.objective_groups.filter(owner=None).exclude(
|
||||
hidden_for__in=school_classes)
|
||||
|
||||
user_created_objective_groups = self.objective_groups.filter(owner__isnull=False,
|
||||
visible_for__in=school_classes)
|
||||
|
||||
return publisher_objective_groups.union(user_created_objective_groups)
|
||||
|
||||
|
||||
class TopicNode(DjangoObjectType):
|
||||
pk = graphene.Int()
|
||||
|
|
@ -146,7 +130,7 @@ class TopicNode(DjangoObjectType):
|
|||
class Meta:
|
||||
model = Topic
|
||||
only_fields = [
|
||||
'slug', 'title', 'meta_title', 'teaser', 'description', 'vimeo_id', 'order'
|
||||
'slug', 'title', 'meta_title', 'teaser', 'description', 'vimeo_id', 'order'
|
||||
]
|
||||
filter_fields = {
|
||||
'slug': ['exact', 'icontains', 'in'],
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ class Command(BaseCommand):
|
|||
self.stdout.write("Assigning teacher role")
|
||||
teacher = Role.objects.get(key='teacher')
|
||||
UserRole.objects.get_or_create(user=user, role=teacher)
|
||||
else:
|
||||
self.stdout.write("Assigning student role")
|
||||
student = Role.objects.get(key='teacher')
|
||||
UserRole.objects.get_or_create(user=user, role=student)
|
||||
|
||||
self.stdout.write("Adding to class(es) {}".format(', '.join(school_class_names)))
|
||||
for school_class_name in school_class_names:
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ class ObjectiveGroupAdmin(admin.ModelAdmin):
|
|||
|
||||
@admin.register(Objective)
|
||||
class ObjectiveAdmin(admin.ModelAdmin):
|
||||
list_display = ('text', 'group')
|
||||
list_filter = ('group',)
|
||||
list_display = ('text', 'group', 'owner')
|
||||
list_filter = ('group', 'owner')
|
||||
|
||||
|
||||
@admin.register(ObjectiveProgressStatus)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ class ObjectiveInput(InputObjectType):
|
|||
id = graphene.ID()
|
||||
|
||||
|
||||
class AddObjectiveArgument(InputObjectType):
|
||||
text = graphene.String(required=True)
|
||||
objective_group = graphene.ID(reuired=True)
|
||||
|
||||
class AddObjectiveGroupArgument(InputObjectType):
|
||||
title = graphene.String(required=True)
|
||||
module = graphene.ID(required=True)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# Generated by Django 2.0.6 on 2019-08-21 12:52
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0007_usersetting'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('objectives', '0007_auto_20181031_1347'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='objective',
|
||||
name='hidden_for',
|
||||
field=models.ManyToManyField(blank=True, related_name='hidden_objectives', to='users.SchoolClass'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='objective',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='objective',
|
||||
name='visible_for',
|
||||
field=models.ManyToManyField(blank=True, related_name='visible_objectives', to='users.SchoolClass'),
|
||||
),
|
||||
]
|
||||
|
|
@ -27,7 +27,7 @@ class ObjectiveGroup(models.Model):
|
|||
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_objective_groups', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return 'ObjectiveGroup {}-{}-{}'.format(self.id, self.module, self.title)
|
||||
return '{} - {}'.format(self.module, self.title)
|
||||
|
||||
|
||||
class Objective(models.Model):
|
||||
|
|
@ -38,6 +38,9 @@ class Objective(models.Model):
|
|||
text = models.CharField('text', blank=True, null=False, max_length=255)
|
||||
group = models.ForeignKey(ObjectiveGroup, blank=False, null=False, on_delete=models.CASCADE,
|
||||
related_name='objectives')
|
||||
owner = models.ForeignKey(get_user_model(), blank=True, null=True, on_delete=models.CASCADE)
|
||||
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objectives', blank=True)
|
||||
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_objectives', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return 'Objective {}-{}'.format(self.id, self.text)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from api.utils import get_object
|
|||
from books.models import Module
|
||||
from books.schema.inputs import UserGroupBlockVisibility
|
||||
from core.utils import set_visible_for, set_hidden_for
|
||||
from objectives.inputs import AddObjectiveGroupArgument, UpdateObjectiveGroupArgument
|
||||
from objectives.inputs import AddObjectiveGroupArgument, UpdateObjectiveGroupArgument, AddObjectiveArgument
|
||||
from objectives.models import ObjectiveProgressStatus, Objective, ObjectiveGroup
|
||||
from objectives.schema import ObjectiveNode, ObjectiveGroupNode
|
||||
|
||||
|
|
@ -36,28 +36,74 @@ class UpdateObjectiveProgress(relay.ClientIDMutation):
|
|||
return cls(objective=objective)
|
||||
|
||||
|
||||
class UpdateObjectiveGroupVisibility(relay.ClientIDMutation):
|
||||
class UpdateObjectiveVisibility(relay.ClientIDMutation):
|
||||
class Input:
|
||||
id = graphene.ID(required=True, description='The ID of the objective group')
|
||||
id = graphene.ID(required=True, description='The ID of the objective')
|
||||
visibility = graphene.List(UserGroupBlockVisibility)
|
||||
|
||||
objective_group = graphene.Field(ObjectiveGroupNode)
|
||||
objective = graphene.Field(ObjectiveNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
objective_group_id = kwargs.get('id')
|
||||
objective_id = kwargs.get('id')
|
||||
visibility_list = kwargs.get('visibility')
|
||||
objective_group = get_object(ObjectiveGroup, objective_group_id) # info.context.user = django user
|
||||
objective = get_object(Objective, objective_id) # info.context.user = django user
|
||||
|
||||
if visibility_list is not None:
|
||||
if objective_group.owner is not None:
|
||||
set_visible_for(objective_group, visibility_list)
|
||||
if objective.owner is not None:
|
||||
set_visible_for(objective, visibility_list)
|
||||
else:
|
||||
set_hidden_for(objective_group, visibility_list)
|
||||
set_hidden_for(objective, visibility_list)
|
||||
|
||||
objective_group.save()
|
||||
objective.save()
|
||||
|
||||
return cls(objective=objective)
|
||||
|
||||
|
||||
class AddObjective(relay.ClientIDMutation):
|
||||
class Input:
|
||||
objective = graphene.Argument(AddObjectiveArgument)
|
||||
|
||||
objective = graphene.Field(ObjectiveNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
owner = info.context.user
|
||||
if not owner.has_perm('users.can_manage_school_class_content'):
|
||||
raise PermissionDenied('Missing permissions')
|
||||
|
||||
objective_data = kwargs.get('objective')
|
||||
objective_group_id = objective_data.get('objective_group')
|
||||
text = objective_data.get('text')
|
||||
|
||||
objective_group = get_object(ObjectiveGroup, objective_group_id)
|
||||
|
||||
objective = Objective.objects.create(text=text, owner=owner, group=objective_group)
|
||||
|
||||
return cls(objective=objective)
|
||||
|
||||
|
||||
class DeleteObjective(relay.ClientIDMutation):
|
||||
class Input:
|
||||
id = graphene.ID(required=True)
|
||||
|
||||
success = graphene.Boolean()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
id = kwargs.get('id')
|
||||
|
||||
objective = get_object(Objective, id)
|
||||
user = info.context.user
|
||||
|
||||
if not user.has_perm('users.can_manage_school_class_content'):
|
||||
raise PermissionDenied('Missing permissions')
|
||||
if objective.owner.pk != user.pk:
|
||||
raise PermissionDenied('Permission denied: Not owner')
|
||||
|
||||
objective.delete()
|
||||
return cls(success=True)
|
||||
|
||||
return cls(objective_group=objective_group)
|
||||
|
||||
|
||||
class AddObjectiveGroup(relay.ClientIDMutation):
|
||||
|
|
@ -126,6 +172,8 @@ class UpdateObjectiveGroup(relay.ClientIDMutation):
|
|||
|
||||
class ObjectiveMutations:
|
||||
update_objective_progress = UpdateObjectiveProgress.Field()
|
||||
update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field()
|
||||
update_objective_visibility = UpdateObjectiveVisibility.Field()
|
||||
add_objective = AddObjective.Field()
|
||||
delete_objective = DeleteObjective.Field()
|
||||
add_objective_group = AddObjectiveGroup.Field()
|
||||
update_objective_group = UpdateObjectiveGroup.Field()
|
||||
|
|
|
|||
|
|
@ -25,9 +25,27 @@ class ObjectiveGroupNode(DjangoObjectType):
|
|||
def resolve_mine(self, info, **kwargs):
|
||||
return self.owner is not None and self.owner.pk == info.context.user.pk
|
||||
|
||||
def resolve_objectives(self, info, **kwargs):
|
||||
user = info.context.user
|
||||
school_classes = user.school_classes.values_list('pk')
|
||||
|
||||
if user.has_perm('users.can_manage_school_class_content'): # teacher
|
||||
publisher_objectives = self.objectives.filter(owner=None)
|
||||
user_created_objectives = self.objectives.filter(owner=user)
|
||||
else: # student
|
||||
publisher_objectives = self.objectives.filter(owner=None).exclude(
|
||||
hidden_for__in=school_classes)
|
||||
|
||||
user_created_objectives = self.objectives.filter(owner__isnull=False,
|
||||
visible_for__in=school_classes)
|
||||
|
||||
return publisher_objectives.union(user_created_objectives)
|
||||
|
||||
|
||||
class ObjectiveNode(DjangoObjectType):
|
||||
pk = graphene.Int()
|
||||
user_created = graphene.Boolean()
|
||||
mine = graphene.Boolean()
|
||||
|
||||
class Meta:
|
||||
model = Objective
|
||||
|
|
@ -37,6 +55,11 @@ class ObjectiveNode(DjangoObjectType):
|
|||
def resolve_objective_progress(self, info, **kwargs):
|
||||
return self.objective_progress.filter(user=info.context.user)
|
||||
|
||||
def resolve_user_created(self, info, **kwargs):
|
||||
return self.owner is not None
|
||||
|
||||
def resolve_mine(self, info, **kwargs):
|
||||
return self.owner is not None and self.owner.pk == info.context.user.pk
|
||||
|
||||
class ObjectiveProgressStatusNode(DjangoObjectType):
|
||||
pk = graphene.Int()
|
||||
|
|
|
|||
Loading…
Reference in New Issue