Merged in feature/chapter-visibility (pull request #78)

Feature/chapter visibility

Approved-by: Christian Cueni
This commit is contained in:
Ramon Wenger 2021-02-23 10:13:16 +00:00
commit 3660a282a3
44 changed files with 621 additions and 482 deletions

View File

@ -23,7 +23,7 @@ python-dotenv = "==0.13.0"
dj-database-url = "==0.4.1"
raven = "==6.9.0"
django-extensions = "==1.9.8"
graphene-django = "==2.2.0"
graphene-django = "==2.15.0"
django-filter = "==2.0.0"
djangorestframework = "==3.8.2"
pillow = "==5.0.0"

90
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "a5323ad6907e180d4901fe31c0edc344ee9890f7a95577b2d60e5a1ee19be2f3"
"sha256": "58d8faf7e03679ac7b0053dd01e54288d3a719c8ee25c1edf20a74ebcbf87951"
},
"pipfile-spec": 6,
"requires": {
@ -25,9 +25,10 @@
},
"autopep8": {
"hashes": [
"sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"
"sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
"sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
],
"version": "==1.5.4"
"version": "==1.5.5"
},
"backcall": {
"hashes": [
@ -54,18 +55,18 @@
},
"boto3": {
"hashes": [
"sha256:2a39bd5e5f2d50ce9267d682cc92750f8771399665021f47e80f9c8d2fb812a6",
"sha256:b4860f56bc585d3d1fde90d288da5eb4d1198401d72201dc3e25de8887b080e2"
"sha256:1709ff5feb363fee7fcaa2330e659fcbc2b4c03a14f75a884ed682ee66011fc4",
"sha256:80a761eff3b1cb0798d7e1a41b7c8e6d85c9647a8f7b6105335201a69404caa2"
],
"index": "pypi",
"version": "==1.17.0"
"version": "==1.17.10"
},
"botocore": {
"hashes": [
"sha256:634b39ab0d55477cfbffb0e5dff31b7ab4bb171b04a0c69f8bcf65135f26ba94",
"sha256:a608d6d644b852f3c154fc433eaae52febbebc7c474fa8f4d666797d0931770a"
"sha256:8c84eac6daf38890714e005623083106d68e9b2088e62132fdbf7d2b1228ecbd",
"sha256:a601ee5a4ae66832f328ca362b5404d22b75f1c181f6cc0934f3cfca749eb27d"
],
"version": "==1.20.0"
"version": "==1.20.10"
},
"certifi": {
"hashes": [
@ -189,10 +190,10 @@
},
"django-treebeard": {
"hashes": [
"sha256:214ae3ab331a7de11fb055a2015c201e34f3fa14255b667e1e07752231a7a398",
"sha256:f50e4eea146f7af6702decf7ef198ac1eee1fb9bb4af2c5dba276c3c48f76623"
"sha256:313fb61843e1ad025262014c382bc0c58fefc064cf5401421dcb5a2cfdacdc9d",
"sha256:8085928debdd187e9919afc522ea40069bb9f090fa804c7ae9a324b0f62843c6"
],
"version": "==4.4"
"version": "==4.5"
},
"djangorestframework": {
"hashes": [
@ -219,10 +220,10 @@
},
"faker": {
"hashes": [
"sha256:0783729c61501d52efea2967aff6e6fcb8370f0f6b5a558f2a81233642ae529a",
"sha256:6b2995ffff6c2b02bc5daad96f8c24c021e5bd491d9d53d31bcbd66f348181d4"
"sha256:3971803f32728314c54ba051139cd433fc93fde371e18d07a2cec960a7a2222a",
"sha256:b27f9bc97490a11f14c1501cc25f1109cf68c75f11c6ef97714757a4298c33e5"
],
"version": "==5.8.0"
"version": "==6.3.0"
},
"future": {
"hashes": [
@ -245,11 +246,11 @@
},
"graphene-django": {
"hashes": [
"sha256:3afd81d47c8b702650e05cc1179fac1cfceae991d241bb164d51f28bed9ec95c",
"sha256:760a18068feb5457e2ec00d2447c09b2fbac2a6b8c32cc8be2abce3752107ad3"
"sha256:02671d195f0c09c8649acff2a8f4ad4f297d0f7d98ea6e6cdf034b81bab92880",
"sha256:b78c9b05bc899016b9cc5bf13faa1f37fe1faa8c5407552c6ddd1a28f46fc31a"
],
"index": "pypi",
"version": "==2.2.0"
"version": "==2.15.0"
},
"graphql-core": {
"hashes": [
@ -500,10 +501,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
],
"version": "==3.0.14"
"version": "==3.0.16"
},
"psycopg2": {
"hashes": [
@ -542,10 +543,10 @@
},
"pygments": {
"hashes": [
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
"sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0",
"sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"
],
"version": "==2.7.4"
"version": "==2.8.0"
},
"pyparsing": {
"hashes": [
@ -641,10 +642,10 @@
},
"sendgrid": {
"hashes": [
"sha256:499a4910623c03e73cb27bd9ef7cadd0968eb2c811afd5c83cfb517f76163f65",
"sha256:5a87682dba540b706683d4b4a3a605e11fbe24f340ecff5fd502bfb17dfa7ef8"
"sha256:2eb1dcb1f7d8656eed4db586e428c2c86f347590b8511d7f92993882d0e4fab9",
"sha256:e422c8263563ac7d664066d2f87b90bcb005b067eb7c33a9b1396442b2ed285b"
],
"version": "==6.5.0"
"version": "==6.6.0"
},
"sentry-sdk": {
"hashes": [
@ -711,10 +712,10 @@
},
"unidecode": {
"hashes": [
"sha256:4c9d15d2f73eb0d2649a151c566901f80a030da1ccb0a2043352e1dbf647586b",
"sha256:a039f89014245e0cad8858976293e23501accc9ff5a7bdbc739a14a2b7b85cdc"
"sha256:12435ef2fc4cdfd9cf1035a1db7e98b6b047fe591892e81f34e94959591fad00",
"sha256:8d73a97d387a956922344f6b74243c2c6771594659778744b2dbdaad8f6b727d"
],
"version": "==1.1.2"
"version": "==1.2.0"
},
"unittest-xml-reporting": {
"hashes": [
@ -794,17 +795,18 @@
},
"autopep8": {
"hashes": [
"sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"
"sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
"sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
],
"version": "==1.5.4"
"version": "==1.5.5"
},
"awscli": {
"hashes": [
"sha256:2bdc2ef330f9334dbeefb91c942061feb6a53646bd55c663e0056f8d0dc66fed",
"sha256:5a28b63869261c5c2f4ee83f7c43d8ec9622f790d0daf73f91643dcf7148bcf8"
"sha256:299161d80c226fea405a69fda44fa90cec3d5cf2e484021e9c323d6454246d20",
"sha256:f04edb9f34308a84541ba125387bb9d7f4ae03c066b03d46af306e995c4c5e42"
],
"index": "pypi",
"version": "==1.19.0"
"version": "==1.19.10"
},
"backcall": {
"hashes": [
@ -815,10 +817,10 @@
},
"botocore": {
"hashes": [
"sha256:634b39ab0d55477cfbffb0e5dff31b7ab4bb171b04a0c69f8bcf65135f26ba94",
"sha256:a608d6d644b852f3c154fc433eaae52febbebc7c474fa8f4d666797d0931770a"
"sha256:8c84eac6daf38890714e005623083106d68e9b2088e62132fdbf7d2b1228ecbd",
"sha256:a601ee5a4ae66832f328ca362b5404d22b75f1c181f6cc0934f3cfca749eb27d"
],
"version": "==1.20.0"
"version": "==1.20.10"
},
"certifi": {
"hashes": [
@ -1064,10 +1066,10 @@
},
"prompt-toolkit": {
"hashes": [
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
],
"version": "==3.0.14"
"version": "==3.0.16"
},
"ptyprocess": {
"hashes": [
@ -1103,10 +1105,10 @@
},
"pygments": {
"hashes": [
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
"sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0",
"sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"
],
"version": "==2.7.4"
"version": "==2.8.0"
},
"python-dateutil": {
"hashes": [

View File

@ -5,7 +5,7 @@
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
}]
],
"plugins": [
"transform-vue-jsx",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,12 +17,12 @@ const topic = {
return {
node: module,
__typename: 'ModuleNodeEdge'
}
};
})
]
}
}
}
};
Cypress.Commands.add('checkHome', (n, skipHome) => {
if (!skipHome) {
@ -71,7 +71,7 @@ describe('Current Module', () => {
'__typename': 'UserNode',
'permissions': []
}
}
};
},
AssignmentsQuery: {
assignments
@ -79,7 +79,7 @@ describe('Current Module', () => {
ModulesQuery: variables => {
return {
module: fullModules[variables.slug]
}
};
},
TopicsQuery: topics,
Topic: topic,
@ -95,7 +95,7 @@ describe('Current Module', () => {
lastModule: moduleTeasers[variables.input.id],
__typename: 'UpdateLastModulePayload'
}
}
};
}
}
});
@ -150,5 +150,5 @@ describe('Current Module', () => {
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Lerntipps');
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Random');
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Geld');
})
});
});

