Merged in feature/chapter-visibility (pull request #78)
Feature/chapter visibility Approved-by: Christian Cueni
This commit is contained in:
commit
3660a282a3
2
Pipfile
2
Pipfile
|
|
@ -23,7 +23,7 @@ python-dotenv = "==0.13.0"
|
||||||
dj-database-url = "==0.4.1"
|
dj-database-url = "==0.4.1"
|
||||||
raven = "==6.9.0"
|
raven = "==6.9.0"
|
||||||
django-extensions = "==1.9.8"
|
django-extensions = "==1.9.8"
|
||||||
graphene-django = "==2.2.0"
|
graphene-django = "==2.15.0"
|
||||||
django-filter = "==2.0.0"
|
django-filter = "==2.0.0"
|
||||||
djangorestframework = "==3.8.2"
|
djangorestframework = "==3.8.2"
|
||||||
pillow = "==5.0.0"
|
pillow = "==5.0.0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "a5323ad6907e180d4901fe31c0edc344ee9890f7a95577b2d60e5a1ee19be2f3"
|
"sha256": "58d8faf7e03679ac7b0053dd01e54288d3a719c8ee25c1edf20a74ebcbf87951"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
@ -25,9 +25,10 @@
|
||||||
},
|
},
|
||||||
"autopep8": {
|
"autopep8": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"
|
"sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
|
||||||
|
"sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
|
||||||
],
|
],
|
||||||
"version": "==1.5.4"
|
"version": "==1.5.5"
|
||||||
},
|
},
|
||||||
"backcall": {
|
"backcall": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -54,18 +55,18 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2a39bd5e5f2d50ce9267d682cc92750f8771399665021f47e80f9c8d2fb812a6",
|
"sha256:1709ff5feb363fee7fcaa2330e659fcbc2b4c03a14f75a884ed682ee66011fc4",
|
||||||
"sha256:b4860f56bc585d3d1fde90d288da5eb4d1198401d72201dc3e25de8887b080e2"
|
"sha256:80a761eff3b1cb0798d7e1a41b7c8e6d85c9647a8f7b6105335201a69404caa2"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.17.0"
|
"version": "==1.17.10"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:634b39ab0d55477cfbffb0e5dff31b7ab4bb171b04a0c69f8bcf65135f26ba94",
|
"sha256:8c84eac6daf38890714e005623083106d68e9b2088e62132fdbf7d2b1228ecbd",
|
||||||
"sha256:a608d6d644b852f3c154fc433eaae52febbebc7c474fa8f4d666797d0931770a"
|
"sha256:a601ee5a4ae66832f328ca362b5404d22b75f1c181f6cc0934f3cfca749eb27d"
|
||||||
],
|
],
|
||||||
"version": "==1.20.0"
|
"version": "==1.20.10"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -189,10 +190,10 @@
|
||||||
},
|
},
|
||||||
"django-treebeard": {
|
"django-treebeard": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:214ae3ab331a7de11fb055a2015c201e34f3fa14255b667e1e07752231a7a398",
|
"sha256:313fb61843e1ad025262014c382bc0c58fefc064cf5401421dcb5a2cfdacdc9d",
|
||||||
"sha256:f50e4eea146f7af6702decf7ef198ac1eee1fb9bb4af2c5dba276c3c48f76623"
|
"sha256:8085928debdd187e9919afc522ea40069bb9f090fa804c7ae9a324b0f62843c6"
|
||||||
],
|
],
|
||||||
"version": "==4.4"
|
"version": "==4.5"
|
||||||
},
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -219,10 +220,10 @@
|
||||||
},
|
},
|
||||||
"faker": {
|
"faker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0783729c61501d52efea2967aff6e6fcb8370f0f6b5a558f2a81233642ae529a",
|
"sha256:3971803f32728314c54ba051139cd433fc93fde371e18d07a2cec960a7a2222a",
|
||||||
"sha256:6b2995ffff6c2b02bc5daad96f8c24c021e5bd491d9d53d31bcbd66f348181d4"
|
"sha256:b27f9bc97490a11f14c1501cc25f1109cf68c75f11c6ef97714757a4298c33e5"
|
||||||
],
|
],
|
||||||
"version": "==5.8.0"
|
"version": "==6.3.0"
|
||||||
},
|
},
|
||||||
"future": {
|
"future": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -245,11 +246,11 @@
|
||||||
},
|
},
|
||||||
"graphene-django": {
|
"graphene-django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3afd81d47c8b702650e05cc1179fac1cfceae991d241bb164d51f28bed9ec95c",
|
"sha256:02671d195f0c09c8649acff2a8f4ad4f297d0f7d98ea6e6cdf034b81bab92880",
|
||||||
"sha256:760a18068feb5457e2ec00d2447c09b2fbac2a6b8c32cc8be2abce3752107ad3"
|
"sha256:b78c9b05bc899016b9cc5bf13faa1f37fe1faa8c5407552c6ddd1a28f46fc31a"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.0"
|
"version": "==2.15.0"
|
||||||
},
|
},
|
||||||
"graphql-core": {
|
"graphql-core": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -500,10 +501,10 @@
|
||||||
},
|
},
|
||||||
"prompt-toolkit": {
|
"prompt-toolkit": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
|
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
|
||||||
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
|
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
|
||||||
],
|
],
|
||||||
"version": "==3.0.14"
|
"version": "==3.0.16"
|
||||||
},
|
},
|
||||||
"psycopg2": {
|
"psycopg2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -542,10 +543,10 @@
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
|
"sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0",
|
||||||
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
|
"sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"
|
||||||
],
|
],
|
||||||
"version": "==2.7.4"
|
"version": "==2.8.0"
|
||||||
},
|
},
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -641,10 +642,10 @@
|
||||||
},
|
},
|
||||||
"sendgrid": {
|
"sendgrid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:499a4910623c03e73cb27bd9ef7cadd0968eb2c811afd5c83cfb517f76163f65",
|
"sha256:2eb1dcb1f7d8656eed4db586e428c2c86f347590b8511d7f92993882d0e4fab9",
|
||||||
"sha256:5a87682dba540b706683d4b4a3a605e11fbe24f340ecff5fd502bfb17dfa7ef8"
|
"sha256:e422c8263563ac7d664066d2f87b90bcb005b067eb7c33a9b1396442b2ed285b"
|
||||||
],
|
],
|
||||||
"version": "==6.5.0"
|
"version": "==6.6.0"
|
||||||
},
|
},
|
||||||
"sentry-sdk": {
|
"sentry-sdk": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -711,10 +712,10 @@
|
||||||
},
|
},
|
||||||
"unidecode": {
|
"unidecode": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4c9d15d2f73eb0d2649a151c566901f80a030da1ccb0a2043352e1dbf647586b",
|
"sha256:12435ef2fc4cdfd9cf1035a1db7e98b6b047fe591892e81f34e94959591fad00",
|
||||||
"sha256:a039f89014245e0cad8858976293e23501accc9ff5a7bdbc739a14a2b7b85cdc"
|
"sha256:8d73a97d387a956922344f6b74243c2c6771594659778744b2dbdaad8f6b727d"
|
||||||
],
|
],
|
||||||
"version": "==1.1.2"
|
"version": "==1.2.0"
|
||||||
},
|
},
|
||||||
"unittest-xml-reporting": {
|
"unittest-xml-reporting": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -794,17 +795,18 @@
|
||||||
},
|
},
|
||||||
"autopep8": {
|
"autopep8": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:d21d3901cb0da6ebd1e83fc9b0dfbde8b46afc2ede4fe32fbda0c7c6118ca094"
|
"sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
|
||||||
|
"sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
|
||||||
],
|
],
|
||||||
"version": "==1.5.4"
|
"version": "==1.5.5"
|
||||||
},
|
},
|
||||||
"awscli": {
|
"awscli": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2bdc2ef330f9334dbeefb91c942061feb6a53646bd55c663e0056f8d0dc66fed",
|
"sha256:299161d80c226fea405a69fda44fa90cec3d5cf2e484021e9c323d6454246d20",
|
||||||
"sha256:5a28b63869261c5c2f4ee83f7c43d8ec9622f790d0daf73f91643dcf7148bcf8"
|
"sha256:f04edb9f34308a84541ba125387bb9d7f4ae03c066b03d46af306e995c4c5e42"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.19.0"
|
"version": "==1.19.10"
|
||||||
},
|
},
|
||||||
"backcall": {
|
"backcall": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -815,10 +817,10 @@
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:634b39ab0d55477cfbffb0e5dff31b7ab4bb171b04a0c69f8bcf65135f26ba94",
|
"sha256:8c84eac6daf38890714e005623083106d68e9b2088e62132fdbf7d2b1228ecbd",
|
||||||
"sha256:a608d6d644b852f3c154fc433eaae52febbebc7c474fa8f4d666797d0931770a"
|
"sha256:a601ee5a4ae66832f328ca362b5404d22b75f1c181f6cc0934f3cfca749eb27d"
|
||||||
],
|
],
|
||||||
"version": "==1.20.0"
|
"version": "==1.20.10"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -1064,10 +1066,10 @@
|
||||||
},
|
},
|
||||||
"prompt-toolkit": {
|
"prompt-toolkit": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525",
|
"sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974",
|
||||||
"sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"
|
"sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"
|
||||||
],
|
],
|
||||||
"version": "==3.0.14"
|
"version": "==3.0.16"
|
||||||
},
|
},
|
||||||
"ptyprocess": {
|
"ptyprocess": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -1103,10 +1105,10 @@
|
||||||
},
|
},
|
||||||
"pygments": {
|
"pygments": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435",
|
"sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0",
|
||||||
"sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
|
"sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"
|
||||||
],
|
],
|
||||||
"version": "==2.7.4"
|
"version": "==2.8.0"
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"targets": {
|
"targets": {
|
||||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||||
}
|
}
|
||||||
}],
|
}]
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"transform-vue-jsx",
|
"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
|
|
@ -17,12 +17,12 @@ const topic = {
|
||||||
return {
|
return {
|
||||||
node: module,
|
node: module,
|
||||||
__typename: 'ModuleNodeEdge'
|
__typename: 'ModuleNodeEdge'
|
||||||
}
|
};
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
Cypress.Commands.add('checkHome', (n, skipHome) => {
|
Cypress.Commands.add('checkHome', (n, skipHome) => {
|
||||||
if (!skipHome) {
|
if (!skipHome) {
|
||||||
|
|
@ -71,7 +71,7 @@ describe('Current Module', () => {
|
||||||
'__typename': 'UserNode',
|
'__typename': 'UserNode',
|
||||||
'permissions': []
|
'permissions': []
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
AssignmentsQuery: {
|
AssignmentsQuery: {
|
||||||
assignments
|
assignments
|
||||||
|
|
@ -79,7 +79,7 @@ describe('Current Module', () => {
|
||||||
ModulesQuery: variables => {
|
ModulesQuery: variables => {
|
||||||
return {
|
return {
|
||||||
module: fullModules[variables.slug]
|
module: fullModules[variables.slug]
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
TopicsQuery: topics,
|
TopicsQuery: topics,
|
||||||
Topic: topic,
|
Topic: topic,
|
||||||
|
|
@ -95,7 +95,7 @@ describe('Current Module', () => {
|
||||||
lastModule: moduleTeasers[variables.input.id],
|
lastModule: moduleTeasers[variables.input.id],
|
||||||
__typename: 'UpdateLastModulePayload'
|
__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]').first().should('contain', 'Lerntipps');
|
||||||
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Random');
|
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Random');
|
||||||
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Geld');
|
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Geld');
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,20 @@
|
||||||
<div
|
<div
|
||||||
:data-scrollto="chapter.id"
|
:data-scrollto="chapter.id"
|
||||||
class="chapter">
|
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
|
<bookmark-actions
|
||||||
:bookmarked="chapter.bookmark"
|
:bookmarked="chapter.bookmark"
|
||||||
|
|
@ -12,9 +25,22 @@
|
||||||
@edit-note="editNote"
|
@edit-note="editNote"
|
||||||
@bookmark="bookmark(!chapter.bookmark)"
|
@bookmark="bookmark(!chapter.bookmark)"
|
||||||
/>
|
/>
|
||||||
<p class="chapter__description intro">
|
<div
|
||||||
{{ chapter.description }}
|
:class="{'hideable-element--greyed-out': descriptionGreyedOut}"
|
||||||
</p>
|
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
|
<add-content-button
|
||||||
:parent="chapter"
|
:parent="chapter"
|
||||||
|
|
@ -29,132 +55,154 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ContentBlock from '@/components/ContentBlock';
|
import ContentBlock from '@/components/ContentBlock';
|
||||||
import AddContentButton from '@/components/AddContentButton';
|
import AddContentButton from '@/components/AddContentButton';
|
||||||
import BookmarkActions from '@/components/notes/BookmarkActions';
|
import BookmarkActions from '@/components/notes/BookmarkActions';
|
||||||
|
import VisibilityAction from '@/components/visibility/VisibilityAction';
|
||||||
|
|
||||||
import {mapState} from 'vuex';
|
import {mapState} from 'vuex';
|
||||||
import {isHidden} from '@/helpers/content-block';
|
import {hidden} from '@/helpers/visibility';
|
||||||
import {meQuery} from '@/graphql/queries';
|
import {CONTENT_TYPE, CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE} from '@/consts/types';
|
||||||
|
|
||||||
import UPDATE_CHAPTER_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateChapterBookmark.gql';
|
import UPDATE_CHAPTER_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateChapterBookmark.gql';
|
||||||
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
|
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
|
||||||
|
|
||||||
export default {
|
import me from '@/mixins/me';
|
||||||
props: ['chapter', 'index'],
|
|
||||||
|
|
||||||
components: {
|
export default {
|
||||||
BookmarkActions,
|
props: ['chapter', 'index'],
|
||||||
ContentBlock,
|
|
||||||
AddContentButton
|
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
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
note() {
|
||||||
data() {
|
if (this.chapter && this.chapter.bookmark) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
return this.chapter.bookmark.note;
|
return this.chapter.bookmark.note;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
titleGreyedOut() {
|
||||||
methods: {
|
return this.textHidden(CHAPTER_TITLE_TYPE) && this.editModule;
|
||||||
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);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
// never hidden when editing the module
|
||||||
apollo: {
|
titleHidden() {
|
||||||
me: meQuery
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
.chapter {
|
.chapter {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&__bookmark-actions {
|
&__bookmark-actions {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
|
||||||
|
|
||||||
&__description {
|
|
||||||
@include lead-paragraph;
|
|
||||||
|
|
||||||
margin-bottom: $large-spacing;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__intro {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
@include lead-paragraph;
|
||||||
|
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{'hideable-element--hidden': hidden}"
|
:class="{'hideable-element--greyed-out': hidden}"
|
||||||
class="content-block__container hideable-element">
|
class="content-block__container hideable-element">
|
||||||
<div
|
<div
|
||||||
:class="specialClass"
|
:class="specialClass"
|
||||||
|
|
@ -60,11 +60,11 @@
|
||||||
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
|
import CHAPTER_QUERY from '@/graphql/gql/chapterQuery.gql';
|
||||||
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
|
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';
|
||||||
|
|
||||||
import {meQuery} from '@/graphql/queries';
|
|
||||||
|
|
||||||
import {mapState} from 'vuex';
|
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 = {
|
const instruments = {
|
||||||
base_communication: 'Sprache & Kommunikation',
|
base_communication: 'Sprache & Kommunikation',
|
||||||
|
|
@ -77,6 +77,8 @@
|
||||||
name: 'ContentBlock',
|
name: 'ContentBlock',
|
||||||
props: ['contentBlock', 'parent'],
|
props: ['contentBlock', 'parent'],
|
||||||
|
|
||||||
|
mixins: [me],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ContentComponent,
|
ContentComponent,
|
||||||
AddContentButton,
|
AddContentButton,
|
||||||
|
|
@ -85,13 +87,6 @@
|
||||||
UserWidget
|
UserWidget
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showVisibility: false,
|
|
||||||
me: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['editModule']),
|
...mapState(['editModule']),
|
||||||
canEditModule() {
|
canEditModule() {
|
||||||
|
|
@ -164,11 +159,12 @@
|
||||||
contents: this.removeSingleContentListItem(newContent, startingIndex)
|
contents: this.removeSingleContentListItem(newContent, startingIndex)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
schoolClass() {
|
|
||||||
return this.me.selectedClass;
|
|
||||||
},
|
|
||||||
hidden() {
|
hidden() {
|
||||||
return isHidden(this.contentBlock, this.schoolClass);
|
return hidden({
|
||||||
|
block: this.contentBlock,
|
||||||
|
schoolClass: this.schoolClass,
|
||||||
|
type: CONTENT_TYPE
|
||||||
|
});
|
||||||
},
|
},
|
||||||
root() {
|
root() {
|
||||||
// we need the root content block id, not the generated content block if inside a content list block
|
// 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)];
|
return [...content.slice(0, listIndex), ...content[listIndex].contents[0].value, ...content.slice(listIndex + 1)];
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
apollo: {
|
|
||||||
me: meQuery
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -47,14 +47,11 @@
|
||||||
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
|
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
|
||||||
import Chapter from '@/components/Chapter.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_LAST_MODULE_MUTATION from '@/graphql/gql/mutations/updateLastModule.gql';
|
||||||
import UPDATE_MODULE_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateModuleBookmark.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 ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql';
|
import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql';
|
||||||
|
|
||||||
import {withoutOwnerFirst} from '@/helpers/sorting';
|
|
||||||
import BookmarkActions from '@/components/notes/BookmarkActions';
|
import BookmarkActions from '@/components/notes/BookmarkActions';
|
||||||
import meMixin from '@/mixins/me';
|
import meMixin from '@/mixins/me';
|
||||||
|
|
||||||
|
|
@ -82,18 +79,15 @@
|
||||||
computed: {
|
computed: {
|
||||||
languageCommunicationObjectiveGroups() {
|
languageCommunicationObjectiveGroups() {
|
||||||
return this.module.objectiveGroups ? this.module.objectiveGroups
|
return this.module.objectiveGroups ? this.module.objectiveGroups
|
||||||
.filter(group => group.title === 'LANGUAGE_COMMUNICATION')
|
.filter(group => group.title === 'LANGUAGE_COMMUNICATION') : [];
|
||||||
.sort(withoutOwnerFirst) : [];
|
|
||||||
},
|
},
|
||||||
societyObjectiveGroups() {
|
societyObjectiveGroups() {
|
||||||
return this.module.objectiveGroups ? this.module.objectiveGroups
|
return this.module.objectiveGroups ? this.module.objectiveGroups
|
||||||
.filter(group => group.title === 'SOCIETY')
|
.filter(group => group.title === 'SOCIETY') : [];
|
||||||
.sort(withoutOwnerFirst) : [];
|
|
||||||
},
|
},
|
||||||
interdisciplinaryObjectiveGroups() {
|
interdisciplinaryObjectiveGroups() {
|
||||||
return this.module.objectiveGroups ? this.module.objectiveGroups
|
return this.module.objectiveGroups ? this.module.objectiveGroups
|
||||||
.filter(group => group.title === 'INTERDISCIPLINARY')
|
.filter(group => group.title === 'INTERDISCIPLINARY') : [];
|
||||||
.sort(withoutOwnerFirst) : [];
|
|
||||||
},
|
},
|
||||||
isStudent() {
|
isStudent() {
|
||||||
return !this.me.permissions.includes('users.can_manage_school_class_content');
|
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) {
|
bookmark(bookmarked) {
|
||||||
const slug = this.module.slug;
|
const slug = this.module.slug;
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
|
|
|
||||||
|
|
@ -59,21 +59,20 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {moduleQuery} from '@/graphql/queries';
|
import {moduleQuery} from '@/graphql/queries';
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
|
||||||
|
|
||||||
import SubNavigationItem from '@/components/book-navigation/SubNavigationItem';
|
import SubNavigationItem from '@/components/book-navigation/SubNavigationItem';
|
||||||
import ToggleSolutionsForModule from '@/components/toggle-menu/ToggleSolutionsForModule';
|
import ToggleSolutionsForModule from '@/components/toggle-menu/ToggleSolutionsForModule';
|
||||||
import ToggleEditing from '@/components/toggle-menu/ToggleEditing';
|
import ToggleEditing from '@/components/toggle-menu/ToggleEditing';
|
||||||
import ChevronLeft from '@/components/icons/ChevronLeft';
|
import ChevronLeft from '@/components/icons/ChevronLeft';
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
apollo: {
|
apollo: {
|
||||||
module: moduleQuery,
|
module: moduleQuery,
|
||||||
me: {
|
|
||||||
query: ME_QUERY
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [me],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
SubNavigationItem,
|
SubNavigationItem,
|
||||||
ToggleSolutionsForModule,
|
ToggleSolutionsForModule,
|
||||||
|
|
@ -87,9 +86,6 @@
|
||||||
assignments: [],
|
assignments: [],
|
||||||
topic: {}
|
topic: {}
|
||||||
},
|
},
|
||||||
me: {
|
|
||||||
permissions: []
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -103,9 +99,6 @@
|
||||||
showResults() {
|
showResults() {
|
||||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
return this.me.permissions.includes('users.can_manage_school_class_content');
|
||||||
},
|
},
|
||||||
canManageContent() {
|
|
||||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
|
||||||
},
|
|
||||||
assignments() {
|
assignments() {
|
||||||
if (!this.module.chapters) {
|
if (!this.module.chapters) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li
|
||||||
:class="{'hideable-element--hidden': hidden}"
|
:class="{'hideable-element--greyed-out': hidden}"
|
||||||
class="objective hideable-element"
|
class="objective hideable-element"
|
||||||
v-if="editModule || !hidden">
|
v-if="editModule || !hidden">
|
||||||
<visibility-action
|
<visibility-action
|
||||||
:block="objective"
|
:block="objective"
|
||||||
|
:type="type"
|
||||||
v-if="editModule"/>
|
v-if="editModule"/>
|
||||||
<div
|
<div
|
||||||
class="block-actions"
|
class="block-actions"
|
||||||
|
|
@ -28,15 +29,19 @@
|
||||||
import UserWidget from '@/components/UserWidget';
|
import UserWidget from '@/components/UserWidget';
|
||||||
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
|
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 DELETE_OBJECTIVE_MUTATION from '@/graphql/gql/mutations/deleteObjective.gql';
|
||||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.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 {
|
export default {
|
||||||
props: ['objective', 'schoolClass'],
|
props: ['objective', 'schoolClass'],
|
||||||
|
|
||||||
|
mixins: [me, editModule],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
MoreOptionsWidget,
|
MoreOptionsWidget,
|
||||||
VisibilityAction,
|
VisibilityAction,
|
||||||
|
|
@ -45,14 +50,17 @@
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
me: {}
|
type: OBJECTIVE_TYPE
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['editModule']),
|
|
||||||
hidden() {
|
hidden() {
|
||||||
return isHidden(this.objective, this.schoolClass);
|
return hidden({
|
||||||
|
block: this.objective,
|
||||||
|
schoolClass: this.schoolClass,
|
||||||
|
type: this.type
|
||||||
|
});
|
||||||
},
|
},
|
||||||
canEdit() {
|
canEdit() {
|
||||||
return this.objective.mine;
|
return this.objective.mine;
|
||||||
|
|
@ -89,17 +97,14 @@
|
||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
apollo: {
|
|
||||||
me: meQuery
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.objective {
|
.objective {
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
|
|
||||||
&__user-widget {
|
&__user-widget {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
<template>
|
<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>
|
<h4>{{ group.displayTitle }}</h4>
|
||||||
|
|
||||||
|
|
@ -7,7 +14,7 @@
|
||||||
<objective
|
<objective
|
||||||
:key="objective.id"
|
:key="objective.id"
|
||||||
:objective="objective"
|
:objective="objective"
|
||||||
:school-class="currentFilter"
|
:school-class="schoolClass"
|
||||||
class="objective-group__objective"
|
class="objective-group__objective"
|
||||||
v-for="objective in group.objectives"/>
|
v-for="objective in group.objectives"/>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -20,11 +27,14 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Objective from '@/components/objective-groups/Objective';
|
import Objective from '@/components/objective-groups/Objective';
|
||||||
|
import VisibilityAction from '@/components/visibility/VisibilityAction';
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
|
||||||
import AddContentButton from '@/components/AddContentButton';
|
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 {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -34,24 +44,27 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [me, editModule],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
AddContentButton,
|
AddContentButton,
|
||||||
Objective
|
Objective,
|
||||||
|
VisibilityAction
|
||||||
},
|
},
|
||||||
|
|
||||||
apollo: {
|
data() {
|
||||||
me: {
|
return {
|
||||||
query: ME_QUERY,
|
type: OBJECTIVE_GROUP_TYPE
|
||||||
},
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['editModule']),
|
hidden() {
|
||||||
canManageContent() {
|
return hidden({
|
||||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
block: this.group,
|
||||||
},
|
schoolClass: this.schoolClass,
|
||||||
currentFilter() {
|
type: this.type
|
||||||
return this.me.selectedClass;
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
<script>
|
<script>
|
||||||
import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup';
|
import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup';
|
||||||
import {meQuery} from '@/graphql/queries';
|
import {meQuery} from '@/graphql/queries';
|
||||||
import {withoutOwnerFirst} from '@/helpers/sorting';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -47,19 +46,13 @@
|
||||||
return this.groups;
|
return this.groups;
|
||||||
} else {
|
} else {
|
||||||
// todo: maybe this can be done a bit more elegantly
|
// 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 objectives = groups.map(g => g.objectives).flat(); // get all objectives in one array
|
||||||
const firstGroup = Object.assign({}, groups.shift(), {objectives});
|
const firstGroup = Object.assign({}, groups.shift(), {objectives});
|
||||||
|
|
||||||
return [firstGroup];
|
return [firstGroup];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
updateObjectiveProgress(checked, id) {
|
|
||||||
this.$emit('updateObjectiveProgress', checked, id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Checkbox from '@/components/Checkbox';
|
import Checkbox from '@/components/Checkbox';
|
||||||
import {mapGetters, mapActions} from 'vuex';
|
import {mapState, mapActions} from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapState({
|
||||||
checked: 'editModule',
|
checked: 'editModule',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,79 +18,51 @@
|
||||||
import EyeIcon from '@/components/icons/EyeIcon';
|
import EyeIcon from '@/components/icons/EyeIcon';
|
||||||
import ClosedEyeIcon from '@/components/icons/ClosedEyeIcon';
|
import ClosedEyeIcon from '@/components/icons/ClosedEyeIcon';
|
||||||
|
|
||||||
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
import me from '@/mixins/me';
|
||||||
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
|
|
||||||
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
|
import {TYPES, CONTENT_TYPE} from '@/consts/types';
|
||||||
|
import {createVisibilityMutation, hidden} from '@/helpers/visibility';
|
||||||
|
|
||||||
export default {
|
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: {
|
components: {
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
ClosedEyeIcon
|
ClosedEyeIcon
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showVisibility: false,
|
|
||||||
me: {
|
|
||||||
permissions: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
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() {
|
hidden() {
|
||||||
// is this content block / objective group user created?
|
return hidden({type: this.type, block: this.block, schoolClass: this.schoolClass});
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggleVisibility() {
|
toggleVisibility() {
|
||||||
let hidden = !this.hidden;
|
const hidden = !this.hidden;
|
||||||
let schoolClassId = this.schoolClass.id;
|
const schoolClassId = this.schoolClass.id;
|
||||||
|
|
||||||
const visibility = [{
|
const visibility = [{
|
||||||
schoolClassId,
|
schoolClassId,
|
||||||
hidden
|
hidden
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let mutation, variables;
|
const {mutation, variables} = createVisibilityMutation(this.type, this.block.id, visibility);
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
mutation,
|
mutation,
|
||||||
|
|
@ -98,12 +70,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
apollo: {
|
|
||||||
me: {
|
|
||||||
query: ME_QUERY,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
];
|
||||||
|
|
@ -16,4 +16,20 @@ fragment ChapterParts on ChapterNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
titleHiddenFor {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptionHiddenFor {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
displayTitle
|
displayTitle
|
||||||
mine
|
|
||||||
owner {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
hiddenFor {
|
hiddenFor {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|
@ -14,12 +10,4 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visibleFor {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,4 @@ fragment ObjectiveParts on ObjectiveNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
objectiveProgress {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
done
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,5 @@ mutation MutateContentBlock($input: MutateContentBlockInput!) {
|
||||||
contentBlock {
|
contentBlock {
|
||||||
...ContentBlockParts
|
...ContentBlockParts
|
||||||
}
|
}
|
||||||
errors
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
#import "../fragments/chapterParts.gql"
|
||||||
|
mutation UpdateChapterVisibility($input: UpdateChapterVisibilityInput!) {
|
||||||
|
updateChapterVisibility(input: $input) {
|
||||||
|
chapter {
|
||||||
|
...ChapterParts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
#import "../fragments/objectiveGroupParts.gql"
|
||||||
|
mutation UpdateObjectiveGroupVisibility($input: UpdateObjectiveGroupVisibilityInput!) {
|
||||||
|
updateObjectiveGroupVisibility(input: $input) {
|
||||||
|
objectiveGroup {
|
||||||
|
...ObjectiveGroupParts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,17 +1,3 @@
|
||||||
export function setUserBlockType(isAssignment) {
|
export function setUserBlockType(isAssignment) {
|
||||||
return isAssignment ? 'TASK' : 'NORMAL';
|
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import {mapState} from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState(['editModule']),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -15,19 +15,25 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
topicRoute() {
|
topicRoute() {
|
||||||
if (this.me.lastTopic && this.me.lastTopic.slug) {
|
if (this.me.lastTopic && this.me.lastTopic.slug) {
|
||||||
return {
|
return {
|
||||||
name: 'topic',
|
name: 'topic',
|
||||||
params: {
|
params: {
|
||||||
topicSlug: this.me.lastTopic.slug
|
topicSlug: this.me.lastTopic.slug
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
return '/book/topic/berufliche-grundbildung';
|
|
||||||
}
|
}
|
||||||
|
return '/book/topic/berufliche-grundbildung';
|
||||||
},
|
},
|
||||||
|
schoolClass() {
|
||||||
|
return this.me.selectedClass;
|
||||||
|
},
|
||||||
|
canManageContent() {
|
||||||
|
return this.me.permissions.includes('users.can_manage_school_class_content');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
apollo: {
|
apollo: {
|
||||||
me: {
|
me: {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters, mapActions} from 'vuex';
|
import {mapState, mapActions} from 'vuex';
|
||||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
|
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
|
||||||
|
|
||||||
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
|
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapState({
|
||||||
editModule: 'editModule'
|
editModule: 'editModule'
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
.hideable-element {
|
.hideable-element {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&--hidden {
|
&--greyed-out {
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
|
|
@ -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.mutations import AssignmentMutations
|
||||||
from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
|
from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
|
||||||
from basicknowledge.queries import BasicKnowledgeQuery
|
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 books.schema.queries import BookQuery
|
||||||
from core.schema.mutations.coupon import CouponMutations
|
from core.schema.mutations.coupon import CouponMutations
|
||||||
from core.schema.mutations.main import CoreMutations
|
from core.schema.mutations.main import CoreMutations
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -4,6 +4,7 @@ from django.db import models
|
||||||
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
|
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
|
||||||
|
|
||||||
from core.wagtail_utils import StrictHierarchyPage
|
from core.wagtail_utils import StrictHierarchyPage
|
||||||
|
from users.models import SchoolClass
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -34,3 +35,5 @@ class Chapter(StrictHierarchyPage):
|
||||||
parent_page_types = ['books.Module']
|
parent_page_types = ['books.Module']
|
||||||
subpage_types = ['books.ContentBlock']
|
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')
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
from books.schema.mutations.chapter import UpdateChapterVisibility
|
||||||
#
|
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
|
||||||
# Iterativ GmbH
|
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, UpdateLastTopic
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2018 Iterativ GmbH. All rights reserved.
|
class BookMutations(object):
|
||||||
#
|
mutate_content_block = MutateContentBlock.Field()
|
||||||
# Created on 25.09.18
|
add_content_block = AddContentBlock.Field()
|
||||||
# @author: Ramon Wenger <ramon.wenger@iterativ.ch>
|
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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -10,7 +10,6 @@ from books.models import ContentBlock, Chapter, SchoolClass
|
||||||
from books.schema.inputs import ContentBlockInput
|
from books.schema.inputs import ContentBlockInput
|
||||||
from books.schema.queries import ContentBlockNode
|
from books.schema.queries import ContentBlockNode
|
||||||
from core.utils import set_hidden_for, set_visible_for
|
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
|
from .utils import handle_content_block, set_user_defined_block_type
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,7 +18,6 @@ class MutateContentBlock(relay.ClientIDMutation):
|
||||||
id = graphene.ID(required=True)
|
id = graphene.ID(required=True)
|
||||||
content_block = graphene.Argument(ContentBlockInput)
|
content_block = graphene.Argument(ContentBlockInput)
|
||||||
|
|
||||||
errors = graphene.List(graphene.String)
|
|
||||||
content_block = graphene.Field(ContentBlockNode)
|
content_block = graphene.Field(ContentBlockNode)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -52,17 +50,16 @@ class MutateContentBlock(relay.ClientIDMutation):
|
||||||
if contents is not None:
|
if contents is not None:
|
||||||
content_block.contents = json.dumps([handle_content_block(c, info.context, module) for c in contents])
|
content_block.contents = json.dumps([handle_content_block(c, info.context, module) for c in contents])
|
||||||
|
|
||||||
|
|
||||||
content_block.save()
|
content_block.save()
|
||||||
|
|
||||||
return cls(content_block=content_block)
|
return cls(content_block=content_block)
|
||||||
|
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
errors = get_errors(e)
|
errors = get_errors(e)
|
||||||
|
raise errors
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors = ['Error: {}'.format(e)]
|
errors = ['Error: {}'.format(e)]
|
||||||
|
raise errors
|
||||||
return cls(content_block=None, errors=errors)
|
|
||||||
|
|
||||||
|
|
||||||
class AddContentBlock(relay.ClientIDMutation):
|
class AddContentBlock(relay.ClientIDMutation):
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -77,7 +77,7 @@ class ChapterNode(DjangoObjectType):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Chapter
|
model = Chapter
|
||||||
only_fields = [
|
only_fields = [
|
||||||
'slug', 'title', 'description',
|
'slug', 'title', 'description', 'title_hidden_for', 'description_hidden_for'
|
||||||
]
|
]
|
||||||
filter_fields = [
|
filter_fields = [
|
||||||
'slug', 'title',
|
'slug', 'title',
|
||||||
|
|
@ -183,9 +183,7 @@ class ModuleNode(DjangoObjectType):
|
||||||
|
|
||||||
def resolve_objective_groups(self, root, **kwargs):
|
def resolve_objective_groups(self, root, **kwargs):
|
||||||
return self.objective_groups.all() \
|
return self.objective_groups.all() \
|
||||||
.prefetch_related('hidden_for') \
|
.prefetch_related('hidden_for')
|
||||||
.prefetch_related('visible_for') \
|
|
||||||
.prefetch_related('objectives__objective_progress')
|
|
||||||
|
|
||||||
|
|
||||||
class RecentModuleNode(DjangoObjectType):
|
class RecentModuleNode(DjangoObjectType):
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ class Command(BaseCommand):
|
||||||
for objective_group_idx, objective_group_entry in enumerate(objective_group_data):
|
for objective_group_idx, objective_group_entry in enumerate(objective_group_data):
|
||||||
factory_params = self.filter_data(objective_group_entry, 'objectives')
|
factory_params = self.filter_data(objective_group_entry, 'objectives')
|
||||||
objective_group = ObjectiveGroupFactory.create(module=module,
|
objective_group = ObjectiveGroupFactory.create(module=module,
|
||||||
owner=None,
|
|
||||||
**factory_params)
|
**factory_params)
|
||||||
|
|
||||||
default_objectives = [{} for i in range(0, 4)]
|
default_objectives = [{} for i in range(0, 4)]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from wagtail.core.models import Page
|
from wagtail.core.models import Page
|
||||||
|
|
||||||
from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
|
from objectives.models import ObjectiveGroup, Objective
|
||||||
from books.models import Topic
|
from books.models import Topic
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ObjectiveGroup)
|
@admin.register(ObjectiveGroup)
|
||||||
class ObjectiveGroupAdmin(admin.ModelAdmin):
|
class ObjectiveGroupAdmin(admin.ModelAdmin):
|
||||||
list_display = ('title', 'module', 'owner')
|
list_display = ('title', 'module')
|
||||||
list_filter = ('title', 'module', 'owner')
|
list_filter = ('title', 'module')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Objective)
|
@admin.register(Objective)
|
||||||
class ObjectiveAdmin(admin.ModelAdmin):
|
class ObjectiveAdmin(admin.ModelAdmin):
|
||||||
list_display = ('text', 'get_topic', 'group', 'order', 'owner')
|
list_display = ('text', 'get_topic', 'group', 'order', 'owner')
|
||||||
list_filter = ('group', 'owner')
|
list_filter = ('group', 'owner')
|
||||||
|
|
||||||
def get_topic(self, obj):
|
def get_topic(self, obj):
|
||||||
|
|
@ -36,9 +37,3 @@ class ObjectiveAdmin(admin.ModelAdmin):
|
||||||
return topic.title
|
return topic.title
|
||||||
|
|
||||||
get_topic.short_description = 'Thema'
|
get_topic.short_description = 'Thema'
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ObjectiveProgressStatus)
|
|
||||||
class ObjectiveProgressStatusAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('objective', 'user', 'done')
|
|
||||||
list_filter = ('objective', 'user', 'done')
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ class ObjectiveGroupFactory(factory.django.DjangoModelFactory):
|
||||||
title = factory.Iterator([ObjectiveGroup.LANGUAGE_COMMUNICATION, ObjectiveGroup.SOCIETY])
|
title = factory.Iterator([ObjectiveGroup.LANGUAGE_COMMUNICATION, ObjectiveGroup.SOCIETY])
|
||||||
|
|
||||||
module = factory.SubFactory(ModuleFactory)
|
module = factory.SubFactory(ModuleFactory)
|
||||||
owner = factory.Iterator(get_user_model().objects.all())
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectiveFactory(factory.django.DjangoModelFactory):
|
class ObjectiveFactory(factory.django.DjangoModelFactory):
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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)
|
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')
|
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)
|
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):
|
def __str__(self):
|
||||||
return '{} - {}'.format(self.module, self.title)
|
return '{} - {}'.format(self.module, self.title)
|
||||||
|
|
@ -49,18 +46,3 @@ class Objective(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Objective {}-{}'.format(self.id, self.text)
|
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)
|
|
||||||
|
|
|
||||||
|
|
@ -6,34 +6,10 @@ from api.utils import get_object
|
||||||
from books.schema.inputs import UserGroupBlockVisibility
|
from books.schema.inputs import UserGroupBlockVisibility
|
||||||
from core.utils import set_visible_for, set_hidden_for
|
from core.utils import set_visible_for, set_hidden_for
|
||||||
from objectives.inputs import AddObjectiveArgument
|
from objectives.inputs import AddObjectiveArgument
|
||||||
from objectives.models import ObjectiveProgressStatus, Objective, ObjectiveGroup
|
from objectives.models import Objective, ObjectiveGroup
|
||||||
from objectives.schema import ObjectiveNode, ObjectiveGroupNode
|
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 UpdateObjectiveVisibility(relay.ClientIDMutation):
|
||||||
class Input:
|
class Input:
|
||||||
id = graphene.ID(required=True, description='The ID of the objective')
|
id = graphene.ID(required=True, description='The ID of the objective')
|
||||||
|
|
@ -58,6 +34,26 @@ class UpdateObjectiveVisibility(relay.ClientIDMutation):
|
||||||
return cls(objective=objective)
|
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 AddObjective(relay.ClientIDMutation):
|
||||||
class Input:
|
class Input:
|
||||||
objective = graphene.Argument(AddObjectiveArgument)
|
objective = graphene.Argument(AddObjectiveArgument)
|
||||||
|
|
@ -104,7 +100,7 @@ class DeleteObjective(relay.ClientIDMutation):
|
||||||
|
|
||||||
|
|
||||||
class ObjectiveMutations:
|
class ObjectiveMutations:
|
||||||
update_objective_progress = UpdateObjectiveProgress.Field()
|
|
||||||
update_objective_visibility = UpdateObjectiveVisibility.Field()
|
update_objective_visibility = UpdateObjectiveVisibility.Field()
|
||||||
|
update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field()
|
||||||
add_objective = AddObjective.Field()
|
add_objective = AddObjective.Field()
|
||||||
delete_objective = DeleteObjective.Field()
|
delete_objective = DeleteObjective.Field()
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@ from graphene import relay
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
||||||
from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
|
from objectives.models import ObjectiveGroup, Objective
|
||||||
|
|
||||||
|
|
||||||
class ObjectiveGroupNode(DjangoObjectType):
|
class ObjectiveGroupNode(DjangoObjectType):
|
||||||
pk = graphene.Int()
|
pk = graphene.Int()
|
||||||
display_title = graphene.String()
|
display_title = graphene.String()
|
||||||
mine = graphene.Boolean()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ObjectiveGroup
|
model = ObjectiveGroup
|
||||||
|
|
@ -23,9 +22,6 @@ class ObjectiveGroupNode(DjangoObjectType):
|
||||||
def resolve_display_title(self, *args, **kwargs):
|
def resolve_display_title(self, *args, **kwargs):
|
||||||
return self.get_title_display()
|
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):
|
def resolve_objectives(self, info, **kwargs):
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
school_classes = user.school_classes.values_list('pk')
|
school_classes = user.school_classes.values_list('pk')
|
||||||
|
|
@ -59,17 +55,6 @@ class ObjectiveNode(DjangoObjectType):
|
||||||
def resolve_mine(self, info, **kwargs):
|
def resolve_mine(self, info, **kwargs):
|
||||||
return self.owner is not None and self.owner.pk == info.context.user.pk
|
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):
|
class ObjectivesQuery(object):
|
||||||
objective_group = relay.Node.Field(ObjectiveGroupNode)
|
objective_group = relay.Node.Field(ObjectiveGroupNode)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class ObjectiveOrderTestCase(TestCase):
|
||||||
|
|
||||||
self.user = user = User.objects.get(username='teacher')
|
self.user = user = User.objects.get(username='teacher')
|
||||||
|
|
||||||
self.objective_group = ObjectiveGroupFactory(owner=None)
|
self.objective_group = ObjectiveGroupFactory()
|
||||||
|
|
||||||
|
|
||||||
request = RequestFactory().get('/')
|
request = RequestFactory().get('/')
|
||||||
|
|
@ -29,10 +29,10 @@ class ObjectiveOrderTestCase(TestCase):
|
||||||
|
|
||||||
self.client = Client(schema=schema, context_value=request)
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
Objective.objects.create(owner=None, text='first', group=self.objective_group, order=0)
|
Objective.objects.create(text='first', group=self.objective_group, order=0)
|
||||||
Objective.objects.create(owner=None, text='second', group=self.objective_group, order=1)
|
Objective.objects.create(text='second', group=self.objective_group, order=1)
|
||||||
Objective.objects.create(owner=None, text='third', group=self.objective_group)
|
Objective.objects.create(text='third', group=self.objective_group)
|
||||||
Objective.objects.create(owner=user, text='fourth', group=self.objective_group)
|
Objective.objects.create(text='fourth', group=self.objective_group)
|
||||||
|
|
||||||
def test_objective_order(self):
|
def test_objective_order(self):
|
||||||
query = """
|
query = """
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue