From ff61c3ba997ac65fe20521ba1b57d762ba6900cf Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Sat, 11 May 2024 23:24:14 +0200 Subject: [PATCH 01/16] Allow for including courses of child groups in TCC raster --- .../TimeboundCourseConfigRaster.vue | 37 +++++++++++++++---- .../timeboundCourseConfig.graphql | 8 +++- .../apps/lesrooster/frontend/messages/en.json | 5 ++- aleksis/apps/lesrooster/schema/__init__.py | 15 +++++--- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index e6f52d9b..5fffea52 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -55,6 +55,18 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; /> </v-col> + <v-col + cols="6" + lg="2" + class="d-flex justify-space-between flex-wrap align-center" + > + <v-switch + v-model="includeChildGroups" + inset + :label="$t('lesrooster.timebound_course_config.filters.include_child_groups')" + ></v-switch> + </v-col> + <v-spacer /> <v-col @@ -234,6 +246,7 @@ export default { currentCourse: null, currentSubject: null, loading: false, + includeChildGroups: true, }; }, methods: { @@ -435,16 +448,25 @@ export default { let groupCombinations = {}; subject.courses.forEach((course) => { - let groupIds = JSON.stringify( - course.groups.map((group) => group.id).sort(), - ); + const ownGroupIDs = course.groups.map((group) => group.id); + let groupIDs; + + if (this.includeChildGroups && ownGroupIDs.some((groupID) => !this.groupIDList.includes(groupID))) { + groupIDs = JSON.stringify( + course.groups.map((group) => group.parentGroups.map((parentGroup) => parentGroup.id)).flat().sort() + ); + } else { + groupIDs = JSON.stringify( + ownGroupIDs.sort() + ); + } - if (!groupCombinations[groupIds]) { - groupCombinations[groupIds] = []; + if (!groupCombinations[groupIDs]) { + groupCombinations[groupIDs] = []; } - if (!groupCombinations[groupIds].find((c) => c.id === course.id)) { - groupCombinations[groupIds].push({ + if (!groupCombinations[groupIDs].find((c) => c.id === course.id)) { + groupCombinations[groupIDs].push({ ...course, }); } @@ -505,6 +527,7 @@ export default { variables() { return { groups: this.groupIDList, + includeChildGroups: this.includeChildGroups, }; }, }, diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql index 885c6d33..b0e4aad2 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql @@ -23,6 +23,11 @@ fragment courseFields on LesroosterExtendedCourseType { id name shortName + parentGroups { + id + name + shortName + } } lessonQuota } @@ -93,11 +98,12 @@ mutation updateTimeboundCourseConfigs( } } -query subjects($orderBy: [String], $filters: JSONString, $groups: [ID]) { +query subjects($orderBy: [String], $filters: JSONString, $groups: [ID], $includeChildGroups: Boolean!) { subjects: lesroosterExtendedSubjects( orderBy: $orderBy filters: $filters groups: $groups + includeChildGroups: $includeChildGroups ) { ...subjectFields courses { diff --git a/aleksis/apps/lesrooster/frontend/messages/en.json b/aleksis/apps/lesrooster/frontend/messages/en.json index 2a5da55f..02c440cd 100644 --- a/aleksis/apps/lesrooster/frontend/messages/en.json +++ b/aleksis/apps/lesrooster/frontend/messages/en.json @@ -81,7 +81,10 @@ "all_teachers": "All teachers", "no_course_selected": "No course selected", "create_timebound_course_config": "Create timebound course config", - "subject": "Subject" + "subject": "Subject", + "filters": { + "include_child_groups": "Include courses from child groups" + } }, "lesson_raster": { "menu_title": "Lesson Raster" diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py index f432dd7f..3a51a42a 100644 --- a/aleksis/apps/lesrooster/schema/__init__.py +++ b/aleksis/apps/lesrooster/schema/__init__.py @@ -109,7 +109,7 @@ class Query(graphene.ObjectType): current_validity_range = graphene.Field(ValidityRangeType) lesrooster_extended_subjects = FilterOrderList( - LesroosterExtendedSubjectType, groups=graphene.List(graphene.ID) + LesroosterExtendedSubjectType, groups=graphene.List(graphene.ID), include_child_groups=graphene.Boolean(required=True) ) groups_by_time_grid = graphene.List(GroupType, time_grid=graphene.ID(required=True)) @@ -160,13 +160,18 @@ class Query(graphene.ObjectType): return Supervision.objects.all() @staticmethod - def resolve_lesrooster_extended_subjects(root, info, groups): + def resolve_lesrooster_extended_subjects(root, info, groups, include_child_groups): + courses = get_objects_for_user( + info.context.user, "cursus.view_course", Course.objects.all() + ) + if include_child_groups: + courses = courses.filter(Q(groups__in=groups) | Q(groups__parent_groups__in=groups)) + else: + courses = courses.filter(groups__in=groups) subjects = Subject.objects.all().prefetch_related( Prefetch( "courses", - queryset=get_objects_for_user( - info.context.user, "cursus.view_course", Course.objects.all() - ).filter(groups__in=groups), + queryset=courses, ) ) if not info.context.user.has_perm("lesrooster.view_subject_rule"): -- GitLab From df4de0b67b10881be46b06b186ebe6fe2f3c8245 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Sun, 12 May 2024 01:43:01 +0200 Subject: [PATCH 02/16] Add loading indicators --- .../timebound_course_config/TimeboundCourseConfigRaster.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 5fffea52..704b94c8 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -15,6 +15,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; hide-default-footer :headers="headers" :items="tableItems" + :loading="$apollo.queries.subjects.loading" > <template #top> <v-row> @@ -64,6 +65,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; v-model="includeChildGroups" inset :label="$t('lesrooster.timebound_course_config.filters.include_child_groups')" + :loading="$apollo.queries.subjects.loading" ></v-switch> </v-col> @@ -91,6 +93,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; !createdCourseConfigs.length && !createdCourses.length " + :loading="loading" @click="save" /> </v-col> -- GitLab From e1720c322b6a82b6d463e51e7f865017ee47c77f Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Sun, 12 May 2024 01:54:46 +0200 Subject: [PATCH 03/16] Set fixed width in TCC raster --- .../timebound_course_config/TimeboundCourseConfigRaster.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 704b94c8..dbdc7cad 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -436,7 +436,7 @@ export default { headers() { let groupHeadersWithWidth = this.groupHeaders.map((header) => ({ ...header, - width: `${Math.max(95 / this.groupHeaders.length, 15)}vw`, + width: "20vw", })); return [ { -- GitLab From ee9165da4c20712b9f45c13f546b92c3dc2913de Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Sun, 12 May 2024 20:46:01 +0200 Subject: [PATCH 04/16] Compute group combinations more efficiently and without duplicates --- .../TimeboundCourseConfigRaster.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index dbdc7cad..fcda5ff4 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -403,10 +403,12 @@ export default { return this.selectedGroups.map((group) => group.id); }, subjectGroupCombinations() { - return [].concat.apply( - [], - this.items.map((subject) => Object.keys(subject.groupCombinations)), + const uniqueCombinations = new Set( + this.items.flatMap(subject => + Object.keys(subject.groupCombinations) + ) ); + return Array.from(uniqueCombinations); }, groupHeaders() { return this.selectedGroups -- GitLab From d454ea9e4deca96a1c4938886838af3d90346491 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Mon, 13 May 2024 13:31:24 +0200 Subject: [PATCH 05/16] Fix subject resolving mechanism --- aleksis/apps/lesrooster/schema/__init__.py | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py index 3a51a42a..67b3f349 100644 --- a/aleksis/apps/lesrooster/schema/__init__.py +++ b/aleksis/apps/lesrooster/schema/__init__.py @@ -161,22 +161,21 @@ class Query(graphene.ObjectType): @staticmethod def resolve_lesrooster_extended_subjects(root, info, groups, include_child_groups): - courses = get_objects_for_user( - info.context.user, "cursus.view_course", Course.objects.all() - ) if include_child_groups: - courses = courses.filter(Q(groups__in=groups) | Q(groups__parent_groups__in=groups)) + courses = Course.objects.filter(Q(groups__in=groups) | Q(groups__parent_groups__in=groups)) else: - courses = courses.filter(groups__in=groups) - subjects = Subject.objects.all().prefetch_related( - Prefetch( - "courses", - queryset=courses, - ) + courses = Course.objects.filter(groups__in=groups) + courses = get_objects_for_user( + info.context.user, "cursus.view_course", courses ) - if not info.context.user.has_perm("lesrooster.view_subject_rule"): - return get_objects_for_user(info.context.user, "cursus.view_subject", subjects) - return subjects + subjects = get_objects_for_user(info.context.user, "cursus.view_subject", Subject.objects.all()) + + return subjects.prefetch_related( + Prefetch( + "courses", + queryset=courses, + ) + ) @staticmethod def resolve_current_validity_range(root, info): -- GitLab From 00139d2487c217ffd9310cec107e8ee4db94d253 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Mon, 13 May 2024 13:36:02 +0200 Subject: [PATCH 06/16] Make header generation mechanism more efficient --- .../TimeboundCourseConfigRaster.vue | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index fcda5ff4..9a42bfee 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -250,6 +250,8 @@ export default { currentSubject: null, loading: false, includeChildGroups: true, + subjectGroupCombinations: [], + selectedGroupHeaders: [], }; }, methods: { @@ -402,38 +404,22 @@ export default { groupIDList() { return this.selectedGroups.map((group) => group.id); }, - subjectGroupCombinations() { - const uniqueCombinations = new Set( - this.items.flatMap(subject => - Object.keys(subject.groupCombinations) - ) - ); - return Array.from(uniqueCombinations); + groupCombinationHeaders() { + return this.subjectGroupCombinations.map((combination) => { + let parsedCombination = JSON.parse(combination); + return { + text: parsedCombination + .map( + (groupID) => + this.selectedGroups.find((group) => group.id === groupID)?.shortName || this.selectedGroups.find((group) => group.id === groupID)?.shortName, + ) + .join(", "), + value: combination, + }; + }); }, groupHeaders() { - return this.selectedGroups - .map((group) => ({ - text: group.shortName, - value: JSON.stringify([group.id]), - })) - .concat( - this.subjectGroupCombinations.map((combination) => { - let parsedCombination = JSON.parse(combination); - return { - text: parsedCombination - .map( - (groupID) => - this.groups.find((group) => group.id === groupID).shortName, - ) - .join(", "), - value: combination, - }; - }), - ) - .filter( - (obj, index, self) => - index === self.findIndex((o) => o.value === obj.value), - ); + return [...this.selectedGroupHeaders, ...this.groupCombinationHeaders]; }, headers() { let groupHeadersWithWidth = this.groupHeaders.map((header) => ({ @@ -449,7 +435,9 @@ export default { ].concat(groupHeadersWithWidth); }, items() { - return this.subjects.map((subject) => { + let groupCombinationsSet = new Set(); + + const groupedItems = this.subjects.map((subject) => { let groupCombinations = {}; subject.courses.forEach((course) => { @@ -468,6 +456,9 @@ export default { if (!groupCombinations[groupIDs]) { groupCombinations[groupIDs] = []; + if (course.groups.length > 1) { + groupCombinationsSet.add(groupIDs); + } } if (!groupCombinations[groupIDs].find((c) => c.id === course.id)) { @@ -489,6 +480,10 @@ export default { return subject; }); + + this.subjectGroupCombinations = Array.from(groupCombinationsSet); + + return groupedItems; }, tableItems() { return this.items.map((subject) => { @@ -505,6 +500,15 @@ export default { }); }, }, + watch: { + selectedGroups(newValue) { + this.selectedGroupHeaders = newValue + .map((group) => ({ + text: group.shortName, + value: JSON.stringify([group.id]), + })); + }, + }, apollo: { currentValidityRange: { query: gqlCurrentValidityRange, @@ -513,9 +517,6 @@ export default { this.internalValidityRange = data.currentValidityRange; }, }, - groups: { - query: gqlGroups, - }, groupsForPlanning: { query: gqlGroupsForPlanning, result({ data }) { -- GitLab From 4c12d98598653e82eb850b8435a4e922d83ef36e Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Mon, 13 May 2024 13:39:34 +0200 Subject: [PATCH 07/16] Reformat --- .../TimeboundCourseConfigRaster.vue | 42 ++++++++++++------- .../timeboundCourseConfig.graphql | 7 +++- aleksis/apps/lesrooster/schema/__init__.py | 24 ++++++----- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 9a42bfee..e6e3f276 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -64,7 +64,11 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; <v-switch v-model="includeChildGroups" inset - :label="$t('lesrooster.timebound_course_config.filters.include_child_groups')" + :label=" + $t( + 'lesrooster.timebound_course_config.filters.include_child_groups', + ) + " :loading="$apollo.queries.subjects.loading" ></v-switch> </v-col> @@ -409,11 +413,14 @@ export default { let parsedCombination = JSON.parse(combination); return { text: parsedCombination - .map( - (groupID) => - this.selectedGroups.find((group) => group.id === groupID)?.shortName || this.selectedGroups.find((group) => group.id === groupID)?.shortName, + .map( + (groupID) => + this.selectedGroups.find((group) => group.id === groupID) + ?.shortName || + this.selectedGroups.find((group) => group.id === groupID) + ?.shortName, ) - .join(", "), + .join(", "), value: combination, }; }); @@ -444,14 +451,20 @@ export default { const ownGroupIDs = course.groups.map((group) => group.id); let groupIDs; - if (this.includeChildGroups && ownGroupIDs.some((groupID) => !this.groupIDList.includes(groupID))) { + if ( + this.includeChildGroups && + ownGroupIDs.some((groupID) => !this.groupIDList.includes(groupID)) + ) { groupIDs = JSON.stringify( - course.groups.map((group) => group.parentGroups.map((parentGroup) => parentGroup.id)).flat().sort() + course.groups + .map((group) => + group.parentGroups.map((parentGroup) => parentGroup.id), + ) + .flat() + .sort(), ); } else { - groupIDs = JSON.stringify( - ownGroupIDs.sort() - ); + groupIDs = JSON.stringify(ownGroupIDs.sort()); } if (!groupCombinations[groupIDs]) { @@ -502,11 +515,10 @@ export default { }, watch: { selectedGroups(newValue) { - this.selectedGroupHeaders = newValue - .map((group) => ({ - text: group.shortName, - value: JSON.stringify([group.id]), - })); + this.selectedGroupHeaders = newValue.map((group) => ({ + text: group.shortName, + value: JSON.stringify([group.id]), + })); }, }, apollo: { diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql index b0e4aad2..4f9ca031 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql @@ -98,7 +98,12 @@ mutation updateTimeboundCourseConfigs( } } -query subjects($orderBy: [String], $filters: JSONString, $groups: [ID], $includeChildGroups: Boolean!) { +query subjects( + $orderBy: [String] + $filters: JSONString + $groups: [ID] + $includeChildGroups: Boolean! +) { subjects: lesroosterExtendedSubjects( orderBy: $orderBy filters: $filters diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py index 67b3f349..26e612a0 100644 --- a/aleksis/apps/lesrooster/schema/__init__.py +++ b/aleksis/apps/lesrooster/schema/__init__.py @@ -109,7 +109,9 @@ class Query(graphene.ObjectType): current_validity_range = graphene.Field(ValidityRangeType) lesrooster_extended_subjects = FilterOrderList( - LesroosterExtendedSubjectType, groups=graphene.List(graphene.ID), include_child_groups=graphene.Boolean(required=True) + LesroosterExtendedSubjectType, + groups=graphene.List(graphene.ID), + include_child_groups=graphene.Boolean(required=True), ) groups_by_time_grid = graphene.List(GroupType, time_grid=graphene.ID(required=True)) @@ -162,20 +164,22 @@ class Query(graphene.ObjectType): @staticmethod def resolve_lesrooster_extended_subjects(root, info, groups, include_child_groups): if include_child_groups: - courses = Course.objects.filter(Q(groups__in=groups) | Q(groups__parent_groups__in=groups)) + courses = Course.objects.filter( + Q(groups__in=groups) | Q(groups__parent_groups__in=groups) + ) else: courses = Course.objects.filter(groups__in=groups) - courses = get_objects_for_user( - info.context.user, "cursus.view_course", courses + courses = get_objects_for_user(info.context.user, "cursus.view_course", courses) + subjects = get_objects_for_user( + info.context.user, "cursus.view_subject", Subject.objects.all() ) - subjects = get_objects_for_user(info.context.user, "cursus.view_subject", Subject.objects.all()) return subjects.prefetch_related( - Prefetch( - "courses", - queryset=courses, - ) - ) + Prefetch( + "courses", + queryset=courses, + ) + ) @staticmethod def resolve_current_validity_range(root, info): -- GitLab From f222ea864e2a90a35cb6eb778291a5ec917050b1 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Mon, 13 May 2024 16:23:35 +0200 Subject: [PATCH 08/16] Some optimisations in frontend --- .../TimeboundCourseConfigRaster.vue | 157 +++++++----------- 1 file changed, 62 insertions(+), 95 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index e6e3f276..b9dcaafc 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -14,7 +14,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; disable-pagination hide-default-footer :headers="headers" - :items="tableItems" + :items="items" :loading="$apollo.queries.subjects.loading" > <template #top> @@ -248,14 +248,14 @@ export default { subjects: [], editedCourseConfigs: [], createdCourseConfigs: [], - newCourses: [], createdCourses: [], currentCourse: null, currentSubject: null, loading: false, includeChildGroups: true, - subjectGroupCombinations: [], selectedGroupHeaders: [], + items: [], + groupCombinationsSet: new Set(), }; }, methods: { @@ -383,30 +383,62 @@ export default { ]; }, addCourse(subject, groups) { - let courseSubjectGroup = this.newCourses.find( - (courseSubject) => courseSubject.subject === subject, - ); - if (courseSubjectGroup) { - if (courseSubjectGroup.groupCombinations) { - this.$set(courseSubjectGroup.groupCombinations, groups, [ + this.$set(this.items.find((i) => i.subject.id === subject), groups, [ { teachers: [], newCourse: true }, - ]); - } else { - courseSubjectGroup.groupCombinations = { - [groups]: [{ teachers: [], newCourse: true }], - }; - } - } else { - this.newCourses.push({ - subject: subject, - groupCombinations: { [groups]: [{ teachers: [], newCourse: true }] }, + ]); + }, + generateTableItems(subjects) { + return subjects.map((subject) => { + let { courses, ...reducedSubject } = subject; + let groupCombinations = {}; + + courses.forEach((course) => { + const ownGroupIDs = course.groups.map((group) => group.id); + let groupIDs; + + if ( + this.includeChildGroups && + ownGroupIDs.some((groupID) => !this.groupIDSet.has(groupID)) + ) { + groupIDs = JSON.stringify( + course.groups + .flatMap((group) => + group.parentGroups.map((parentGroup) => parentGroup.id), + ) + .sort(), + ); + } else { + groupIDs = JSON.stringify(ownGroupIDs.sort()); + } + + if (!groupCombinations[groupIDs]) { + groupCombinations[groupIDs] = []; + if (course.groups.length > 1) { + this.groupCombinationsSet.add(groupIDs); + } + groupCombinations[groupIDs].push({ + ...course, + }); + } else if (!groupCombinations[groupIDs].some((c) => c.id === course.id)) { + groupCombinations[groupIDs].push({ + ...course, + }); + } }); - } + + return { + subject: reducedSubject, + ...Object.fromEntries( + this.groupHeaders.map((header) => [header.value, []]), + ), + ...groupCombinations, + }; + }); }, }, computed: { - groupIDList() { - return this.selectedGroups.map((group) => group.id); + groupIDSet() { + return new Set(this.selectedGroups.map((group) => group.id)); }, groupCombinationHeaders() { return this.subjectGroupCombinations.map((combination) => { @@ -441,76 +473,8 @@ export default { }, ].concat(groupHeadersWithWidth); }, - items() { - let groupCombinationsSet = new Set(); - - const groupedItems = this.subjects.map((subject) => { - let groupCombinations = {}; - - subject.courses.forEach((course) => { - const ownGroupIDs = course.groups.map((group) => group.id); - let groupIDs; - - if ( - this.includeChildGroups && - ownGroupIDs.some((groupID) => !this.groupIDList.includes(groupID)) - ) { - groupIDs = JSON.stringify( - course.groups - .map((group) => - group.parentGroups.map((parentGroup) => parentGroup.id), - ) - .flat() - .sort(), - ); - } else { - groupIDs = JSON.stringify(ownGroupIDs.sort()); - } - - if (!groupCombinations[groupIDs]) { - groupCombinations[groupIDs] = []; - if (course.groups.length > 1) { - groupCombinationsSet.add(groupIDs); - } - } - - if (!groupCombinations[groupIDs].find((c) => c.id === course.id)) { - groupCombinations[groupIDs].push({ - ...course, - }); - } - }); - - subject = { - ...subject, - groupCombinations: { ...groupCombinations }, - newCourses: { - ...this.newCourses.find( - (courseSubject) => courseSubject.subject === subject.id, - )?.groupCombinations, - }, - }; - - return subject; - }); - - this.subjectGroupCombinations = Array.from(groupCombinationsSet); - - return groupedItems; - }, - tableItems() { - return this.items.map((subject) => { - // eslint-disable-next-line no-unused-vars - let { courses, groupCombinations, ...reducedSubject } = subject; - return { - subject: reducedSubject, - ...Object.fromEntries( - this.groupHeaders.map((header) => [header.value, []]), - ), - ...subject.groupCombinations, - ...subject.newCourses, - }; - }); + subjectGroupCombinations() { + return Array.from(this.groupCombinationsSet); }, }, watch: { @@ -533,21 +497,24 @@ export default { query: gqlGroupsForPlanning, result({ data }) { if (!data) return; - console.log(data.groups); this.selectedGroups = data.groupsForPlanning; }, }, subjects: { query: subjects, skip() { - return !this.groupIDList.length; + return !this.groupIDSet.size || !this.internalValidityRange; }, variables() { return { - groups: this.groupIDList, + groups: Array.from(this.groupIDSet), includeChildGroups: this.includeChildGroups, }; }, + result({ data }) { + if (!data) return; + this.items = this.generateTableItems(data.subjects); + }, }, persons: { query: gqlTeachers, -- GitLab From 6cae2debe76c80e8c043c154dec54ac43764efc0 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Mon, 13 May 2024 16:23:57 +0200 Subject: [PATCH 09/16] Reformat --- .../TimeboundCourseConfigRaster.vue | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index b9dcaafc..3b56b428 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -383,9 +383,11 @@ export default { ]; }, addCourse(subject, groups) { - this.$set(this.items.find((i) => i.subject.id === subject), groups, [ - { teachers: [], newCourse: true }, - ]); + this.$set( + this.items.find((i) => i.subject.id === subject), + groups, + [{ teachers: [], newCourse: true }], + ); }, generateTableItems(subjects) { return subjects.map((subject) => { @@ -419,7 +421,9 @@ export default { groupCombinations[groupIDs].push({ ...course, }); - } else if (!groupCombinations[groupIDs].some((c) => c.id === course.id)) { + } else if ( + !groupCombinations[groupIDs].some((c) => c.id === course.id) + ) { groupCombinations[groupIDs].push({ ...course, }); -- GitLab From 53a38d26be2cb8b64663c6200b142f95e2194e79 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 12:02:12 +0200 Subject: [PATCH 10/16] Move filtering of TCCs for ValidityRange to backend --- .../TimeboundCourseConfigRaster.vue | 17 ++++------------- .../timeboundCourseConfig.graphql | 2 ++ aleksis/apps/lesrooster/schema/__init__.py | 6 ++++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 3b56b428..8aee25e3 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -264,15 +264,7 @@ export default { }, getCurrentCourseConfig(course) { if (course.lrTimeboundCourseConfigs?.length) { - let currentCourseConfigs = course.lrTimeboundCourseConfigs.filter( - (timeboundConfig) => - timeboundConfig.validityRange.id === this.internalValidityRange.id, - ); - if (currentCourseConfigs.length) { - return currentCourseConfigs[0]; - } else { - return null; - } + return course.lrTimeboundCourseConfigs[0]; } else { return null; } @@ -296,9 +288,7 @@ export default { } } else { if ( - !course.lrTimeboundCourseConfigs?.filter( - (c) => c.validityRange.id === this.internalValidityRange?.id, - ).length + !course.lrTimeboundCourseConfigs?.length ) { let existingCreatedCourseConfig = this.createdCourseConfigs.find( (c) => @@ -507,12 +497,13 @@ export default { subjects: { query: subjects, skip() { - return !this.groupIDSet.size || !this.internalValidityRange; + return !this.groupIDSet.size || !this.internalValidityRange || !this.persons.length; }, variables() { return { groups: Array.from(this.groupIDSet), includeChildGroups: this.includeChildGroups, + validityRange: this.internalValidityRange.id, }; }, result({ data }) { diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql index 4f9ca031..64749799 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/timeboundCourseConfig.graphql @@ -103,12 +103,14 @@ query subjects( $filters: JSONString $groups: [ID] $includeChildGroups: Boolean! + $validityRange: ID! ) { subjects: lesroosterExtendedSubjects( orderBy: $orderBy filters: $filters groups: $groups includeChildGroups: $includeChildGroups + validityRange: $validityRange ) { ...subjectFields courses { diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py index 26e612a0..50fff730 100644 --- a/aleksis/apps/lesrooster/schema/__init__.py +++ b/aleksis/apps/lesrooster/schema/__init__.py @@ -112,6 +112,7 @@ class Query(graphene.ObjectType): LesroosterExtendedSubjectType, groups=graphene.List(graphene.ID), include_child_groups=graphene.Boolean(required=True), + validity_range=graphene.ID(required=True) ) groups_by_time_grid = graphene.List(GroupType, time_grid=graphene.ID(required=True)) @@ -162,14 +163,15 @@ class Query(graphene.ObjectType): return Supervision.objects.all() @staticmethod - def resolve_lesrooster_extended_subjects(root, info, groups, include_child_groups): + def resolve_lesrooster_extended_subjects(root, info, groups, include_child_groups, validity_range): if include_child_groups: courses = Course.objects.filter( Q(groups__in=groups) | Q(groups__parent_groups__in=groups) ) else: courses = Course.objects.filter(groups__in=groups) - courses = get_objects_for_user(info.context.user, "cursus.view_course", courses) + course_configs = TimeboundCourseConfig.objects.filter(validity_range=validity_range) + courses = get_objects_for_user(info.context.user, "cursus.view_course", courses).prefetch_related(Prefetch("lr_timebound_course_configs", queryset=course_configs)) subjects = get_objects_for_user( info.context.user, "cursus.view_subject", Subject.objects.all() ) -- GitLab From 303ce99e0f40b8cc61a947e7f2a7f980e336ee2f Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 12:21:59 +0200 Subject: [PATCH 11/16] Move course name generation to backend --- .../timebound_course_config/TimeboundCourseConfigRaster.vue | 1 - aleksis/apps/lesrooster/schema/timebound_course_config.py | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index d3c3d5d2..e9a902dd 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -281,7 +281,6 @@ export default { this.createdCourses.push({ subject: subject.id, groups: JSON.parse(header.value), - name: `${header.text}-${subject.name}`, schoolTerm: this.internalValidityRange.schoolTerm.id, ...newValue, }); diff --git a/aleksis/apps/lesrooster/schema/timebound_course_config.py b/aleksis/apps/lesrooster/schema/timebound_course_config.py index 1d1e07a5..87426363 100644 --- a/aleksis/apps/lesrooster/schema/timebound_course_config.py +++ b/aleksis/apps/lesrooster/schema/timebound_course_config.py @@ -134,7 +134,8 @@ class CourseBatchCreateForSchoolTermMutation(graphene.Mutation): teachers = Person.objects.filter(pk__in=course_input.teachers) course = Course.objects.create( - name=course_input.name, + name=f"""{''.join(groups.values_list('short_name', flat=True) + .order_by('short_name'))}-{subject.name}""", subject=subject, lesson_quota=course_input.lesson_quota or None, ) -- GitLab From e9e81d97d0bef41a0aac3d461429a6d0e3931c52 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 12:24:56 +0200 Subject: [PATCH 12/16] Remove unused copy from different VR button --- .../TimeboundCourseConfigRaster.vue | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index e9a902dd..69e3d4d5 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -74,18 +74,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; </v-col> <v-spacer /> - - <v-col - cols="8" - lg="3" - class="d-flex justify-space-between flex-wrap align-center" - > - <secondary-action-button - i18n-key="actions.copy_last_configuration" - block - class="mr-4" - /> - </v-col> + <v-col cols="4" lg="1" -- GitLab From cb30b7ae75da04ed507854e58a3f24da928c8046 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 12:41:14 +0200 Subject: [PATCH 13/16] Make rows in course planning more dense --- .../TimeboundCourseConfigRaster.vue | 19 +++++++++++-------- .../apps/lesrooster/frontend/messages/en.json | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 69e3d4d5..2117db79 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -108,12 +108,13 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; v-for="(course, index) in value" :key="index" no-gutters - class="mt-2" + class="my-1" > - <v-col cols="6"> + <v-col cols="4"> <positive-small-integer-field dense - filled + outlined + hide-details class="mx-1" :disabled="loading" :value=" @@ -130,14 +131,16 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; " /> </v-col> - <v-col cols="6"> + <v-col cols="8"> <v-autocomplete - counter dense - filled + outlined multiple + chips + small-chips + hide-details :items="getTeacherList(item.subject.teachers)" - item-text="fullName" + item-text="shortName" item-value="id" class="mx-1" :disabled="loading" @@ -447,7 +450,7 @@ export default { headers() { let groupHeadersWithWidth = this.groupHeaders.map((header) => ({ ...header, - width: "20vw", + width: "15vw", })); return [ { diff --git a/aleksis/apps/lesrooster/frontend/messages/en.json b/aleksis/apps/lesrooster/frontend/messages/en.json index 02c440cd..8bf279d3 100644 --- a/aleksis/apps/lesrooster/frontend/messages/en.json +++ b/aleksis/apps/lesrooster/frontend/messages/en.json @@ -72,7 +72,7 @@ "raster_menu_title": "Plan courses", "title": "Timebound course config", "title_plural": "Timebound course configs", - "lesson_quota": "Scheduled lesson quota", + "lesson_quota": "Lessons", "course": "Course", "groups": "Groups", "teachers": "Teachers", -- GitLab From 2c0743cc61ed0091b0cbd4e4dd29b264e8b181d1 Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 13:12:05 +0200 Subject: [PATCH 14/16] Use i18n key for VR select label --- .../timebound_course_config/TimeboundCourseConfigRaster.vue | 2 +- aleksis/apps/lesrooster/frontend/messages/en.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 2117db79..ee32df73 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -49,7 +49,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; <validity-range-field outlined filled - label="Select Validity Range" + :label="$t('lesrooster.timebound_course_config.filters.validity_range')" hide-details v-model="internalValidityRange" :loading="$apollo.queries.currentValidityRange.loading" diff --git a/aleksis/apps/lesrooster/frontend/messages/en.json b/aleksis/apps/lesrooster/frontend/messages/en.json index 8bf279d3..5044cf19 100644 --- a/aleksis/apps/lesrooster/frontend/messages/en.json +++ b/aleksis/apps/lesrooster/frontend/messages/en.json @@ -83,7 +83,8 @@ "create_timebound_course_config": "Create timebound course config", "subject": "Subject", "filters": { - "include_child_groups": "Include courses from child groups" + "include_child_groups": "Include courses from child groups", + "validity_range": "Validity range" } }, "lesson_raster": { -- GitLab From 95fcef558584753a340e6921e07814f26e8e86ee Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 13:31:54 +0200 Subject: [PATCH 15/16] Make group select field more compact --- .../TimeboundCourseConfigRaster.vue | 24 +++++++++++++++++-- .../apps/lesrooster/frontend/messages/de.json | 12 ++++++++-- .../apps/lesrooster/frontend/messages/en.json | 8 +++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index ee32df73..90938cb3 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -34,11 +34,31 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; item-value="id" return-object :disabled="$apollo.queries.groupsForPlanning.loading" - :label="$t('lesrooster.timebound_course_config.groups')" + :label="$t('lesrooster.timebound_course_config.filters.groups.label')" :loading="$apollo.queries.groupsForPlanning.loading" v-model="selectedGroups" class="mr-4" - /> + > + <template #selection="{ item, index }"> + <div v-if="selectedGroups.length === groupsForPlanning.length"> + <span + v-if="index === 0" + class="grey--text" + > + {{ $t("lesrooster.timebound_course_config.filters.groups.all_groups") }} + </span> + </div> + <div v-else> + <span v-if="index === 0">{{ item.shortName }}</span> + <span + v-if="index === 1" + class="grey--text text-caption" + > + {{ $t("lesrooster.timebound_course_config.filters.groups.other_groups", { groupCount: selectedGroups.length - 1 }) }} + </span> + </div> + </template> + </v-autocomplete> </v-col> <v-col diff --git a/aleksis/apps/lesrooster/frontend/messages/de.json b/aleksis/apps/lesrooster/frontend/messages/de.json index 7f151145..5a72ae73 100644 --- a/aleksis/apps/lesrooster/frontend/messages/de.json +++ b/aleksis/apps/lesrooster/frontend/messages/de.json @@ -74,14 +74,22 @@ "title_plural": "Kurskonfigurationen", "lesson_quota": "Stundenpensum", "course": "Kurs", - "groups": "Gruppen", "teachers": "Lehrkräfte", "teachers_for": "Lehrkräfte für", "subject_teachers": "Fachlehrkräfte", "all_teachers": "Alle Lehrkräfte", "no_course_selected": "Kein Kurs ausgewählt", "create_timebound_course_config": "Kurskonfiguration erstellen", - "subject": "Fach" + "subject": "Fach", + "filters": { + "include_child_groups": "Kurse von Kindgruppen einbeziehen", + "validity_range": "Gültigkeitszeitraum", + "groups": { + "label": "Gruppen", + "other_groups": " (+{groupCount} andere)", + "all_groups": "Alle ausgewählt" + } + } }, "lesson_raster": { "menu_title": "Stundenraster" diff --git a/aleksis/apps/lesrooster/frontend/messages/en.json b/aleksis/apps/lesrooster/frontend/messages/en.json index 5044cf19..233fa044 100644 --- a/aleksis/apps/lesrooster/frontend/messages/en.json +++ b/aleksis/apps/lesrooster/frontend/messages/en.json @@ -74,7 +74,6 @@ "title_plural": "Timebound course configs", "lesson_quota": "Lessons", "course": "Course", - "groups": "Groups", "teachers": "Teachers", "teachers_for": "Teachers for", "subject_teachers": "Teachers for this subject", @@ -84,7 +83,12 @@ "subject": "Subject", "filters": { "include_child_groups": "Include courses from child groups", - "validity_range": "Validity range" + "validity_range": "Validity range", + "groups": { + "label": "Groups", + "other_groups": " (+{groupCount} others)", + "all_groups": "All selected" + } } }, "lesson_raster": { -- GitLab From 3d6488a5c246e141b0721452f7947ad7d0a60f8a Mon Sep 17 00:00:00 2001 From: Hangzhi Yu <hangzhi@protonmail.com> Date: Tue, 14 May 2024 13:57:11 +0200 Subject: [PATCH 16/16] Reformat --- .../TimeboundCourseConfigRaster.vue | 45 +++++++++++-------- aleksis/apps/lesrooster/schema/__init__.py | 10 +++-- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue index 90938cb3..32fa24b2 100644 --- a/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue +++ b/aleksis/apps/lesrooster/frontend/components/timebound_course_config/TimeboundCourseConfigRaster.vue @@ -34,27 +34,32 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; item-value="id" return-object :disabled="$apollo.queries.groupsForPlanning.loading" - :label="$t('lesrooster.timebound_course_config.filters.groups.label')" - :loading="$apollo.queries.groupsForPlanning.loading" + :label=" + $t('lesrooster.timebound_course_config.filters.groups.label') + " + :loading="$apollo.queries.grouptemplatesForPlanning.loading" v-model="selectedGroups" class="mr-4" > <template #selection="{ item, index }"> <div v-if="selectedGroups.length === groupsForPlanning.length"> - <span - v-if="index === 0" - class="grey--text" - > - {{ $t("lesrooster.timebound_course_config.filters.groups.all_groups") }} + <span v-if="index === 0" class="grey--text"> + {{ + $t( + "lesrooster.timebound_course_config.filters.groups.all_groups", + ) + }} </span> </div> <div v-else> <span v-if="index === 0">{{ item.shortName }}</span> - <span - v-if="index === 1" - class="grey--text text-caption" - > - {{ $t("lesrooster.timebound_course_config.filters.groups.other_groups", { groupCount: selectedGroups.length - 1 }) }} + <span v-if="index === 1" class="grey--text text-caption"> + {{ + $t( + "lesrooster.timebound_course_config.filters.groups.other_groups", + { groupCount: selectedGroups.length - 1 }, + ) + }} </span> </div> </template> @@ -69,7 +74,9 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; <validity-range-field outlined filled - :label="$t('lesrooster.timebound_course_config.filters.validity_range')" + :label=" + $t('lesrooster.timebound_course_config.filters.validity_range') + " hide-details v-model="internalValidityRange" :loading="$apollo.queries.currentValidityRange.loading" @@ -94,7 +101,7 @@ import SubjectChip from "aleksis.apps.cursus/components/SubjectChip.vue"; </v-col> <v-spacer /> - + <v-col cols="4" lg="1" @@ -300,9 +307,7 @@ export default { Object.assign(existingCreatedCourse, newValue); } } else { - if ( - !course.lrTimeboundCourseConfigs?.length - ) { + if (!course.lrTimeboundCourseConfigs?.length) { let existingCreatedCourseConfig = this.createdCourseConfigs.find( (c) => c.course === course.id && @@ -510,7 +515,11 @@ export default { subjects: { query: subjects, skip() { - return !this.groupIDSet.size || !this.internalValidityRange || !this.persons.length; + return ( + !this.groupIDSet.size || + !this.internalValidityRange || + !this.persons.length + ); }, variables() { return { diff --git a/aleksis/apps/lesrooster/schema/__init__.py b/aleksis/apps/lesrooster/schema/__init__.py index 05c91b5c..354aea19 100644 --- a/aleksis/apps/lesrooster/schema/__init__.py +++ b/aleksis/apps/lesrooster/schema/__init__.py @@ -113,7 +113,7 @@ class Query(graphene.ObjectType): LesroosterExtendedSubjectType, groups=graphene.List(graphene.ID), include_child_groups=graphene.Boolean(required=True), - validity_range=graphene.ID(required=True) + validity_range=graphene.ID(required=True), ) groups_by_time_grid = graphene.List(GroupType, time_grid=graphene.ID(required=True)) @@ -164,7 +164,9 @@ class Query(graphene.ObjectType): return Supervision.objects.all() @staticmethod - def resolve_lesrooster_extended_subjects(root, info, groups, include_child_groups, validity_range): + def resolve_lesrooster_extended_subjects( + root, info, groups, include_child_groups, validity_range + ): if include_child_groups: courses = Course.objects.filter( Q(groups__in=groups) | Q(groups__parent_groups__in=groups) @@ -172,7 +174,9 @@ class Query(graphene.ObjectType): else: courses = Course.objects.filter(groups__in=groups) course_configs = TimeboundCourseConfig.objects.filter(validity_range=validity_range) - courses = get_objects_for_user(info.context.user, "cursus.view_course", courses).prefetch_related(Prefetch("lr_timebound_course_configs", queryset=course_configs)) + courses = get_objects_for_user( + info.context.user, "cursus.view_course", courses + ).prefetch_related(Prefetch("lr_timebound_course_configs", queryset=course_configs)) subjects = get_objects_for_user( info.context.user, "cursus.view_subject", Subject.objects.all() ) -- GitLab