View File

@ -2,7 +2,20 @@
<div
:data-scrollto="chapter.id"
class="chapter">
<h3 :id="'chapter-' + index">{{ chapter.title }}</h3>
<div
:class="{'hideable-element--greyed-out': titleGreyedOut}"
class="hideable-element"
v-if="!titleHidden">
<h3
:id="'chapter-' + index"
>{{ chapter.title }}</h3>
</div>
<visibility-action
:block="chapter"
type="chapter-title"
v-if="editModule"
/>
<bookmark-actions
:bookmarked="chapter.bookmark"
@ -12,9 +25,22 @@
@edit-note="editNote"
@bookmark="bookmark(!chapter.bookmark)"
/>
<p class="chapter__description intro">
{{ chapter.description }}
</p>
<div
:class="{'hideable-element--greyed-out': descriptionGreyedOut}"
class="chapter__intro intro hideable-element"
v-if="!descriptionHidden"
>
<visibility-action
:block="chapter"
:chapter="true"
type="chapter-description"
v-if="editModule"
/>
<p
class="chapter__description">
{{ chapter.description }}
</p>
</div>
<add-content-button
:parent="chapter"
@ -29,132 +55,154 @@
</template>
<script>
import ContentBlock from '@/components/ContentBlock';
import AddContentButton from '@/components/AddContentButton';
import BookmarkActions from '@/components/notes/BookmarkActions';
import ContentBlock from '@/components/ContentBlock';
import AddContentButton from '@/components/AddContentButton';
import BookmarkActions from '@/components/notes/BookmarkActions';
import VisibilityAction from '@/components/visibility/VisibilityAction';
import {mapState} from 'vuex';
import {isHidden} from '@/helpers/content-block';
import {meQuery} from '@/graphql/queries';
import {mapState} from 'vuex';
import {hidden} from '@/helpers/visibility';
import {CONTENT_TYPE, CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE} from '@/consts/types';
import UPDATE_CHAPTER_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateChapterBookmark.gql';
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
import UPDATE_CHAPTER_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateChapterBookmark.gql';
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
export default {
props: ['chapter', 'index'],
import me from '@/mixins/me';
components: {
BookmarkActions,
ContentBlock,
AddContentButton
export default {
props: ['chapter', 'index'],
mixins: [me],
components: {
BookmarkActions,
VisibilityAction,
ContentBlock,
AddContentButton
},
computed: {
...mapState(['editModule']),
filteredContentBlocks() {
if (!(this.chapter && this.chapter.contentBlocks)) {
return [];
}
if (this.editModule) {
return this.chapter.contentBlocks;
}
return this.chapter.contentBlocks.filter(contentBlock => !hidden({
block: contentBlock,
schoolClass: this.schoolClass,
type: CONTENT_TYPE
}));
},
data() {
return {
me: {}
};
},
computed: {
...mapState(['editModule']),
filteredContentBlocks() {
if (!(this.chapter && this.chapter.contentBlocks)) {
return [];
}
if (this.editModule) {
return this.chapter.contentBlocks;
}
return this.chapter.contentBlocks.filter(contentBlock => !isHidden(contentBlock, this.schoolClass));
},
schoolClass() {
return this.me.selectedClass;
},
note() {
if (!(this.chapter && this.chapter.bookmark)) {
return;
}
note() {
if (this.chapter && this.chapter.bookmark) {
return this.chapter.bookmark.note;
}
},
methods: {
bookmark(bookmarked) {
const id = this.chapter.id;
this.$apollo.mutate({
mutation: UPDATE_CHAPTER_BOOKMARK_MUTATION,
variables: {
input: {
chapter: id,
bookmarked
}
},
update: (store, response) => {
const query = CHAPTER_QUERY;
const variables = {id};
const data = store.readQuery({
query,
variables
});
const chapter = data.chapter;
if (bookmarked) {
chapter.bookmark = {
__typename: 'ChapterBookmarkNode',
note: null
};
} else {
chapter.bookmark = null;
}
data.chapter = chapter;
store.writeQuery({
data,
query,
variables
});
},
optimisticResponse: {
__typename: 'Mutation',
updateChapterBookmark: {
__typename: 'UpdateChapterBookmarkPayload',
success: true
}
}
});
},
addNote(id) {
this.$store.dispatch('addNote', {
content: id,
parent: this.chapter.id
});
},
editNote() {
this.$store.dispatch('editNote', this.chapter.bookmark.note);
},
titleGreyedOut() {
return this.textHidden(CHAPTER_TITLE_TYPE) && this.editModule;
},
apollo: {
me: meQuery
// never hidden when editing the module
titleHidden() {
return this.textHidden(CHAPTER_TITLE_TYPE) && !this.editModule;
},
descriptionGreyedOut() {
return this.textHidden(CHAPTER_DESCRIPTION_TYPE) && this.editModule;
},
// never hidden when editing the module
descriptionHidden() {
return this.textHidden(CHAPTER_DESCRIPTION_TYPE) && !this.editModule;
}
};
},
methods: {
bookmark(bookmarked) {
const id = this.chapter.id;
this.$apollo.mutate({
mutation: UPDATE_CHAPTER_BOOKMARK_MUTATION,
variables: {
input: {
chapter: id,
bookmarked
}
},
update: (store, response) => {
const query = CHAPTER_QUERY;
const variables = {id};
const data = store.readQuery({
query,
variables
});
const chapter = data.chapter;
if (bookmarked) {
chapter.bookmark = {
__typename: 'ChapterBookmarkNode',
note: null
};
} else {
chapter.bookmark = null;
}
data.chapter = chapter;
store.writeQuery({
data,
query,
variables
});
},
optimisticResponse: {
__typename: 'Mutation',
updateChapterBookmark: {
__typename: 'UpdateChapterBookmarkPayload',
success: true
}
}
});
},
addNote(id) {
this.$store.dispatch('addNote', {
content: id,
parent: this.chapter.id
});
},
editNote() {
this.$store.dispatch('editNote', this.chapter.bookmark.note);
},
textHidden(type) {
return hidden({
block: this.chapter,
schoolClass: this.schoolClass,
type
});
}
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_mixins.scss";
@import "@/styles/_mixins.scss";
.chapter {
position: relative;
.chapter {
position: relative;
&__bookmark-actions {
margin-top: 3px;
}
&__description {
@include lead-paragraph;
margin-bottom: $large-spacing;
}
&__bookmark-actions {
margin-top: 3px;
}
&__intro {
position: relative;
}
&__description {
@include lead-paragraph;
margin-bottom: $large-spacing;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div
:class="{'hideable-element--hidden': hidden}"
:class="{'hideable-element--greyed-out': hidden}"
class="content-block__container hideable-element">
<div
:class="specialClass"
@ -60,11 +60,11 @@
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
import {meQuery} from '@/graphql/queries';
import {mapState} from 'vuex';
import {isHidden} from '@/helpers/content-block';
import me from '@/mixins/me';
import {hidden} from '@/helpers/visibility';
import {CONTENT_TYPE} from '@/consts/types';
const instruments = {
base_communication: 'Sprache & Kommunikation',
@ -77,6 +77,8 @@
name: 'ContentBlock',
props: ['contentBlock', 'parent'],
mixins: [me],
components: {
ContentComponent,
AddContentButton,
@ -85,13 +87,6 @@
UserWidget
},
data() {
return {
showVisibility: false,
me: {}
};
},
computed: {
...mapState(['editModule']),
canEditModule() {
@ -164,11 +159,12 @@
contents: this.removeSingleContentListItem(newContent, startingIndex)
});
},
schoolClass() {
return this.me.selectedClass;
},
hidden() {
return isHidden(this.contentBlock, this.schoolClass);
return hidden({
block: this.contentBlock,
schoolClass: this.schoolClass,
type: CONTENT_TYPE
});
},
root() {
// we need the root content block id, not the generated content block if inside a content list block
@ -235,10 +231,6 @@
}
return [...content.slice(0, listIndex), ...content[listIndex].contents[0].value, ...content.slice(listIndex + 1)];
}
},
apollo: {
me: meQuery
}
};
</script>

View File

@ -47,14 +47,11 @@
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
import Chapter from '@/components/Chapter.vue';
import UPDATE_OBJECTIVE_PROGRESS_MUTATION from '@/graphql/gql/mutations/updateObjectiveProgress.gql';
import UPDATE_LAST_MODULE_MUTATION from '@/graphql/gql/mutations/updateLastModule.gql';
import UPDATE_MODULE_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateModuleBookmark.gql';
import OBJECTIVE_QUERY from '@/graphql/gql/objectiveQuery.gql';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql';
import {withoutOwnerFirst} from '@/helpers/sorting';
import BookmarkActions from '@/components/notes/BookmarkActions';
import meMixin from '@/mixins/me';
@ -82,18 +79,15 @@
computed: {
languageCommunicationObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'LANGUAGE_COMMUNICATION')
.sort(withoutOwnerFirst) : [];
.filter(group => group.title === 'LANGUAGE_COMMUNICATION') : [];
},
societyObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'SOCIETY')
.sort(withoutOwnerFirst) : [];
.filter(group => group.title === 'SOCIETY') : [];
},
interdisciplinaryObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'INTERDISCIPLINARY')
.sort(withoutOwnerFirst) : [];
.filter(group => group.title === 'INTERDISCIPLINARY') : [];
},
isStudent() {
return !this.me.permissions.includes('users.can_manage_school_class_content');
@ -145,28 +139,6 @@
}
});
},
updateObjectiveProgress(done, objectiveId) {
this.$apollo.mutate({
mutation: UPDATE_OBJECTIVE_PROGRESS_MUTATION,
variables: {
input: {
id: objectiveId,
done: done
}
},
update(store, {data: {updateObjectiveProgress: {objective}}}) {
if (objective) {
const variables = {id: objectiveId};
const query = OBJECTIVE_QUERY;
const data = store.readQuery({query, variables});
if (data && data.objective.objectiveProgress.edges.length > 0) {
data.objective.objectiveProgress.edges[0].node.done = done;
}
store.writeQuery({query: OBJECTIVE_QUERY, data, variables});
}
}
});
},
bookmark(bookmarked) {
const slug = this.module.slug;
this.$apollo.mutate({

View File

@ -59,21 +59,20 @@
<script>
import {moduleQuery} from '@/graphql/queries';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import SubNavigationItem from '@/components/book-navigation/SubNavigationItem';
import ToggleSolutionsForModule from '@/components/toggle-menu/ToggleSolutionsForModule';
import ToggleEditing from '@/components/toggle-menu/ToggleEditing';
import ChevronLeft from '@/components/icons/ChevronLeft';
import me from '@/mixins/me';
export default {
apollo: {
module: moduleQuery,
me: {
query: ME_QUERY
}
},
mixins: [me],
components: {
SubNavigationItem,
ToggleSolutionsForModule,
@ -87,9 +86,6 @@
assignments: [],
topic: {}
},
me: {
permissions: []
}
};
},
@ -103,9 +99,6 @@
showResults() {
return this.me.permissions.includes('users.can_manage_school_class_content');
},
canManageContent() {
return this.me.permissions.includes('users.can_manage_school_class_content');
},
assignments() {
if (!this.module.chapters) {
return [];

View File

@ -1,10 +1,11 @@
<template>
<li
:class="{'hideable-element--hidden': hidden}"
:class="{'hideable-element--greyed-out': hidden}"
class="objective hideable-element"
v-if="editModule || !hidden">
<visibility-action
:block="objective"
:type="type"
v-if="editModule"/>
<div
class="block-actions"
@ -28,15 +29,19 @@
import UserWidget from '@/components/UserWidget';
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import {mapState} 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';
import {hidden} from '@/helpers/visibility';
import {OBJECTIVE_TYPE} from '@/consts/types';
import editModule from '@/mixins/edit-module';
import me from '@/mixins/me';
export default {
props: ['objective', 'schoolClass'],
mixins: [me, editModule],
components: {
MoreOptionsWidget,
VisibilityAction,
@ -45,14 +50,17 @@
data() {
return {
me: {}
type: OBJECTIVE_TYPE
};
},
computed: {
...mapState(['editModule']),
hidden() {
return isHidden(this.objective, this.schoolClass);
return hidden({
block: this.objective,
schoolClass: this.schoolClass,
type: this.type
});
},
canEdit() {
return this.objective.mine;
@ -89,17 +97,14 @@
// }
});
}
},
apollo: {
me: meQuery
},
}
};
</script>
<style scoped lang="scss">
.objective {
min-height: 50px;
&__user-widget {
margin-right: 0;
}

View File

@ -1,5 +1,12 @@
<template>
<div class="objective-group">
<div
:class="{'hideable-element--greyed-out': hidden}"
class="objective-group hideable-element"
v-if="editModule || !hidden">
<visibility-action
:block="group"
:type="type"
v-if="editModule"/>
<h4>{{ group.displayTitle }}</h4>
@ -7,7 +14,7 @@
<objective
:key="objective.id"
:objective="objective"
:school-class="currentFilter"
:school-class="schoolClass"
class="objective-group__objective"
v-for="objective in group.objectives"/>
</ul>
@ -20,11 +27,14 @@
<script>
import Objective from '@/components/objective-groups/Objective';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import VisibilityAction from '@/components/visibility/VisibilityAction';
import AddContentButton from '@/components/AddContentButton';
import {mapState} from 'vuex';
import me from '@/mixins/me';
import editModule from '@/mixins/edit-module';
import {OBJECTIVE_GROUP_TYPE} from '@/consts/types';
import {hidden} from '@/helpers/visibility';
export default {
props: {
@ -34,24 +44,27 @@
}
},
mixins: [me, editModule],
components: {
AddContentButton,
Objective
Objective,
VisibilityAction
},
apollo: {
me: {
query: ME_QUERY,
},
data() {
return {
type: OBJECTIVE_GROUP_TYPE
};
},
computed: {
...mapState(['editModule']),
canManageContent() {
return this.me.permissions.includes('users.can_manage_school_class_content');
},
currentFilter() {
return this.me.selectedClass;
hidden() {
return hidden({
block: this.group,
schoolClass: this.schoolClass,
type: this.type
});
},
},
};

View File

@ -10,7 +10,6 @@
<script>
import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup';
import {meQuery} from '@/graphql/queries';
import {withoutOwnerFirst} from '@/helpers/sorting';
export default {
props: {
@ -47,19 +46,13 @@
return this.groups;
} else {
// todo: maybe this can be done a bit more elegantly
const groups = [...this.groups].sort(withoutOwnerFirst);
let groups = this.groups;
const objectives = groups.map(g => g.objectives).flat(); // get all objectives in one array
const firstGroup = Object.assign({}, groups.shift(), {objectives});
return [firstGroup];
}
}
},
methods: {
updateObjectiveProgress(checked, id) {
this.$emit('updateObjectiveProgress', checked, id);
}
}
};
</script>

View File

@ -8,7 +8,7 @@
<script>
import Checkbox from '@/components/Checkbox';
import {mapGetters, mapActions} from 'vuex';
import {mapState, mapActions} from 'vuex';
export default {
components: {
@ -16,7 +16,7 @@
},
computed: {
...mapGetters({
...mapState({
checked: 'editModule',
})
},

View File

@ -18,79 +18,51 @@
import EyeIcon from '@/components/icons/EyeIcon';
import ClosedEyeIcon from '@/components/icons/ClosedEyeIcon';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
import me from '@/mixins/me';
import {TYPES, CONTENT_TYPE} from '@/consts/types';
import {createVisibilityMutation, hidden} from '@/helpers/visibility';
export default {
props: ['block'],
props: {
block: {
type: Object,
default: () => ({})
},
type: {
type: String,
default: CONTENT_TYPE,
validator: value => {
// value must be one of TYPES
return TYPES.indexOf(value) !== -1;
}
}
},
mixins: [me],
components: {
EyeIcon,
ClosedEyeIcon
},
data() {
return {
showVisibility: false,
me: {
permissions: []
}
};
},
computed: {
canManageContent() {
return this.me.permissions.includes('users.can_manage_school_class_content');
},
isContentBlock() {
return this.block.__typename === 'ContentBlockNode';
},
schoolClass() {
return this.me.selectedClass;
},
hidden() {
// is this content block / objective group user created?
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?
: this.block.hiddenFor.findIndex(el => el.id === this.schoolClass.id) > -1;
return hidden({type: this.type, block: this.block, schoolClass: this.schoolClass});
}
},
methods: {
toggleVisibility() {
let hidden = !this.hidden;
let schoolClassId = this.schoolClass.id;
const hidden = !this.hidden;
const schoolClassId = this.schoolClass.id;
const visibility = [{
schoolClassId,
hidden
}];
let mutation, variables;
const id = this.block.id;
if (this.isContentBlock) {
mutation = CHANGE_CONTENT_BLOCK_MUTATION;
variables = {
input: {
id,
contentBlock: {
visibility
}
}
};
} else {
mutation = UPDATE_OBJECTIVE_VISIBILITY_MUTATION;
variables = {
input: {
id,
visibility
}
};
}
const {mutation, variables} = createVisibilityMutation(this.type, this.block.id, visibility);
this.$apollo.mutate({
mutation,
@ -98,12 +70,6 @@
});
},
},
apollo: {
me: {
query: ME_QUERY,
},
},
};
</script>

View File

@ -0,0 +1,12 @@
export const CONTENT_TYPE = 'content';
export const OBJECTIVE_TYPE = 'objective';
export const OBJECTIVE_GROUP_TYPE = 'objective-group';
export const CHAPTER_TITLE_TYPE = 'chapter-title';
export const CHAPTER_DESCRIPTION_TYPE = 'chapter-description';
export const TYPES = [
CONTENT_TYPE,
OBJECTIVE_TYPE,
OBJECTIVE_GROUP_TYPE,
CHAPTER_TITLE_TYPE,
CHAPTER_DESCRIPTION_TYPE
];

View File

@ -16,4 +16,20 @@ fragment ChapterParts on ChapterNode {
}
}
}
titleHiddenFor {
edges {
node {
id
name
}
}
}
descriptionHiddenFor {
edges {
node {
id
name
}
}
}
}

View File

@ -2,10 +2,6 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
id
title
displayTitle
mine
owner {
id
}
hiddenFor {
edges {
node {
@ -14,12 +10,4 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
}
}
}
visibleFor {
edges {
node {
id
name
}
}
}
}

View File

@ -19,12 +19,4 @@ fragment ObjectiveParts on ObjectiveNode {
}
}
}
objectiveProgress {
edges {
node {
id
done
}
}
}
}

