From a1490c60494aeb452f209e23670f727e34da022a Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Mon, 1 Mar 2021 23:25:38 +0100 Subject: [PATCH 01/19] Add basic test for new visibility feature --- server/books/schema/queries.py | 2 +- .../test_copy_visibility_for_other_class.py | 83 +++++++++++++++++++ server/core/settings.py | 5 ++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 server/books/tests/test_copy_visibility_for_other_class.py diff --git a/server/books/schema/queries.py b/server/books/schema/queries.py index 338aae4b..bcf41e0e 100644 --- a/server/books/schema/queries.py +++ b/server/books/schema/queries.py @@ -199,7 +199,7 @@ class TopicNode(DjangoObjectType): class Meta: model = Topic only_fields = [ - 'slug', 'title', 'meta_title', 'teaser', 'description', 'vimeo_id', 'order', 'instructions' + 'slug', 'title', 'teaser', 'description', 'vimeo_id', 'order', 'instructions' ] filter_fields = { 'slug': ['exact', 'icontains', 'in'], diff --git a/server/books/tests/test_copy_visibility_for_other_class.py b/server/books/tests/test_copy_visibility_for_other_class.py new file mode 100644 index 00000000..39c3dcfd --- /dev/null +++ b/server/books/tests/test_copy_visibility_for_other_class.py @@ -0,0 +1,83 @@ +import logging + +from django.test import TestCase, RequestFactory +from graphene.test import Client +from graphql_relay import to_global_id + +from api.schema import schema +from books.models import ContentBlock, Chapter +from books.factories import ModuleFactory +from users.factories import SchoolClassFactory +from users.models import User +from users.services import create_users + +logger = logging.getLogger(__name__) + +class CopyVisibilityForClassesTestCase(TestCase): + def setUp(self): + module = ModuleFactory() + chapter = Chapter(title='Some Chapter') + module.add_child(instance=chapter) + create_users() + teacher = User.objects.get(username='teacher') + student1 = User.objects.get(username='student1') + student2 = User.objects.get(username='student2') + school_class1 = SchoolClassFactory(name='hidden-class', users=[teacher, student1]) + school_class2 = SchoolClassFactory(name='default-class', users=[teacher, student2]) + + default_content_block = ContentBlock(title='default block', slug='default') + hidden_content_block = ContentBlock(title='hidden block', slug='hidden') + + chapter.specific.add_child(instance=default_content_block) + chapter.specific.add_child(instance=hidden_content_block) + + hidden_content_block.hidden_for.add(school_class1) + + teacher_request = RequestFactory().get('/') + teacher_request.user = teacher + self.teacher_client = Client(schema=schema, context_value=teacher_request) + + student1_request = RequestFactory().get('/') + student1_request.user = student1 + self.student1_client = Client(schema=schema, context_value=student1_request) + + student2_request = RequestFactory().get('/') + student2_request.user = student2 + self.student2_client = Client(schema=schema, context_value=student2_request) + + self.chapter = to_global_id('ChapterNode', chapter.pk) + self.default_content_block = to_global_id('ContentBlockNode', default_content_block.pk) + self.hidden_content_block = to_global_id('ContentBlockNode', hidden_content_block.pk) + + def _get_result(self, query, client, id): + result = client.execute(query, variables={ + 'id': id + }) + return result + + def test_hidden_for_set_correctly(self): + self.assertEqual(ContentBlock.objects.count(), 2) + + query = """ + query ContentBlockQuery($id: ID!) { + contentBlock(id: $id) { + hiddenFor { + edges { + node { + id + name + } + } + } + } + } + """ + + result = self._get_result(query, self.teacher_client, self.hidden_content_block) + logger.info(result) + hiddenFor = result.get('data').get('contentBlock').get('hiddenFor').get('edges') + logger.info(hiddenFor) + self.assertTrue('hidden-class' in map(lambda x: x['node']['name'], hiddenFor)) + self.assertFalse('default-class' in map(lambda x: x['node']['name'], hiddenFor)) + + diff --git a/server/core/settings.py b/server/core/settings.py index 74892f06..f74aacd3 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -335,6 +335,11 @@ LOGGING = { 'level': 'WARNING', 'propagate': True, }, + 'wagtail': { + 'handlers': ['console'], + 'level': 'WARNING', + 'propagate': False, + }, } } From b3268a62999c5519fca9db3ca2e29f8c39b6ceb1 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 3 Mar 2021 16:36:44 +0100 Subject: [PATCH 02/19] Add settings pages --- client/src/pages/moduleSettings.vue | 35 +++++++++++++ client/src/pages/moduleVisibility.vue | 73 +++++++++++++++++++++++++++ client/src/router/index.js | 13 +++++ client/src/styles/_mixins.scss | 29 +++++++++++ 4 files changed, 150 insertions(+) create mode 100644 client/src/pages/moduleSettings.vue create mode 100644 client/src/pages/moduleVisibility.vue diff --git a/client/src/pages/moduleSettings.vue b/client/src/pages/moduleSettings.vue new file mode 100644 index 00000000..8ed8a4dd --- /dev/null +++ b/client/src/pages/moduleSettings.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/client/src/pages/moduleVisibility.vue b/client/src/pages/moduleVisibility.vue new file mode 100644 index 00000000..d7ac79eb --- /dev/null +++ b/client/src/pages/moduleVisibility.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/client/src/router/index.js b/client/src/router/index.js index 759df143..70a678c0 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -40,9 +40,11 @@ import onboardingStart from '@/pages/onboarding/start'; import onboardingStep1 from '@/pages/onboarding/step1'; import onboardingStep2 from '@/pages/onboarding/step2'; import onboardingStep3 from '@/pages/onboarding/step3'; +import settingsPage from '@/pages/moduleSettings'; import portfolioRoutes from './portfolio.routes'; import store from '@/store/index'; +import moduleVisibility from '@/pages/moduleVisibility'; const ONBOARDING_STEP_1 = 'onboarding-step-1'; const ONBOARDING_STEP_2 = 'onboarding-step-2'; @@ -240,6 +242,17 @@ const routes = [ }, ] }, + { + path: '/settings', + component: settingsPage + }, + { + path: '/visibility', + component: moduleVisibility, + meta: { + layout: 'simple' + } + }, {path: '/styleguide', component: styleGuidePage}, { path: '*', diff --git a/client/src/styles/_mixins.scss b/client/src/styles/_mixins.scss index dd5aea82..bf627f81 100644 --- a/client/src/styles/_mixins.scss +++ b/client/src/styles/_mixins.scss @@ -211,3 +211,32 @@ line-height: $default-heading-line-height; margin-top: -0.2rem; // to offset the 1.2 line height, it leaves a padding on top } + +@mixin settings-page { + padding: $section-spacing; + + @include desktop { + width: 800px; + display: flex; + flex-direction: column; + justify-self: center; + } + + &__page-title { + @include main-title; + margin-bottom: $section-spacing; + } + + &__heading { + @include heading-1; + margin-bottom: $medium-spacing; + } + + &__paragraph { + @include lead-paragraph; + } + + &__section { + margin-bottom: $section-spacing; + } +} From 14022fa9eb90ce47ac7deaf2dee65d23db71ac7c Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 3 Mar 2021 17:17:06 +0100 Subject: [PATCH 03/19] Remove unused code --- client/src/pages/moduleVisibility.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/src/pages/moduleVisibility.vue b/client/src/pages/moduleVisibility.vue index d7ac79eb..ab61135b 100644 --- a/client/src/pages/moduleVisibility.vue +++ b/client/src/pages/moduleVisibility.vue @@ -28,19 +28,12 @@ From 209838dadb6518281172032838a3ca5ace1e3554 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Fri, 5 Mar 2021 14:27:36 +0100 Subject: [PATCH 04/19] Add method for syncing school classes --- server/books/models/module.py | 30 +++- .../test_copy_visibility_for_other_class.py | 141 ++++++++++++++---- 2 files changed, 143 insertions(+), 28 deletions(-) diff --git a/server/books/models/module.py b/server/books/models/module.py index 159d923b..2785d3a1 100644 --- a/server/books/models/module.py +++ b/server/books/models/module.py @@ -10,8 +10,6 @@ from books.blocks import DEFAULT_RICH_TEXT_FEATURES from core.wagtail_utils import StrictHierarchyPage from users.models import SchoolClass -logger = logging.getLogger(__name__) - class Module(StrictHierarchyPage): class Meta: @@ -60,6 +58,34 @@ class Module(StrictHierarchyPage): def get_child_ids(self): return self.get_children().values_list('id', flat=True) + def sync_from_school_class(self, school_class_pattern, school_class_to_sync): + # import here so we don't get a circular import error + from books.models import Chapter, ContentBlock + + # get chapters of module + chapters = Chapter.get_by_parent(self) + content_block_query = ContentBlock.objects.none() + + # get content blocks of chapters + for chapter in chapters: + content_block_query = content_block_query.union(ContentBlock.get_by_parent(chapter)) + + # clear all `hidden for` and `visible for` for class `school_class_to_sync` + for content_block in school_class_to_sync.hidden_content_blocks.intersection(content_block_query): + content_block.hidden_for.remove(school_class_to_sync) + for content_block in school_class_to_sync.visible_content_blocks.intersection(content_block_query): + content_block.visible_for.remove(school_class_to_sync) + + # get all content blocks with `hidden for` for class `school_class_pattern` + for content_block in school_class_pattern.hidden_content_blocks.intersection(content_block_query): + # add `school_class_to_sync` to these blocks' `hidden for` + content_block.hidden_for.add(school_class_to_sync) + + # get all content blocks with `visible for` for class `school_class_pattern` + for content_block in school_class_pattern.visible_content_blocks.intersection(content_block_query): + # add `school_class_to_sync` to these blocks' `visible for` + content_block.visible_for.add(school_class_to_sync) + class RecentModule(models.Model): module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules') diff --git a/server/books/tests/test_copy_visibility_for_other_class.py b/server/books/tests/test_copy_visibility_for_other_class.py index 39c3dcfd..60cff107 100644 --- a/server/books/tests/test_copy_visibility_for_other_class.py +++ b/server/books/tests/test_copy_visibility_for_other_class.py @@ -11,9 +11,46 @@ from users.factories import SchoolClassFactory from users.models import User from users.services import create_users -logger = logging.getLogger(__name__) +CONTENT_BLOCK_QUERY = """ + query ContentBlockQuery($id: ID!) { + contentBlock(id: $id) { + hiddenFor { + edges { + node { + id + name + } + } + } + visibleFor { + edges { + node { + id + name + } + } + } + } + } + """ + class CopyVisibilityForClassesTestCase(TestCase): + """ + what do we want to happen? + we have 3 public content blocks [X, Y, Z] + we have 2 custom content block [M, N] + we have 2 school classes [A, B] + one public content block is hidden for class A | [X, Y] + one custom content block is visible for class A | [M] + class B also sees two of three public content blocks, but one is different from what A sees | [X, Z] + class B doesn't see the custom content block, but another one | [N] + so A sees | [X, Y, M] + B sees | [X, Z, N] + we want to copy the settings from class A to class B + now class B sees the same content blocks as class A | [X, Y, N] + """ + def setUp(self): module = ModuleFactory() chapter = Chapter(title='Some Chapter') @@ -22,16 +59,28 @@ class CopyVisibilityForClassesTestCase(TestCase): teacher = User.objects.get(username='teacher') student1 = User.objects.get(username='student1') student2 = User.objects.get(username='student2') - school_class1 = SchoolClassFactory(name='hidden-class', users=[teacher, student1]) - school_class2 = SchoolClassFactory(name='default-class', users=[teacher, student2]) + # school class to be used as the pattern or model + template_school_class = SchoolClassFactory(name='template-class', users=[teacher, student1]) + # school class to be synced, e.g. adapted to be like the other + school_class_to_be_synced = SchoolClassFactory(name='class-to-be-synced', users=[teacher, student2]) default_content_block = ContentBlock(title='default block', slug='default') hidden_content_block = ContentBlock(title='hidden block', slug='hidden') + other_hidden_content_block = ContentBlock(title='other hidden block', slug='other-hidden') + custom_content_block = ContentBlock(title='custom block', slug='custom', owner=teacher) + other_custom_content_block = ContentBlock(title='other custom block', slug='other-custom', owner=teacher) chapter.specific.add_child(instance=default_content_block) chapter.specific.add_child(instance=hidden_content_block) + chapter.specific.add_child(instance=custom_content_block) + chapter.specific.add_child(instance=other_custom_content_block) + chapter.specific.add_child(instance=other_hidden_content_block) - hidden_content_block.hidden_for.add(school_class1) + hidden_content_block.hidden_for.add(template_school_class) + custom_content_block.visible_for.add(template_school_class) + + other_hidden_content_block.hidden_for.add(school_class_to_be_synced) + other_custom_content_block.visible_for.add(school_class_to_be_synced) teacher_request = RequestFactory().get('/') teacher_request.user = teacher @@ -45,9 +94,15 @@ class CopyVisibilityForClassesTestCase(TestCase): student2_request.user = student2 self.student2_client = Client(schema=schema, context_value=student2_request) + self.template_school_class = template_school_class + self.school_class_to_be_synced = school_class_to_be_synced + self.module = module self.chapter = to_global_id('ChapterNode', chapter.pk) self.default_content_block = to_global_id('ContentBlockNode', default_content_block.pk) self.hidden_content_block = to_global_id('ContentBlockNode', hidden_content_block.pk) + self.custom_content_block = to_global_id('ContentBlockNode', custom_content_block.pk) + self.other_custom_content_block = to_global_id('ContentBlockNode', other_custom_content_block.pk) + self.other_hidden_content_block = to_global_id('ContentBlockNode', other_hidden_content_block.pk) def _get_result(self, query, client, id): result = client.execute(query, variables={ @@ -55,29 +110,63 @@ class CopyVisibilityForClassesTestCase(TestCase): }) return result - def test_hidden_for_set_correctly(self): - self.assertEqual(ContentBlock.objects.count(), 2) + def test_hidden_for_and_visible_for_set_correctly(self): + self.assertEqual(ContentBlock.objects.count(), 5) - query = """ - query ContentBlockQuery($id: ID!) { - contentBlock(id: $id) { - hiddenFor { - edges { - node { - id - name - } - } - } - } - } - """ + hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block) + hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for)) + self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for)) - result = self._get_result(query, self.teacher_client, self.hidden_content_block) - logger.info(result) - hiddenFor = result.get('data').get('contentBlock').get('hiddenFor').get('edges') - logger.info(hiddenFor) - self.assertTrue('hidden-class' in map(lambda x: x['node']['name'], hiddenFor)) - self.assertFalse('default-class' in map(lambda x: x['node']['name'], hiddenFor)) + other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, + self.other_hidden_content_block) + hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertFalse('template-class' in map(lambda x: x['node']['name'], hidden_for)) + self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for)) + default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block) + hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertEqual(len(hidden_for), 0) + custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block) + visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') + self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for)) + self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for)) + + other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, + self.other_custom_content_block) + visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') + self.assertFalse('template-class' in map(lambda x: x['node']['name'], visible_for)) + self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for)) + + def test_syncs_correctly(self): + self.module.sync_from_school_class(self.template_school_class, self.school_class_to_be_synced) + + # the hidden block is hidden for both now + hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block) + hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for)) + self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for)) + + # the other hidden block is hidden for no one now + other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, + self.other_hidden_content_block) + hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertEqual(len(hidden_for), 0) + + # the default block is still hidden for no one + default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block) + hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertEqual(len(hidden_for), 0) + + # the custom block is visible for both + custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block) + visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') + self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for)) + self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for)) + + # the other custom block is visible for no one + other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, + self.other_custom_content_block) + visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') + self.assertEqual(len(visible_for), 0) From 9490ffd443c3098a4a15528f2e9a32d9ce769ca9 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Fri, 5 Mar 2021 17:21:53 +0100 Subject: [PATCH 05/19] Add module visibility sync mutation --- .../sync-module-visibility.spec.js | 5 + .../components/school-class/CurrentClass.vue | 9 -- .../gql/mutations/syncModuleVisibility.gql | 5 + client/src/mixins/me.js | 22 +++-- client/src/pages/module-base.vue | 8 +- client/src/pages/moduleVisibility.vue | 58 +++++++++-- client/src/router/index.js | 97 +++++++------------ client/src/router/module.names.js | 3 + client/src/router/module.routes.js | 35 +++++++ server/api/schema.py | 3 +- server/books/models/module.py | 6 +- server/books/schema/mutations/__init__.py | 4 +- server/books/schema/mutations/module.py | 36 ++++--- server/books/schema/mutations/topic.py | 28 ++++++ .../test_copy_visibility_for_other_class.py | 77 +++++++++------ server/graphql.config.json | 25 ----- 16 files changed, 262 insertions(+), 159 deletions(-) create mode 100644 client/cypress/integration/sync-module-visibility.spec.js create mode 100644 client/src/graphql/gql/mutations/syncModuleVisibility.gql create mode 100644 client/src/router/module.names.js create mode 100644 client/src/router/module.routes.js create mode 100644 server/books/schema/mutations/topic.py delete mode 100644 server/graphql.config.json diff --git a/client/cypress/integration/sync-module-visibility.spec.js b/client/cypress/integration/sync-module-visibility.spec.js new file mode 100644 index 00000000..6f778a03 --- /dev/null +++ b/client/cypress/integration/sync-module-visibility.spec.js @@ -0,0 +1,5 @@ +describe('Survey', () => { + it('needs to be implemented', () => { + expect(true).to.equal(false); + }); +}); diff --git a/client/src/components/school-class/CurrentClass.vue b/client/src/components/school-class/CurrentClass.vue index 3b8109cd..a385418b 100644 --- a/client/src/components/school-class/CurrentClass.vue +++ b/client/src/components/school-class/CurrentClass.vue @@ -9,15 +9,6 @@ export default { mixins: [me], - - computed: { - currentClassName() { - let currentClass = this.me.schoolClasses.find(schoolClass => { - return schoolClass.id === this.me.selectedClass.id; - }); - return currentClass ? currentClass.name : this.me.schoolClasses.length ? this.me.schoolClasses[0].name : ''; - } - } }; diff --git a/client/src/graphql/gql/mutations/syncModuleVisibility.gql b/client/src/graphql/gql/mutations/syncModuleVisibility.gql new file mode 100644 index 00000000..00cd6040 --- /dev/null +++ b/client/src/graphql/gql/mutations/syncModuleVisibility.gql @@ -0,0 +1,5 @@ +mutation SyncModuleVisibility($input: SyncModuleVisibilityInput!) { + syncModuleVisibility(input: $input) { + success + } +} diff --git a/client/src/mixins/me.js b/client/src/mixins/me.js index aaa043d3..e2e592c7 100644 --- a/client/src/mixins/me.js +++ b/client/src/mixins/me.js @@ -5,13 +5,13 @@ export default { return { me: { selectedClass: { - id: '' + id: '', }, permissions: [], schoolClasses: [], - isTeacher: false + isTeacher: false, }, - showPopover: false + showPopover: false, }; }, @@ -21,8 +21,8 @@ export default { return { name: 'topic', params: { - topicSlug: this.me.lastTopic.slug - } + topicSlug: this.me.lastTopic.slug, + }, }; } return '/book/topic/berufliche-grundbildung'; @@ -33,15 +33,21 @@ export default { canManageContent() { return this.me.permissions.includes('users.can_manage_school_class_content'); }, + currentClassName() { + let currentClass = this.me.schoolClasses.find(schoolClass => { + return schoolClass.id === this.me.selectedClass.id; + }); + return currentClass ? currentClass.name : this.me.schoolClasses.length ? this.me.schoolClasses[0].name : ''; + }, }, apollo: { me: { query: ME_QUERY, - update(data) { - return this.$getRidOfEdges(data).me; + update({me}) { + return this.$getRidOfEdges(me); }, - fetchPolicy: 'cache-first' + fetchPolicy: 'cache-first', }, }, }; diff --git a/client/src/pages/module-base.vue b/client/src/pages/module-base.vue index f2b39298..b6c529c6 100644 --- a/client/src/pages/module-base.vue +++ b/client/src/pages/module-base.vue @@ -1,6 +1,6 @@ @@ -11,6 +11,12 @@ export default { components: { ModuleNavigation + }, + + computed: { + showNavigation() { + return !this.$route.meta.hideNavigation; + } } }; diff --git a/client/src/pages/moduleVisibility.vue b/client/src/pages/moduleVisibility.vue index ab61135b..b0474497 100644 --- a/client/src/pages/moduleVisibility.vue +++ b/client/src/pages/moduleVisibility.vue @@ -12,16 +12,25 @@
Von - für INF2019i übernehmen. + für {{ currentClassName }} übernehmen.
@@ -29,11 +38,48 @@ diff --git a/client/src/router/index.js b/client/src/router/index.js index 70a678c0..57e36b6d 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -1,8 +1,5 @@ import Vue from 'vue'; -// import index from '@/pages/index' import topic from '@/pages/topic'; -import moduleBase from '@/pages/module-base'; -import module from '@/pages/module'; import rooms from '@/pages/rooms'; import room from '@/pages/room'; import newRoom from '@/pages/newRoom'; @@ -10,7 +7,6 @@ import editRoom from '@/pages/editRoom'; import article from '@/pages/article'; import instrument from '@/pages/instrument'; import instrumentOverview from '@/pages/instrumentOverview'; -import submissions from '@/pages/submissions'; import p404 from '@/pages/p404'; import start from '@/pages/start'; import submission from '@/pages/studentSubmission'; @@ -41,10 +37,11 @@ import onboardingStep1 from '@/pages/onboarding/step1'; import onboardingStep2 from '@/pages/onboarding/step2'; import onboardingStep3 from '@/pages/onboarding/step3'; import settingsPage from '@/pages/moduleSettings'; + +import moduleRoutes from './module.routes'; import portfolioRoutes from './portfolio.routes'; import store from '@/store/index'; -import moduleVisibility from '@/pages/moduleVisibility'; const ONBOARDING_STEP_1 = 'onboarding-step-1'; const ONBOARDING_STEP_2 = 'onboarding-step-2'; @@ -54,7 +51,7 @@ const routes = [ { path: '/', name: 'home', - component: start + component: start, }, { path: '/login', @@ -62,8 +59,8 @@ const routes = [ component: login, meta: { layout: 'public', - public: true - } + public: true, + }, }, { path: '/hello', @@ -71,8 +68,8 @@ const routes = [ component: hello, meta: { layout: 'public', - public: true - } + public: true, + }, }, { path: '/beta-login', @@ -80,27 +77,10 @@ const routes = [ component: betaLogin, meta: { layout: 'public', - public: true - } - }, - { - path: '/module/:slug', - component: moduleBase, - children: [ - { - path: '', - name: 'module', - component: module, - meta: {filter: true} - }, - { - path: 'submissions/:id', - name: 'submissions', - component: submissions, - meta: {filter: true} - } - ] + public: true, + }, }, + ...moduleRoutes, {path: '/rooms', name: 'rooms', component: rooms, meta: {filter: true}}, {path: '/new-room/', name: 'new-room', component: newRoom}, {path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true}, @@ -110,13 +90,13 @@ const routes = [ name: 'moduleRoom', component: moduleRoom, props: true, - meta: {layout: 'fullScreen'} + meta: {layout: 'fullScreen'}, }, {path: '/article/:slug', name: 'article', component: article, meta: {layout: 'simple'}}, { path: '/instruments/', name: 'instrument-overview', - component: instrumentOverview + component: instrumentOverview, }, {path: '/instrument/:slug', name: 'instrument', component: instrument, meta: {layout: 'simple'}}, {path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}}, @@ -134,11 +114,11 @@ const routes = [ path: 'old-classes', name: 'old-classes', component: oldClasses, - meta: {isProfile: true} + meta: {isProfile: true}, }, {path: 'create-class', name: 'create-class', component: createClass, meta: {layout: 'simple'}}, {path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}}, - ] + ], }, {path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'public'}}, { @@ -146,7 +126,7 @@ const routes = [ component: surveyPage, name: 'survey', props: true, - meta: {layout: 'simple'} + meta: {layout: 'simple'}, }, { path: '/register', @@ -155,7 +135,7 @@ const routes = [ meta: { public: true, layout: 'public', - } + }, }, { path: '/check-email', @@ -163,8 +143,8 @@ const routes = [ name: 'checkEmail', meta: { public: true, - layout: 'public' - } + layout: 'public', + }, }, { path: '/verify-email', @@ -172,16 +152,16 @@ const routes = [ name: 'emailVerification', meta: { public: true, - layout: 'public' - } + layout: 'public', + }, }, { path: '/license-activation', component: licenseActivation, name: 'licenseActivation', meta: { - layout: 'public' - } + layout: 'public', + }, }, { path: '/forgot-password', @@ -189,13 +169,13 @@ const routes = [ name: 'forgotPassword', meta: { layout: 'public', - public: true - } + public: true, + }, }, { path: '/news', component: news, - name: 'news' + name: 'news', }, { path: '/onboarding', @@ -207,7 +187,7 @@ const routes = [ name: 'onboarding-start', meta: { layout: 'blank', - next: ONBOARDING_STEP_1 + next: ONBOARDING_STEP_1, }, }, { @@ -217,7 +197,7 @@ const routes = [ meta: { layout: 'blank', next: ONBOARDING_STEP_2, - illustration: 'contents' + illustration: 'contents', }, }, { @@ -227,7 +207,7 @@ const routes = [ meta: { layout: 'blank', next: ONBOARDING_STEP_3, - illustration: 'rooms' + illustration: 'rooms', }, }, { @@ -237,30 +217,23 @@ const routes = [ meta: { layout: 'blank', next: 'home', - illustration: 'portfolio' + illustration: 'portfolio', }, }, - ] + ], }, { path: '/settings', - component: settingsPage - }, - { - path: '/visibility', - component: moduleVisibility, - meta: { - layout: 'simple' - } + component: settingsPage, }, {path: '/styleguide', component: styleGuidePage}, { path: '*', component: p404, meta: { - layout: 'blank' - } - } + layout: 'blank', + }, + }, ]; Vue.use(Router); @@ -273,7 +246,7 @@ const router = new Router({ return savedPosition; } return {x: 0, y: 0}; - } + }, }); router.afterEach((to, from) => { diff --git a/client/src/router/module.names.js b/client/src/router/module.names.js new file mode 100644 index 00000000..ec35f516 --- /dev/null +++ b/client/src/router/module.names.js @@ -0,0 +1,3 @@ +export const SUBMISSIONS_PAGE = 'submissions'; +export const MODULE_PAGE = 'module'; +export const VISIBILITY_PAGE = 'visibility'; diff --git a/client/src/router/module.routes.js b/client/src/router/module.routes.js new file mode 100644 index 00000000..4eef3e0b --- /dev/null +++ b/client/src/router/module.routes.js @@ -0,0 +1,35 @@ +import moduleBase from '@/pages/module-base'; +import module from '@/pages/module'; +import submissions from '@/pages/submissions'; +import moduleVisibility from '@/pages/moduleVisibility'; +import {MODULE_PAGE, SUBMISSIONS_PAGE, VISIBILITY_PAGE} from '@/router/module.names'; + +export default [ + { + path: '/module/:slug', + component: moduleBase, + children: [ + { + path: '', + name: MODULE_PAGE, + component: module, + meta: {filter: true}, + }, + { + path: 'submissions/:id', + name: SUBMISSIONS_PAGE, + component: submissions, + meta: {filter: true}, + }, + { + path: 'visibility', + name: VISIBILITY_PAGE, + component: moduleVisibility, + meta: { + layout: 'simple', + hideNavigation: true + }, + }, + ], +} +]; diff --git a/server/api/schema.py b/server/api/schema.py index f5e87a87..7421800f 100644 --- a/server/api/schema.py +++ b/server/api/schema.py @@ -24,7 +24,6 @@ from rooms.mutations import RoomMutations from rooms.schema import RoomsQuery, ModuleRoomsQuery from users.schema import AllUsersQuery, UsersQuery from users.mutations import ProfileMutations -from registration.mutations_public import RegistrationMutations class CustomQuery(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, @@ -36,7 +35,7 @@ class CustomQuery(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, Objec class CustomMutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations, - ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, SpellCheckMutations, + ProfileMutations, SurveyMutations, NoteMutations, SpellCheckMutations, CouponMutations, graphene.ObjectType): if settings.DEBUG: debug = graphene.Field(DjangoDebug, name='_debug') diff --git a/server/books/models/module.py b/server/books/models/module.py index 2785d3a1..093a082b 100644 --- a/server/books/models/module.py +++ b/server/books/models/module.py @@ -58,7 +58,7 @@ class Module(StrictHierarchyPage): def get_child_ids(self): return self.get_children().values_list('id', flat=True) - def sync_from_school_class(self, school_class_pattern, school_class_to_sync): + def sync_from_school_class(self, school_class_template, school_class_to_sync): # import here so we don't get a circular import error from books.models import Chapter, ContentBlock @@ -77,12 +77,12 @@ class Module(StrictHierarchyPage): content_block.visible_for.remove(school_class_to_sync) # get all content blocks with `hidden for` for class `school_class_pattern` - for content_block in school_class_pattern.hidden_content_blocks.intersection(content_block_query): + for content_block in school_class_template.hidden_content_blocks.intersection(content_block_query): # add `school_class_to_sync` to these blocks' `hidden for` content_block.hidden_for.add(school_class_to_sync) # get all content blocks with `visible for` for class `school_class_pattern` - for content_block in school_class_pattern.visible_content_blocks.intersection(content_block_query): + for content_block in school_class_template.visible_content_blocks.intersection(content_block_query): # add `school_class_to_sync` to these blocks' `visible for` content_block.visible_for.add(school_class_to_sync) diff --git a/server/books/schema/mutations/__init__.py b/server/books/schema/mutations/__init__.py index fc85ed51..999faf9b 100644 --- a/server/books/schema/mutations/__init__.py +++ b/server/books/schema/mutations/__init__.py @@ -1,6 +1,7 @@ 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 +from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility +from books.schema.mutations.topic import UpdateLastTopic class BookMutations(object): @@ -11,3 +12,4 @@ class BookMutations(object): update_last_module = UpdateLastModule.Field() update_last_topic = UpdateLastTopic.Field() update_chapter_visibility = UpdateChapterVisibility.Field() + sync_module_visibility = SyncModuleVisibility.Field() diff --git a/server/books/schema/mutations/module.py b/server/books/schema/mutations/module.py index 77b00244..d50e8b20 100644 --- a/server/books/schema/mutations/module.py +++ b/server/books/schema/mutations/module.py @@ -3,9 +3,10 @@ from datetime import datetime import graphene from graphene import relay -from api.utils import get_errors, get_object -from books.models import Module, Topic, RecentModule -from books.schema.queries import ModuleNode, TopicNode +from api.utils import get_object +from books.models import Module, RecentModule +from books.schema.queries import ModuleNode +from users.models import SchoolClass class UpdateSolutionVisibility(relay.ClientIDMutation): @@ -75,23 +76,30 @@ class UpdateLastModule(relay.ClientIDMutation): return cls(last_module=last_module.module) -class UpdateLastTopic(relay.ClientIDMutation): +class SyncModuleVisibility(relay.ClientIDMutation): class Input: - # todo: use slug here too - id = graphene.ID() + module = graphene.String(required=True) + template_school_class = graphene.ID(required=True) + school_class = graphene.ID(required=True) - topic = graphene.Field(TopicNode) + success = graphene.Boolean() @classmethod def mutate_and_get_payload(cls, root, info, **args): user = info.context.user - id = args.get('id') + if not user.is_teacher(): + raise Exception('Permission denied') - topic = get_object(Topic, id) - if not topic: - raise Topic.DoesNotExist + module_slug = args.get('module') + template_id = args.get('template_school_class') + school_class_id = args.get('school_class') - user.last_topic = topic - user.save() + module = Module.objects.get(slug=module_slug) + template = get_object(SchoolClass, template_id) + school_class = get_object(SchoolClass, school_class_id) + if not template.is_user_in_schoolclass(user) or not school_class.is_user_in_schoolclass(user): + raise Exception('Permission denied') - return cls(topic=topic) + module.sync_from_school_class(template, school_class) + + return cls(success=True) diff --git a/server/books/schema/mutations/topic.py b/server/books/schema/mutations/topic.py new file mode 100644 index 00000000..fafadae1 --- /dev/null +++ b/server/books/schema/mutations/topic.py @@ -0,0 +1,28 @@ +import graphene +from graphene import relay + +from api.utils import get_object +from books.models import Topic +from books.schema.queries import TopicNode + + +class UpdateLastTopic(relay.ClientIDMutation): + class Input: + # todo: use slug here too + id = graphene.ID() + + topic = graphene.Field(TopicNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **args): + user = info.context.user + id = args.get('id') + + topic = get_object(Topic, id) + if not topic: + raise Topic.DoesNotExist + + user.last_topic = topic + user.save() + + return cls(topic=topic) diff --git a/server/books/tests/test_copy_visibility_for_other_class.py b/server/books/tests/test_copy_visibility_for_other_class.py index 60cff107..6fd27a22 100644 --- a/server/books/tests/test_copy_visibility_for_other_class.py +++ b/server/books/tests/test_copy_visibility_for_other_class.py @@ -34,6 +34,14 @@ CONTENT_BLOCK_QUERY = """ } """ +SYNC_MUTATION = """ +mutation SyncMutationVisibility($input: SyncModuleVisibilityInput!) { + syncModuleVisibility(input: $input) { + success + } +} +""" + class CopyVisibilityForClassesTestCase(TestCase): """ @@ -52,7 +60,7 @@ class CopyVisibilityForClassesTestCase(TestCase): """ def setUp(self): - module = ModuleFactory() + module = ModuleFactory(slug='some-module') chapter = Chapter(title='Some Chapter') module.add_child(instance=chapter) create_users() @@ -110,6 +118,36 @@ class CopyVisibilityForClassesTestCase(TestCase): }) return result + def _test_in_sync(self): + # the hidden block is hidden for both now + hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block) + hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for)) + self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for)) + + # the other hidden block is hidden for no one now + other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, + self.other_hidden_content_block) + hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertEqual(len(hidden_for), 0) + + # the default block is still hidden for no one + default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block) + hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges') + self.assertEqual(len(hidden_for), 0) + + # the custom block is visible for both + custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block) + visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') + self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for)) + self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for)) + + # the other custom block is visible for no one + other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, + self.other_custom_content_block) + visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') + self.assertEqual(len(visible_for), 0) + def test_hidden_for_and_visible_for_set_correctly(self): self.assertEqual(ContentBlock.objects.count(), 5) @@ -142,31 +180,14 @@ class CopyVisibilityForClassesTestCase(TestCase): def test_syncs_correctly(self): self.module.sync_from_school_class(self.template_school_class, self.school_class_to_be_synced) - # the hidden block is hidden for both now - hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block) - hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') - self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for)) - self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for)) + self._test_in_sync() - # the other hidden block is hidden for no one now - other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, - self.other_hidden_content_block) - hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges') - self.assertEqual(len(hidden_for), 0) - - # the default block is still hidden for no one - default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block) - hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges') - self.assertEqual(len(hidden_for), 0) - - # the custom block is visible for both - custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block) - visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') - self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for)) - self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for)) - - # the other custom block is visible for no one - other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, - self.other_custom_content_block) - visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges') - self.assertEqual(len(visible_for), 0) + def test_mutation(self): + self.teacher_client.execute(SYNC_MUTATION, variables={ + 'input': { + 'module': self.module.slug, + 'templateSchoolClass': to_global_id('SchoolClassNode', self.template_school_class.pk), + 'schoolClass': to_global_id('SchoolClassNode', self.school_class_to_be_synced.pk) + } + }) + self._test_in_sync() diff --git a/server/graphql.config.json b/server/graphql.config.json deleted file mode 100644 index 8208188c..00000000 --- a/server/graphql.config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "schema": { - "request": { - "url": "http://localhost:8000/graphql", - "method": "POST", - "postIntrospectionQuery": true, - "options": { - "headers": { - "user-agent": "JS GraphQL" - } - } - } - }, - "endpoints": [ - { - "name": "Default (http://localhost:8000/graphql", - "url": "http://localhost:8000/graphql", - "options": { - "headers": { - "user-agent": "JS GraphQL" - } - } - } - ] -} \ No newline at end of file From 50dbc4e4fcd928f369b25c533eeb2adaeeac80d6 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 9 Mar 2021 15:36:51 +0100 Subject: [PATCH 06/19] Update readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 174efafa..0f7a0002 100644 --- a/README.md +++ b/README.md @@ -221,3 +221,12 @@ To generate a new schema, use the management command ``` python manage.py export_schema_for_cypress ``` + + +## GraphQL + +### Generate GraphQL SDL Document + +``` +python manage.py export_schema_graphql +``` From 2e424198f07b9e170c5a59684023ca99709a0362 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 10 Mar 2021 09:19:13 +0100 Subject: [PATCH 07/19] Add settings button --- .../integration/{ => rooms}/room-page.spec.js | 0 .../{ => rooms}/rooms-page.spec.js | 0 .../{ => users}/beta-login.spec.js | 0 .../integration/{ => users}/coupon.spec.js | 2 +- .../integration/{ => users}/login.spec.js | 2 +- .../components/modules/ModuleNavigation.vue | 8 +++ client/src/pages/{ => module}/module-base.vue | 0 client/src/pages/{ => module}/module.vue | 0 client/src/pages/{ => module}/moduleRoom.vue | 0 .../src/pages/{ => module}/moduleSettings.vue | 0 .../pages/{ => module}/moduleVisibility.vue | 0 client/src/router/index.js | 7 +- client/src/router/module.names.js | 1 + client/src/router/module.routes.js | 64 ++++++++++--------- 14 files changed, 47 insertions(+), 37 deletions(-) rename client/cypress/integration/{ => rooms}/room-page.spec.js (100%) rename client/cypress/integration/{ => rooms}/rooms-page.spec.js (100%) rename client/cypress/integration/{ => users}/beta-login.spec.js (100%) rename client/cypress/integration/{ => users}/coupon.spec.js (97%) rename client/cypress/integration/{ => users}/login.spec.js (97%) rename client/src/pages/{ => module}/module-base.vue (100%) rename client/src/pages/{ => module}/module.vue (100%) rename client/src/pages/{ => module}/moduleRoom.vue (100%) rename client/src/pages/{ => module}/moduleSettings.vue (100%) rename client/src/pages/{ => module}/moduleVisibility.vue (100%) diff --git a/client/cypress/integration/room-page.spec.js b/client/cypress/integration/rooms/room-page.spec.js similarity index 100% rename from client/cypress/integration/room-page.spec.js rename to client/cypress/integration/rooms/room-page.spec.js diff --git a/client/cypress/integration/rooms-page.spec.js b/client/cypress/integration/rooms/rooms-page.spec.js similarity index 100% rename from client/cypress/integration/rooms-page.spec.js rename to client/cypress/integration/rooms/rooms-page.spec.js diff --git a/client/cypress/integration/beta-login.spec.js b/client/cypress/integration/users/beta-login.spec.js similarity index 100% rename from client/cypress/integration/beta-login.spec.js rename to client/cypress/integration/users/beta-login.spec.js diff --git a/client/cypress/integration/coupon.spec.js b/client/cypress/integration/users/coupon.spec.js similarity index 97% rename from client/cypress/integration/coupon.spec.js rename to client/cypress/integration/users/coupon.spec.js index 595f503b..0c80bd5d 100644 --- a/client/cypress/integration/coupon.spec.js +++ b/client/cypress/integration/users/coupon.spec.js @@ -1,6 +1,6 @@ import { GraphQLError } from 'graphql'; -const schema = require('../fixtures/schema.json'); +const schema = require('../../fixtures/schema.json'); describe('Email Verifcation', () => { beforeEach(() => { diff --git a/client/cypress/integration/login.spec.js b/client/cypress/integration/users/login.spec.js similarity index 97% rename from client/cypress/integration/login.spec.js rename to client/cypress/integration/users/login.spec.js index 3cb5d56b..f1defc6c 100644 --- a/client/cypress/integration/login.spec.js +++ b/client/cypress/integration/users/login.spec.js @@ -1,4 +1,4 @@ -const schema = require('../fixtures/schema_public.json'); +const schema = require('../../fixtures/schema_public.json'); const isEmailAvailableUrl = '**/rest/deutsch/V1/customers/isEmailAvailable'; const checkPasswordUrl = '**/rest/deutsch/V1/integration/customer/token'; diff --git a/client/src/components/modules/ModuleNavigation.vue b/client/src/components/modules/ModuleNavigation.vue index e033a2e8..aedce220 100644 --- a/client/src/components/modules/ModuleNavigation.vue +++ b/client/src/components/modules/ModuleNavigation.vue @@ -45,6 +45,10 @@ class="module-navigation__toggle-menu" v-if="canManageContent" > + Einstellungen diff --git a/client/src/pages/module-base.vue b/client/src/pages/module/module-base.vue similarity index 100% rename from client/src/pages/module-base.vue rename to client/src/pages/module/module-base.vue diff --git a/client/src/pages/module.vue b/client/src/pages/module/module.vue similarity index 100% rename from client/src/pages/module.vue rename to client/src/pages/module/module.vue diff --git a/client/src/pages/moduleRoom.vue b/client/src/pages/module/moduleRoom.vue similarity index 100% rename from client/src/pages/moduleRoom.vue rename to client/src/pages/module/moduleRoom.vue diff --git a/client/src/pages/moduleSettings.vue b/client/src/pages/module/moduleSettings.vue similarity index 100% rename from client/src/pages/moduleSettings.vue rename to client/src/pages/module/moduleSettings.vue diff --git a/client/src/pages/moduleVisibility.vue b/client/src/pages/module/moduleVisibility.vue similarity index 100% rename from client/src/pages/moduleVisibility.vue rename to client/src/pages/module/moduleVisibility.vue diff --git a/client/src/router/index.js b/client/src/router/index.js index 57e36b6d..b1c58d2a 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -17,7 +17,7 @@ import activity from '@/pages/activity'; import Router from 'vue-router'; import surveyPage from '@/pages/survey'; import styleGuidePage from '@/pages/styleguide'; -import moduleRoom from '@/pages/moduleRoom'; +import moduleRoom from '@/pages/module/moduleRoom'; import login from '@/pages/login'; import betaLogin from '@/pages/beta-login'; import hello from '@/pages/hello'; @@ -36,7 +36,6 @@ import onboardingStart from '@/pages/onboarding/start'; import onboardingStep1 from '@/pages/onboarding/step1'; import onboardingStep2 from '@/pages/onboarding/step2'; import onboardingStep3 from '@/pages/onboarding/step3'; -import settingsPage from '@/pages/moduleSettings'; import moduleRoutes from './module.routes'; import portfolioRoutes from './portfolio.routes'; @@ -222,10 +221,6 @@ const routes = [ }, ], }, - { - path: '/settings', - component: settingsPage, - }, {path: '/styleguide', component: styleGuidePage}, { path: '*', diff --git a/client/src/router/module.names.js b/client/src/router/module.names.js index ec35f516..793e0b65 100644 --- a/client/src/router/module.names.js +++ b/client/src/router/module.names.js @@ -1,3 +1,4 @@ export const SUBMISSIONS_PAGE = 'submissions'; export const MODULE_PAGE = 'module'; +export const MODULE_SETTINGS_PAGE = 'module-settings'; export const VISIBILITY_PAGE = 'visibility'; diff --git a/client/src/router/module.routes.js b/client/src/router/module.routes.js index 4eef3e0b..ba1d79f1 100644 --- a/client/src/router/module.routes.js +++ b/client/src/router/module.routes.js @@ -1,35 +1,41 @@ -import moduleBase from '@/pages/module-base'; -import module from '@/pages/module'; +import moduleBase from '@/pages/module/module-base'; +import module from '@/pages/module/module'; import submissions from '@/pages/submissions'; -import moduleVisibility from '@/pages/moduleVisibility'; -import {MODULE_PAGE, SUBMISSIONS_PAGE, VISIBILITY_PAGE} from '@/router/module.names'; +import moduleVisibility from '@/pages/module/moduleVisibility'; +import {MODULE_PAGE, MODULE_SETTINGS_PAGE, SUBMISSIONS_PAGE, VISIBILITY_PAGE} from '@/router/module.names'; +import settingsPage from '@/pages/module/moduleSettings'; export default [ - { - path: '/module/:slug', - component: moduleBase, - children: [ { - path: '', - name: MODULE_PAGE, - component: module, - meta: {filter: true}, + path: '/module/:slug', + component: moduleBase, + children: [ + { + path: '', + name: MODULE_PAGE, + component: module, + meta: {filter: true}, + }, + { + path: 'submissions/:id', + name: SUBMISSIONS_PAGE, + component: submissions, + meta: {filter: true}, + }, + { + path: 'settings', + name: MODULE_SETTINGS_PAGE, + component: settingsPage, + }, + { + path: 'visibility', + name: VISIBILITY_PAGE, + component: moduleVisibility, + meta: { + layout: 'simple', + hideNavigation: true, + }, + }, + ], }, - { - path: 'submissions/:id', - name: SUBMISSIONS_PAGE, - component: submissions, - meta: {filter: true}, - }, - { - path: 'visibility', - name: VISIBILITY_PAGE, - component: moduleVisibility, - meta: { - layout: 'simple', - hideNavigation: true - }, - }, - ], -} ]; From adf662533d851ef2f93d9839c8a16c286f41151d Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 10 Mar 2021 09:19:45 +0100 Subject: [PATCH 08/19] Add more navigation buttons --- client/src/pages/module/moduleSettings.vue | 7 +++++++ client/src/pages/module/moduleVisibility.vue | 2 ++ 2 files changed, 9 insertions(+) diff --git a/client/src/pages/module/moduleSettings.vue b/client/src/pages/module/moduleSettings.vue index 8ed8a4dd..46928609 100644 --- a/client/src/pages/module/moduleSettings.vue +++ b/client/src/pages/module/moduleSettings.vue @@ -12,6 +12,13 @@ können Sie diese Anpassungen hier übernehmen

+
+ Klasse auswählen +
diff --git a/client/src/pages/module/moduleVisibility.vue b/client/src/pages/module/moduleVisibility.vue index b0474497..302748fa 100644 --- a/client/src/pages/module/moduleVisibility.vue +++ b/client/src/pages/module/moduleVisibility.vue @@ -13,6 +13,7 @@ Von