View File

@ -5,6 +5,5 @@ mutation MutateContentBlock($input: MutateContentBlockInput!) {
contentBlock {
...ContentBlockParts
}
errors
}
}

View File

@ -0,0 +1,9 @@
#import "../fragments/chapterParts.gql"
mutation UpdateChapterVisibility($input: UpdateChapterVisibilityInput!) {
updateChapterVisibility(input: $input) {
chapter {
...ChapterParts
}
}
}

View File

@ -0,0 +1,9 @@
#import "../fragments/objectiveGroupParts.gql"
mutation UpdateObjectiveGroupVisibility($input: UpdateObjectiveGroupVisibilityInput!) {
updateObjectiveGroupVisibility(input: $input) {
objectiveGroup {
...ObjectiveGroupParts
}
}
}

View File

@ -1,17 +1,3 @@
export function setUserBlockType(isAssignment) {
return isAssignment ? 'TASK' : 'NORMAL';
}
export const isHidden = (contentBlock, schoolClass) => {
if (!contentBlock.id || !contentBlock.visibleFor || !contentBlock.hiddenFor) {
return false;
}
if (contentBlock.userCreated) {
if (schoolClass.id === '') {
return false;
}
return !contentBlock.visibleFor.map(entry => entry.id).includes(schoolClass.id);
} else {
return contentBlock.hiddenFor.map(entry => entry.id).includes(schoolClass.id);
}
};

View File

@ -0,0 +1,95 @@
import {
CHAPTER_DESCRIPTION_TYPE,
CHAPTER_TITLE_TYPE,
CONTENT_TYPE,
OBJECTIVE_GROUP_TYPE,
OBJECTIVE_TYPE
} from '@/consts/types';
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
import UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroupVisibility.gql';
import UPDATE_CHAPTER_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateChapterVisibility.gql';
export const createVisibilityMutation = (type, id, visibility) => {
let mutation, variables;
switch (type) {
case CONTENT_TYPE:
mutation = CHANGE_CONTENT_BLOCK_MUTATION;
variables = {
input: {
id,
contentBlock: {
visibility
}
}
};
break;
case OBJECTIVE_TYPE:
mutation = UPDATE_OBJECTIVE_VISIBILITY_MUTATION;
variables = {
input: {
id,
visibility
}
};
break;
case OBJECTIVE_GROUP_TYPE:
mutation = UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION;
variables = {
input: {
id,
visibility
}
};
break;
case CHAPTER_TITLE_TYPE:
case CHAPTER_DESCRIPTION_TYPE:
mutation = UPDATE_CHAPTER_VISIBILITY_MUTATION;
variables = {
input: {
id,
type,
visibility
}
};
break;
}
return {
mutation,
variables
};
};
const containsClass = (arr = [], schoolClass) => arr.map(entry => entry.id).includes(schoolClass.id);
export const hidden = ({
type,
block: {
userCreated,
visibleFor,
hiddenFor,
titleHiddenFor,
descriptionHiddenFor
},
schoolClass
}) => {
switch (type) {
case CONTENT_TYPE:
case OBJECTIVE_TYPE:
// is this content block / objective group user created?
return userCreated
// if so, is visibility not explicitly set for this school class?
? !containsClass(visibleFor, schoolClass)
// otherwise, is it explicitly hidden for this school class?
: containsClass(hiddenFor, schoolClass);
case OBJECTIVE_GROUP_TYPE:
return containsClass(hiddenFor, schoolClass);
case CHAPTER_TITLE_TYPE:
return containsClass(titleHiddenFor, schoolClass);
case CHAPTER_DESCRIPTION_TYPE:
return containsClass(descriptionHiddenFor, schoolClass);
default:
return false;
}
};

View File

@ -0,0 +1,7 @@
import {mapState} from 'vuex';
export default {
computed: {
...mapState(['editModule']),
}
};

View File

@ -15,19 +15,25 @@ export default {
};
},
computed: {
topicRoute() {
if (this.me.lastTopic && this.me.lastTopic.slug) {
return {
name: 'topic',
params: {
topicSlug: this.me.lastTopic.slug
}
};
}
return '/book/topic/berufliche-grundbildung';
computed: {
topicRoute() {
if (this.me.lastTopic && this.me.lastTopic.slug) {
return {
name: 'topic',
params: {
topicSlug: this.me.lastTopic.slug
}
};
}
return '/book/topic/berufliche-grundbildung';
},
schoolClass() {
return this.me.selectedClass;
},
canManageContent() {
return this.me.permissions.includes('users.can_manage_school_class_content');
},
},
apollo: {
me: {

View File

@ -6,7 +6,7 @@
</template>
<script>
import {mapGetters, mapActions} from 'vuex';
import {mapState, mapActions} from 'vuex';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
@ -26,7 +26,7 @@
},
computed: {
...mapGetters({
...mapState({
editModule: 'editModule'
}),
},

View File

@ -1,7 +1,7 @@
.hideable-element {
position: relative;
&--hidden {
&--greyed-out {
&::before {
content: '';
position: absolute;

View File

@ -8,7 +8,7 @@ from api import graphene_wagtail # Keep this import exactly here, it's necessar
from assignments.schema.mutations import AssignmentMutations
from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
from basicknowledge.queries import BasicKnowledgeQuery
from books.schema.mutations.main import BookMutations
from books.schema.mutations import BookMutations
from books.schema.queries import BookQuery
from core.schema.mutations.coupon import CouponMutations
from core.schema.mutations.main import CoreMutations

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.17 on 2021-02-18 13:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0025_auto_20210126_1343'),
('books', '0023_auto_20200707_1501'),
]
operations = [
migrations.AddField(
model_name='chapter',
name='description_hidden_for',
field=models.ManyToManyField(related_name='hidden_chapter_descriptions', to='users.SchoolClass'),
),
migrations.AddField(
model_name='chapter',
name='title_hidden_for',
field=models.ManyToManyField(related_name='hidden_chapter_titles', to='users.SchoolClass'),
),
]

View File

@ -4,6 +4,7 @@ from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
from core.wagtail_utils import StrictHierarchyPage
from users.models import SchoolClass
logger = logging.getLogger(__name__)
@ -34,3 +35,5 @@ class Chapter(StrictHierarchyPage):
parent_page_types = ['books.Module']
subpage_types = ['books.ContentBlock']
title_hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_chapter_titles')
description_hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_chapter_descriptions')

View File

@ -1,9 +1,13 @@
# -*- coding: utf-8 -*-
#
# Iterativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2018 Iterativ GmbH. All rights reserved.
#
# Created on 25.09.18
# @author: Ramon Wenger <ramon.wenger@iterativ.ch>
from books.schema.mutations.chapter import UpdateChapterVisibility
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, UpdateLastTopic
class BookMutations(object):
mutate_content_block = MutateContentBlock.Field()
add_content_block = AddContentBlock.Field()
delete_content_block = DeleteContentBlock.Field()
update_solution_visibility = UpdateSolutionVisibility.Field()
update_last_module = UpdateLastModule.Field()
update_last_topic = UpdateLastTopic.Field()
update_chapter_visibility = UpdateChapterVisibility.Field()

View File

@ -0,0 +1,43 @@
import graphene
from graphene import relay
from api.utils import get_object
from books.models import Chapter
from books.schema.inputs import UserGroupBlockVisibility
from books.schema.queries import ChapterNode
from users.models import SchoolClass
class UpdateChapterVisibility(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True)
visibility = graphene.List(UserGroupBlockVisibility)
type = graphene.String(required=True)
chapter = graphene.Field(ChapterNode)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
id_param = kwargs['id']
visibility_list = kwargs.get('visibility', None)
mutation_type = kwargs['type']
chapter = get_object(Chapter, id_param)
if visibility_list is not None:
for v in visibility_list:
school_class = get_object(SchoolClass, v.school_class_id)
if v.hidden:
if mutation_type == 'chapter-title':
chapter.title_hidden_for.add(school_class)
else:
chapter.description_hidden_for.add(school_class)
else:
if mutation_type == 'chapter-title':
chapter.title_hidden_for.remove(school_class)
else:
chapter.description_hidden_for.remove(school_class)
chapter.save()
return cls(chapter=chapter)

View File

@ -10,7 +10,6 @@ from books.models import ContentBlock, Chapter, SchoolClass
from books.schema.inputs import ContentBlockInput
from books.schema.queries import ContentBlockNode
from core.utils import set_hidden_for, set_visible_for
from notes.models import ContentBlockBookmark
from .utils import handle_content_block, set_user_defined_block_type
@ -19,7 +18,6 @@ class MutateContentBlock(relay.ClientIDMutation):
id = graphene.ID(required=True)
content_block = graphene.Argument(ContentBlockInput)
errors = graphene.List(graphene.String)
content_block = graphene.Field(ContentBlockNode)
@classmethod
@ -52,17 +50,16 @@ class MutateContentBlock(relay.ClientIDMutation):
if contents is not None:
content_block.contents = json.dumps([handle_content_block(c, info.context, module) for c in contents])
content_block.save()
return cls(content_block=content_block)
except ValidationError as e:
errors = get_errors(e)
raise errors
except Exception as e:
errors = ['Error: {}'.format(e)]
return cls(content_block=None, errors=errors)
raise errors
class AddContentBlock(relay.ClientIDMutation):

View File

@ -1,11 +0,0 @@
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, UpdateLastTopic
class BookMutations(object):
mutate_content_block = MutateContentBlock.Field()
add_content_block = AddContentBlock.Field()
delete_content_block = DeleteContentBlock.Field()
update_solution_visibility = UpdateSolutionVisibility.Field()
update_last_module = UpdateLastModule.Field()
update_last_topic = UpdateLastTopic.Field()

View File

@ -77,7 +77,7 @@ class ChapterNode(DjangoObjectType):
class Meta:
model = Chapter
only_fields = [
'slug', 'title', 'description',
'slug', 'title', 'description', 'title_hidden_for', 'description_hidden_for'
]
filter_fields = [
'slug', 'title',
@ -183,9 +183,7 @@ class ModuleNode(DjangoObjectType):
def resolve_objective_groups(self, root, **kwargs):
return self.objective_groups.all() \
.prefetch_related('hidden_for') \
.prefetch_related('visible_for') \
.prefetch_related('objectives__objective_progress')
.prefetch_related('hidden_for')
class RecentModuleNode(DjangoObjectType):

View File

@ -88,7 +88,7 @@ class Command(BaseCommand):
for objective_group_idx, objective_group_entry in enumerate(objective_group_data):
factory_params = self.filter_data(objective_group_entry, 'objectives')
objective_group = ObjectiveGroupFactory.create(module=module,
owner=None,
**factory_params)
default_objectives = [{} for i in range(0, 4)]

View File

@ -1,18 +1,19 @@
from django.contrib import admin
from wagtail.core.models import Page
from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
from objectives.models import ObjectiveGroup, Objective
from books.models import Topic
@admin.register(ObjectiveGroup)
class ObjectiveGroupAdmin(admin.ModelAdmin):
list_display = ('title', 'module', 'owner')
list_filter = ('title', 'module', 'owner')
list_display = ('title', 'module')
list_filter = ('title', 'module')
@admin.register(Objective)
class ObjectiveAdmin(admin.ModelAdmin):
list_display = ('text', 'get_topic', 'group', 'order', 'owner')
list_display = ('text', 'get_topic', 'group', 'order', 'owner')
list_filter = ('group', 'owner')
def get_topic(self, obj):
@ -36,9 +37,3 @@ class ObjectiveAdmin(admin.ModelAdmin):
return topic.title
get_topic.short_description = 'Thema'
@admin.register(ObjectiveProgressStatus)
class ObjectiveProgressStatusAdmin(admin.ModelAdmin):
list_display = ('objective', 'user', 'done')
list_filter = ('objective', 'user', 'done')

View File

@ -15,7 +15,6 @@ class ObjectiveGroupFactory(factory.django.DjangoModelFactory):
title = factory.Iterator([ObjectiveGroup.LANGUAGE_COMMUNICATION, ObjectiveGroup.SOCIETY])
module = factory.SubFactory(ModuleFactory)
owner = factory.Iterator(get_user_model().objects.all())
class ObjectiveFactory(factory.django.DjangoModelFactory):

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.18 on 2021-02-22 15:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('objectives', '0012_auto_20210210_1109'),
]
operations = [
migrations.RemoveField(
model_name='objectivegroup',
name='owner',
),
migrations.RemoveField(
model_name='objectivegroup',
name='visible_for',
),
]

View File

@ -23,11 +23,8 @@ class ObjectiveGroup(models.Model):
title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES, default=LANGUAGE_COMMUNICATION)
module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE, related_name='objective_groups')
# a user can define her own objectives, hence this optional param
owner = models.ForeignKey(get_user_model(), blank=True, null=True, on_delete=models.PROTECT)
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objective_groups', blank=True)
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_objective_groups', blank=True)
def __str__(self):
return '{} - {}'.format(self.module, self.title)
@ -49,18 +46,3 @@ class Objective(models.Model):
def __str__(self):
return 'Objective {}-{}'.format(self.id, self.text)
# todo: delete
class ObjectiveProgressStatus(models.Model):
class Meta:
verbose_name = 'Lernzielstatus'
verbose_name_plural = 'Lernzielstatus'
done = models.BooleanField('Lernziel erledigt?', default=False)
objective = models.ForeignKey(Objective, blank=False, null=False, on_delete=models.CASCADE,
related_name='objective_progress')
user = models.ForeignKey(get_user_model(), blank=True, null=True, on_delete=models.CASCADE)
def __str__(self):
return 'Lernzielstatus {}-{}'.format(self.objective, self.done)

View File

@ -6,34 +6,10 @@ from api.utils import get_object
from books.schema.inputs import UserGroupBlockVisibility
from core.utils import set_visible_for, set_hidden_for
from objectives.inputs import AddObjectiveArgument
from objectives.models import ObjectiveProgressStatus, Objective, ObjectiveGroup
from objectives.models import Objective, ObjectiveGroup
from objectives.schema import ObjectiveNode, ObjectiveGroupNode
class UpdateObjectiveProgress(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True, description="The ID of the objective")
done = graphene.Boolean(required=True)
errors = graphene.List(graphene.String)
objective = graphene.Field(ObjectiveNode)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
objective_id = kwargs.get('id')
done = kwargs.get('done')
objective = get_object(Objective, objective_id) # info.context.user = django user
objective_progress, created = ObjectiveProgressStatus.objects.get_or_create(
objective=objective,
user=info.context.user
)
objective_progress.done = done
objective_progress.save()
return cls(objective=objective)
class UpdateObjectiveVisibility(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True, description='The ID of the objective')
@ -58,6 +34,26 @@ class UpdateObjectiveVisibility(relay.ClientIDMutation):
return cls(objective=objective)
class UpdateObjectiveGroupVisibility(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True, description='The ID of the objective group')
visibility = graphene.List(UserGroupBlockVisibility)
objective_group = graphene.Field(ObjectiveGroupNode)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
objective_group_id = kwargs.get('id')
visibility_list = kwargs.get('visibility')
objective_group = get_object(ObjectiveGroup, objective_group_id) # info.context.user = django user
if visibility_list is not None:
set_hidden_for(objective_group, visibility_list)
objective_group.save()
return cls(objective_group=objective_group)
class AddObjective(relay.ClientIDMutation):
class Input:
objective = graphene.Argument(AddObjectiveArgument)
@ -104,7 +100,7 @@ class DeleteObjective(relay.ClientIDMutation):
class ObjectiveMutations:
update_objective_progress = UpdateObjectiveProgress.Field()
update_objective_visibility = UpdateObjectiveVisibility.Field()
update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field()
add_objective = AddObjective.Field()
delete_objective = DeleteObjective.Field()

View File

@ -4,13 +4,12 @@ from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
from objectives.models import ObjectiveGroup, Objective
class ObjectiveGroupNode(DjangoObjectType):
pk = graphene.Int()
display_title = graphene.String()
mine = graphene.Boolean()
class Meta:
model = ObjectiveGroup
@ -23,9 +22,6 @@ class ObjectiveGroupNode(DjangoObjectType):
def resolve_display_title(self, *args, **kwargs):
return self.get_title_display()
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')
@ -59,17 +55,6 @@ class ObjectiveNode(DjangoObjectType):
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()
class Meta:
model = ObjectiveProgressStatus
filter_fields = ['objective__text', 'user__username', 'done']
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
return self.id
class ObjectivesQuery(object):
objective_group = relay.Node.Field(ObjectiveGroupNode)

View File

@ -21,7 +21,7 @@ class ObjectiveOrderTestCase(TestCase):
self.user = user = User.objects.get(username='teacher')
self.objective_group = ObjectiveGroupFactory(owner=None)
self.objective_group = ObjectiveGroupFactory()
request = RequestFactory().get('/')
@ -29,10 +29,10 @@ class ObjectiveOrderTestCase(TestCase):
self.client = Client(schema=schema, context_value=request)
Objective.objects.create(owner=None, text='first', group=self.objective_group, order=0)
Objective.objects.create(owner=None, text='second', group=self.objective_group, order=1)
Objective.objects.create(owner=None, text='third', group=self.objective_group)
Objective.objects.create(owner=user, text='fourth', group=self.objective_group)
Objective.objects.create(text='first', group=self.objective_group, order=0)
Objective.objects.create(text='second', group=self.objective_group, order=1)
Objective.objects.create(text='third', group=self.objective_group)
Objective.objects.create(text='fourth', group=self.objective_group)
def test_objective_order(self):
query = """