diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0bce4160fd70426d1078ac7b238262af44566b43..9896d170efea81df55d670f15c8df22d6ee08256 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,15 @@ If you're upgrading from 3.x, there is now a migration path to use. Therefore, please install ``AlekSIS-App-Lesrooster`` which now includes parts of the legacy Chronos and the migration path. +`4.0.0.dev8`_ - 2024-11-15 +-------------------------- + +Added +~~~~~ + +* Widgets on person and group pages with detailed coursebook statistics + and including all participations/personal notes. + `4.0.0.dev3`_ - 2024-07-10 -------------------------- @@ -387,3 +396,4 @@ Fixed .. _4.0.0.dev1: https://edugit.org/AlekSIS/Official/AlekSIS-App-Alsijil/-/tags/4.0.0.dev1 .. _4.0.0.dev2: https://edugit.org/AlekSIS/Official/AlekSIS-App-Alsijil/-/tags/4.0.0.dev2 .. _4.0.0.dev3: https://edugit.org/AlekSIS/Official/AlekSIS-App-Alsijil/-/tags/4.0.0.dev3 +.. _4.0.0.dev8: https://edugit.org/AlekSIS/Official/AlekSIS-App-Alsijil/-/tags/4.0.0.dev8 diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue index ac14821941321cd6b792c1ff8bc1bf02b03c6ae0..a9080da16f1f1378a137be40da671a6b48d07342 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue @@ -69,9 +69,9 @@ <DocumentationLoader /> </template> </infinite-scrolling-date-sorted-c-r-u-d-iterator> - <v-scale-transition> - <absence-creation-dialog v-if="pageType === 'absences'" /> - </v-scale-transition> + <absence-creation-dialog + :absence-reasons="absenceReasons" + /> </div> </template> @@ -84,11 +84,11 @@ import CoursebookLoader from "./CoursebookLoader.vue"; import DocumentationModal from "./documentation/DocumentationModal.vue"; import DocumentationAbsencesModal from "./absences/DocumentationAbsencesModal.vue"; import AbsenceCreationDialog from "./absences/AbsenceCreationDialog.vue"; -import { extraMarks } from "../extra_marks/extra_marks.graphql"; +import { extraMarks } from "./queries/extraMarks.graphql"; import DocumentationLoader from "./documentation/DocumentationLoader.vue"; import sendToServerMixin from "./absences/sendToServerMixin"; -import { absenceReasons } from "./absences/absenceReasons.graphql"; -import { subjects } from "aleksis.apps.cursus/components/subject.graphql"; +import { absenceReasons } from "./queries/absenceReasons.graphql"; +import { subjects } from "./queries/subjects.graphql"; export default { name: "Coursebook", diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue index 7a6b4c73f5e3dbe6b6dbfa86df69b96b4a8d74a2..690b07972cd932a8d5eb0fd91609e0a662d829b0 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue @@ -30,6 +30,7 @@ :end-date="endDate" :comment="comment" :absence-reason="absenceReason" + :absence-reasons="absenceReasons" @valid="formValid = $event" @persons="persons = $event" @start-date="startDate = $event" @@ -121,6 +122,12 @@ export default { absenceReason: "", }; }, + props: { + absenceReasons: { + type: Array, + required: true, + }, + }, mounted() { this.addPermissions(["alsijil.view_register_absence_rule"]); this.clearForm(); @@ -154,14 +161,17 @@ export default { reason: this.absenceReason, }, (storedDocumentations, incomingStatuses) => { - const documentation = storedDocumentations.find( - (doc) => doc.id === this.documentation.id, - ); - incomingStatuses.forEach((newStatus) => { + const documentation = storedDocumentations.find( + (doc) => doc.id === newStatus.relatedDocumentation.id, + ); + if (!documentation) { + return; + } const participationStatus = documentation.participations.find( (part) => part.id === newStatus.id, ); + participationStatus.absenceReason = newStatus.absenceReason; participationStatus.isOptimistic = newStatus.isOptimistic; }); diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue index 239c83d6bbd48700773c024967636a82f3d3c4a8..74f07cc4dcc9c24ccd42c0c8345182e5e60c6a9b 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationForm.vue @@ -61,6 +61,7 @@ <absence-reason-group-select :rules="$rules().required.build()" :value="absenceReason" + :custom-absence-reasons="absenceReasons" @input="$emit('absence-reason', $event)" /> </div> @@ -115,6 +116,10 @@ export default { type: String, required: true, }, + absenceReasons: { + type: Array, + required: true, + }, }, computed: { maxStartTime() { diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue index 2c1479f73a0de45c1c687fea455514e3ba5af93a..1fdbd333e2fc21fb44470382e515159e1bc743fe 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue @@ -129,15 +129,18 @@ export default { input: this.markAsAbsentDay.participationIDs, }, (storedDocumentations, incomingStatuses) => { - const documentation = storedDocumentations.find( - (doc) => doc.id === this.documentation.id, - ); - incomingStatuses.forEach((newStatus) => { + const documentation = storedDocumentations.find( + (doc) => doc.id === newStatus.relatedDocumentation.id, + ); + if (!documentation) { + return; + } const participationStatus = documentation.participations.find( (part) => part.id === newStatus.id, ); - participationStatus.baseAbsence = newStatus.baseAbsence; + + participationStatus.absenceReason = newStatus.absenceReason; participationStatus.isOptimistic = newStatus.isOptimistic; }); @@ -351,6 +354,7 @@ export default { /> <h4>{{ $t("alsijil.extra_marks.title_plural") }}</h4> <extra-mark-buttons + :custom-extra-marks="extraMarks" @input="handleMultipleAction('extraMark', $event)" /> <h4>{{ $t("alsijil.personal_notes.tardiness") }}</h4> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceReasons.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceReasons.graphql deleted file mode 100644 index a86f608ec78d1bbc50bf558b5e0650cba5b5e8a7..0000000000000000000000000000000000000000 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/absenceReasons.graphql +++ /dev/null @@ -1,11 +0,0 @@ -query absenceReasons($orderBy: [String], $filters: JSONString) { - items: coursebookAbsenceReasons(orderBy: $orderBy, filters: $filters) { - id - shortName - name - colour - default - canEdit - canDelete - } -} diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/participationStatus.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/absences/participationStatus.graphql index 39eb59528c4f876eb2c75c20f187e5ded6fae2bc..a20cb091a4a3352a8e182330453972e40a539800 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/participationStatus.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/participationStatus.graphql @@ -69,6 +69,15 @@ mutation extendParticipationStatuses($input: [ID]!) { extendParticipationStatuses(input: $input) { items: participations { id + relatedDocumentation { + id + } + absenceReason { + id + name + shortName + colour + } } absences { id diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql index a49b73f149129fa86e131b804e027908c84f65db..6ae9ba9dae7e2f1de386c9b433f73ae2ae9f3a11 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql @@ -111,7 +111,6 @@ query documentationsForCoursebook( oldId canEdit futureNotice - canDelete futureNoticeParticipationStatus canEditParticipationStatus canViewParticipationStatus diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/queries/absenceReasons.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/queries/absenceReasons.graphql new file mode 100644 index 0000000000000000000000000000000000000000..4de4d9cc6b3471a6e025e719730a2f9f8afe28d8 --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/queries/absenceReasons.graphql @@ -0,0 +1,9 @@ +query absenceReasons { + items: coursebookAbsenceReasons { + id + shortName + name + colour + default + } +} diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/queries/extraMarks.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/queries/extraMarks.graphql new file mode 100644 index 0000000000000000000000000000000000000000..2cc007bb8d547d32ad1411bc14ce16cfc96e6dc8 --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/queries/extraMarks.graphql @@ -0,0 +1,10 @@ +query extraMarks { + items: extraMarks { + id + shortName + name + colourFg + colourBg + showInCoursebook + } +} diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/queries/subjects.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/queries/subjects.graphql new file mode 100644 index 0000000000000000000000000000000000000000..7f6de4f2d0871c29147382b3d7e6af573c31bc80 --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/queries/subjects.graphql @@ -0,0 +1,9 @@ +query subjects { + items: subjects { + id + name + shortName + colourFg + colourBg + } +} diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue index f97e7b7f62b4ccee537ccb11a73bfb976c8c48d3..c8bde2e8bed22f4120dec5108f8f2f137bdfefe9 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForGroupTab.vue @@ -68,7 +68,7 @@ name: 'alsijil.coursebook_statistics', params: { personId: item.person.id, - schoolTermId: schoolTerm.id, + mode: MODE.PARTICIPATIONS, }, }" /> @@ -86,8 +86,9 @@ import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip. import ExtraMarkChip from "aleksis.apps.alsijil/components/extra_marks/ExtraMarkChip.vue"; import { statisticsByGroup } from "./statistics.graphql"; -import { absenceReasons } from "../absences/absenceReasons.graphql"; +import { absenceReasons } from "../queries/absenceReasons.graphql"; import { extraMarks } from "../../extra_marks/extra_marks.graphql"; +import { MODE } from "./modes"; export default { name: "StatisticsForGroupTab", @@ -108,6 +109,9 @@ export default { }; }, computed: { + MODE() { + return MODE; + }, headers() { // TODO: i18n return [ diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue index b857a1ba59b7a61263bf2cfc6f2c7ac6e8af102a..546fc8e00701798b801427355328c14d0237578b 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue @@ -15,7 +15,7 @@ name: 'alsijil.coursebook_statistics', params: { personId: person.id, - schoolTermId: schoolTerm.id, + mode: MODE.PARTICIPATIONS, }, }" /> @@ -24,12 +24,22 @@ {{ $t("alsijil.coursebook.statistics.title_plural") }} </v-card-title> - <v-card-text> + <v-card-text + v-if="!$apollo.queries.statistics.loading && statistics == null" + > + <message-box type="error"> + <div>{{ $t("generic_messages.error") }}</div> + <small> + {{ $t("error_code", { errorCode }) }} + </small> + </message-box> + </v-card-text> + <v-card-text v-else> <div class="grid"> <statistics-absences-card style="grid-area: absences" :absence-reasons="statistics.absenceReasons" - :loading="$apollo.loading" + :loading="$apollo.queries.statistics.loading" /> <statistics-tardiness-card style="grid-area: tardinesses" @@ -50,17 +60,21 @@ <script> import personOverviewCardMixin from "aleksis.core/mixins/personOverviewCardMixin.js"; import BaseButton from "aleksis.core/components/generic/buttons/BaseButton.vue"; +import MessageBox from "aleksis.core/components/generic/MessageBox.vue"; import StatisticsAbsencesCard from "./StatisticsAbsencesCard.vue"; import StatisticsTardinessCard from "./StatisticsTardinessCard.vue"; import StatisticsExtraMarksCard from "./StatisticsExtraMarksCard.vue"; import { statisticsByPerson } from "./statistics.graphql"; +import errorCodes from "../../../errorCodes"; +import { MODE } from "./modes"; export default { name: "StatisticsForPersonCard", mixins: [personOverviewCardMixin], components: { BaseButton, + MessageBox, StatisticsAbsencesCard, StatisticsTardinessCard, StatisticsExtraMarksCard, @@ -80,25 +94,23 @@ export default { tardinessCount: 0, extraMarks: [], }, + errorCode: errorCodes.statisticsEmpty, }; }, apollo: { statistics: { query: statisticsByPerson, variables() { - const term = this.schoolTerm ? { term: this.schoolTerm.id } : {}; - return { person: this.person.id, - ...term, }; }, - skip() { - return !this.schoolTerm; - }, }, }, computed: { + MODE() { + return MODE; + }, gridTemplateAreas() { return this.compact ? `"absences extra_marks" "tardinesses tardinesses"` diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue index 7df07c24db4ea90f66268135272a5fe8e634b9e4..c5a8ed51e2d89dbdd79b17e7b839141f2452a9cb 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue @@ -3,11 +3,7 @@ :fallback-url="{ name: 'core.personById', props: { id: personId } }" > <div class="d-flex" style="gap: 4em"> - <!-- TODO: header (close, title, print) --> - <!-- TODO: flex-grow-1 does a little & flex-shrink-1 does nothing --> <div class="flex-grow-1" style="max-width: 100%"> - <!-- school-term-select --> - <school-term-field v-model="schoolTerm" :enable-create="false" /> <!-- documentations for person list --> <c-r-u-d-iterator i18n-key="alsijil.coursebook.statistics" @@ -19,7 +15,8 @@ > <template #additionalActions> <v-btn-toggle - v-model="mode" + :value="mode" + @change="updateMode" mandatory color="secondary" rounded @@ -134,17 +131,20 @@ class="flex-shrink-1" :compact="false" :person="{ id: personId }" - :school-term="{ id: schoolTermId }" /> <v-bottom-sheet v-model="statisticsBottomSheet" v-else> <statistics-for-person-card :compact="false" :person="{ id: personId }" - :school-term="{ id: schoolTermId }" /> </v-bottom-sheet> </div> <template #actions="{ toolbar }"> + <active-school-term-select + v-if="toolbar" + v-model="$root.activeSchoolTerm" + color="secondary" + /> <!-- TODO: add functionality --> <v-btn v-if="toolbar" icon color="primary" disabled> <v-icon>$print</v-icon> @@ -156,7 +156,7 @@ <script> import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip.vue"; -import SchoolTermField from "aleksis.core/components/school_term/SchoolTermField.vue"; +import ActiveSchoolTermSelect from "aleksis.core/components/school_term/ActiveSchoolTermSelect.vue"; import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue"; import FabButton from "aleksis.core/components/generic/buttons/FabButton.vue"; import FullscreenDialogPage from "aleksis.core/components/generic/dialogs/FullscreenDialogPage.vue"; @@ -170,18 +170,14 @@ import { personName, } from "./statistics.graphql"; import ExtraMarkChip from "../../extra_marks/ExtraMarkChip.vue"; - -const MODE = { - PARTICIPATIONS: "PARTICIPATIONS", - PERSONAL_NOTES: "PERSONAL_NOTES", -}; +import { MODE } from "./modes.js"; export default { name: "StatisticsForPersonPage", components: { + ActiveSchoolTermSelect, ExtraMarkChip, AbsenceReasonChip, - SchoolTermField, CRUDIterator, FabButton, FullscreenDialogPage, @@ -190,14 +186,15 @@ export default { StatisticsForPersonCard, }, props: { - // personId & schoolTermId are supplied via the url + // personId is supplied via the url personId: { type: [Number, String], required: true, }, - schoolTermId: { - type: [Number, String], - required: true, + mode: { + type: String, + required: false, + default: MODE.PARTICIPATIONS, }, }, apollo: { @@ -219,7 +216,6 @@ export default { }, data() { return { - mode: MODE.PARTICIPATIONS, statisticsBottomSheet: false, }; }, @@ -227,20 +223,11 @@ export default { gqlQueryArgs() { return { person: this.personId, - term: this.schoolTermId, }; }, MODE() { return MODE; }, - schoolTerm: { - get() { - return this.schoolTermId; - }, - set(value) { - console.log("New SchoolTerm:", value); - }, - }, }, methods: { gqlQuery() { @@ -248,6 +235,19 @@ export default { ? personalNotesForPerson : participationsOfPerson; }, + updateMode(mode = MODE.PARTICIPATIONS) { + if (mode === this.mode) { + return; + } + + this.$router.push({ + name: "alsijil.coursebook_statistics", + params: { + personId: this.personId, + mode: mode, + }, + }); + }, }, }; </script> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js new file mode 100644 index 0000000000000000000000000000000000000000..c345316a23b880f73acddf2271759de63a6e760d --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/modes.js @@ -0,0 +1,4 @@ +export const MODE = { + PARTICIPATIONS: "participations", + PERSONAL_NOTES: "personal_notes", +}; diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql index 68e0a08ca3804bf841fc25c57c99d4e4dc3f66ee..901bcd251829c87783bd432ad90d69a07c2cca65 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql @@ -30,14 +30,14 @@ fragment statistics on StatisticsByPersonType { } } -query statisticsByPerson($person: ID!, $term: ID!) { - statistics: statisticsByPerson(person: $person, term: $term) { +query statisticsByPerson($person: ID!) { + statistics: statisticsByPerson(person: $person) { ...statistics } } -query participationsOfPerson($person: ID!, $term: ID) { - items: participationsOfPerson(person: $person, term: $term) { +query participationsOfPerson($person: ID!) { + items: participationsOfPerson(person: $person) { id absenceReason { id @@ -68,8 +68,8 @@ query participationsOfPerson($person: ID!, $term: ID) { } } -query personalNotesForPerson($person: ID!, $term: ID) { - items: personalNotesForPerson(person: $person, term: $term) { +query personalNotesForPerson($person: ID!) { + items: personalNotesForPerson(person: $person) { id note extraMark { @@ -101,8 +101,8 @@ query personalNotesForPerson($person: ID!, $term: ID) { } } -query statisticsByGroup($group: ID!, $term: ID) { - items: statisticsByGroup(group: $group, term: $term) { +query statisticsByGroup($group: ID!) { + items: statisticsByGroup(group: $group) { # persons { # id # fullName diff --git a/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue b/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue index 919749ac85ed97f937d5b862a6aa37d199529b30..01deef40190efb422376dc7848185e8916400b43 100644 --- a/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue +++ b/aleksis/apps/alsijil/frontend/components/extra_marks/ExtraMarkButtons.vue @@ -13,7 +13,7 @@ export default { query: extraMarks, update: (data) => data.items, skip() { - return this.customExtraMarks > 0; + return this.customExtraMarks.length > 0; }, }, }, diff --git a/aleksis/apps/alsijil/frontend/errorCodes.js b/aleksis/apps/alsijil/frontend/errorCodes.js new file mode 100644 index 0000000000000000000000000000000000000000..27681138a971547448885dfaad4f4b14497b0ded --- /dev/null +++ b/aleksis/apps/alsijil/frontend/errorCodes.js @@ -0,0 +1,17 @@ +/** + * Alsijil Error Codes. + * + * Schema: + * abb. + * a: A|C|D|P|S: + * Component inside Alsijil + * - Absences + * - Coursebook + * - Documentation + * - Personal Notes + * - Statistics + * bb: incrementing number + */ +export default { + statisticsEmpty: "ALSIJIL_S01", +}; diff --git a/aleksis/apps/alsijil/frontend/index.js b/aleksis/apps/alsijil/frontend/index.js index 6e16ad0a1873ad9be17ee842283c3283bd74e8b6..ac475483e76e9538c36ffb55d85f53c6531ec802 100644 --- a/aleksis/apps/alsijil/frontend/index.js +++ b/aleksis/apps/alsijil/frontend/index.js @@ -1,5 +1,6 @@ import { hasPersonValidator } from "aleksis.core/routeValidators"; import { DateTime } from "luxon"; +import { MODE } from "./components/coursebook/statistics/modes"; export const collectionItems = { coreGroupActions: [ @@ -34,7 +35,7 @@ export const collectionItems = { import( "./components/coursebook/statistics/StatisticsForPersonCard.vue" ), - shouldDisplay: (person, currentSchoolTerm) => currentSchoolTerm != null, + shouldDisplay: () => true, colProps: { cols: 12, md: 6, @@ -114,7 +115,7 @@ export default { }, }, { - path: "statistics/:personId/:schoolTermId/", + path: `statistics/:personId/:mode(${Object.values(MODE).join("|")})`, component: () => import( "./components/coursebook/statistics/StatisticsForPersonPage.vue" diff --git a/aleksis/apps/alsijil/managers.py b/aleksis/apps/alsijil/managers.py index 983a29b8d73196278afc3de7b1e25f484e62f02f..b8d17a7aa95c34fd7d87314d37da943844a64771 100644 --- a/aleksis/apps/alsijil/managers.py +++ b/aleksis/apps/alsijil/managers.py @@ -72,18 +72,6 @@ class GroupRoleAssignmentQuerySet(QuerySet): class DocumentationManager(RecurrencePolymorphicManager): """Manager adding specific methods to documentations.""" - def get_queryset(self): - """Ensure often used related data are loaded as well.""" - return ( - super() - .get_queryset() - .select_related( - "course", - "subject", - ) - .prefetch_related("teachers") - ) - class ParticipationStatusManager(RecurrencePolymorphicManager): """Manager adding specific methods to participation statuses.""" diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index efb06db7a0b298c21fa81663a2865a976b65cf77..a3d9c14fafedc02858b025ef221d97b08bfb5b90 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -1,4 +1,4 @@ -from django.db.models import FilteredRelation, Q, QuerySet +from django.db.models import FilteredRelation, Q, QuerySet, Value from django.db.models.aggregates import Count, Sum from django.utils.translation import gettext as _ @@ -34,36 +34,58 @@ Person.add_permission("register_absence_person", _("Can register an absence for def annotate_person_statistics( - persons: QuerySet[Person], participations_filter: Q, personal_notes_filter: Q + persons: QuerySet[Person], + participations_filter: Q, + personal_notes_filter: Q, + *, + ignore_filters: bool = False, ) -> QuerySet[Person]: """Annotate a queryset of persons with class register statistics.""" - persons = persons.annotate( - filtered_participation_statuses=FilteredRelation( - "participations", - condition=(participations_filter), - ), - filtered_personal_notes=FilteredRelation( - "new_personal_notes", - condition=(personal_notes_filter), - ), - ).annotate( - participation_count=Count( - "filtered_participation_statuses", - filter=Q(filtered_participation_statuses__absence_reason__isnull=True), - distinct=True, - ), - absence_count=Count( - "filtered_participation_statuses", - filter=Q(filtered_participation_statuses__absence_reason__count_as_absent=True), - distinct=True, - ), - tardiness_sum=Sum("filtered_participation_statuses__tardiness", distinct=True), - tardiness_count=Count( - "filtered_participation_statuses", - filter=Q(filtered_participation_statuses__tardiness__gt=0), - distinct=True, - ), - ) + + if ignore_filters: + persons = persons.annotate( + absence_count=Value(0), + filtered_participation_statuses=FilteredRelation( + "participations", + condition=Q(pk=None), + ), + filtered_personal_notes=FilteredRelation( + "new_personal_notes", + condition=Q(pk=None), + ), + participation_count=Value(0), + tardiness_count=Value(0), + tardiness_sum=Value(0), + ) + else: + persons = persons.annotate( + filtered_participation_statuses=FilteredRelation( + "participations", + condition=(participations_filter), + ), + filtered_personal_notes=FilteredRelation( + "new_personal_notes", + condition=(personal_notes_filter), + ), + ).annotate( + participation_count=Count( + "filtered_participation_statuses", + filter=Q(filtered_participation_statuses__absence_reason__isnull=True), + distinct=True, + ), + absence_count=Count( + "filtered_participation_statuses", + filter=Q(filtered_participation_statuses__absence_reason__count_as_absent=True), + distinct=True, + ), + tardiness_sum=Sum("filtered_participation_statuses__tardiness", distinct=True), + tardiness_count=Count( + "filtered_participation_statuses", + filter=Q(filtered_participation_statuses__tardiness__gt=0), + distinct=True, + ), + ) + persons = persons.order_by("last_name", "first_name") for absence_reason in AbsenceReason.objects.all(): @@ -102,6 +124,7 @@ def annotate_person_statistics_from_documentations( persons, Q(participations__related_documentation__in=docs), Q(new_personal_notes__documentation__in=docs), + ignore_filters=len(docs) == 0, ) diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index e622c5c3d47cadfe556e6a63387919b122661153..7e10a80cb0f83f065d94198658d13c9bc3acfe30 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -142,7 +142,6 @@ class Documentation(CalendarEvent): events: list, incomplete: Optional[bool] = False, absences_exist: Optional[bool] = False, - request: Optional[HttpRequest] = None, ) -> tuple: """Get all the documentations for the events. Create dummy documentations if none exist. @@ -152,11 +151,22 @@ class Documentation(CalendarEvent): dummies = [] # Prefetch existing documentations to speed things up - existing_documentations = Documentation.objects.filter( - datetime_start__lte=datetime_end, - datetime_end__gte=datetime_start, - amends__in=[e["REFERENCE_OBJECT"] for e in events], - ).prefetch_related("participations") + existing_documentations = ( + Documentation.objects.filter( + datetime_start__lte=datetime_end, + datetime_end__gte=datetime_start, + amends__in=[e["REFERENCE_OBJECT"] for e in events], + ) + .prefetch_related( + "participations", + "participations__person", + "participations__absence_reason", + "teachers", + "personal_notes", + "personal_notes__extra_mark", + ) + .select_related("course", "subject") + ) for event in events: if incomplete and event["STATUS"] == "CANCELLED": @@ -182,6 +192,7 @@ class Documentation(CalendarEvent): ) ): continue + doc._amends_prefetched = event_reference_obj docs.append(doc) elif not absences_exist: if event_reference_obj.amends: @@ -217,7 +228,6 @@ class Documentation(CalendarEvent): start: datetime, end: datetime, incomplete: Optional[bool] = False, - request: Optional[HttpRequest] = None, ) -> tuple: """Get all the documentations for the person from start to end datetime. Create dummy documentations if none exist. @@ -236,7 +246,7 @@ class Documentation(CalendarEvent): with_reference_object=True, ) - return Documentation.get_documentations_for_events(start, end, events, incomplete, request) + return Documentation.get_documentations_for_events(start, end, events, incomplete) @classmethod def parse_dummy( @@ -295,15 +305,13 @@ class Documentation(CalendarEvent): lesson_event.teachers, ) - obj = cls.objects.create( + obj, __ = cls.objects.update_or_create( datetime_start=datetime_start, datetime_end=datetime_end, amends=lesson_event, - course=course, - subject=subject, + defaults=dict(subject=subject, course=course), ) obj.teachers.set(teachers.all()) - obj.save() # Create Participation Statuses obj.touch() @@ -311,7 +319,7 @@ class Documentation(CalendarEvent): return obj @classmethod - def get_or_create_by_id(cls, _id: str | int, user): + def get_or_create_by_id(cls, _id: str, user): if _id.startswith("DUMMY"): return cls.create_from_lesson_event( user, @@ -467,6 +475,29 @@ class ParticipationStatus(CalendarEvent): """Return the title of the calendar event.""" return "" + @classmethod + def set_from_kolego_by_datetimes( + cls, kolego_absence: KolegoAbsence, person: Person, start: datetime, end: datetime + ) -> list["ParticipationStatus"]: + participation_statuses = [] + + events = cls.get_single_events( + start, + end, + None, + {"person": person}, + with_reference_object=True, + ) + + for event in events: + participation_status = event["REFERENCE_OBJECT"] + participation_status.absence_reason = kolego_absence.reason + participation_status.base_absence = kolego_absence + participation_status.save() + participation_statuses.append(participation_status) + + return participation_statuses + def fill_from_kolego(self, kolego_absence: KolegoAbsence): """Take over data from a Kolego absence.""" self.base_absence = kolego_absence diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py index 740aa1ff271e9e4af374f89c7b96c0e57c06a842..604cda6cd229533086533d855563a892f440d572 100644 --- a/aleksis/apps/alsijil/schema/__init__.py +++ b/aleksis/apps/alsijil/schema/__init__.py @@ -3,20 +3,26 @@ from datetime import datetime from django.db.models import BooleanField, ExpressionWrapper, Q import graphene +import graphene_django_optimizer from aleksis.apps.chronos.models import LessonEvent from aleksis.apps.cursus.models import Course from aleksis.apps.cursus.schema import CourseType from aleksis.apps.kolego.models import AbsenceReason from aleksis.apps.kolego.schema.absence import AbsenceReasonType -from aleksis.core.models import Group, Person, SchoolTerm +from aleksis.core.models import Group, Person from aleksis.core.schema.base import FilterOrderList from aleksis.core.schema.group import GroupType from aleksis.core.schema.person import PersonType -from aleksis.core.util.core_helpers import get_site_preferences, has_person +from aleksis.core.util.core_helpers import ( + filter_active_school_term, + get_active_school_term, + get_site_preferences, + has_person, +) from ..model_extensions import annotate_person_statistics_for_school_term -from ..models import Documentation, NewPersonalNote, ParticipationStatus +from ..models import Documentation, ExtraMark, NewPersonalNote, ParticipationStatus from .absences import ( AbsencesForPersonsCreateMutation, ) @@ -47,7 +53,6 @@ from .statistics import StatisticsByPersonType class Query(graphene.ObjectType): - documentations = FilterOrderList(DocumentationType) documentations_by_course_id = FilterOrderList( DocumentationType, course_id=graphene.ID(required=True) ) @@ -80,22 +85,18 @@ class Query(graphene.ObjectType): statistics_by_person = graphene.Field( StatisticsByPersonType, person=graphene.ID(required=True), - term=graphene.ID(required=True), ) participations_of_person = graphene.List( ParticipationStatusType, person=graphene.ID(required=True), - term=graphene.ID(required=False), ) personal_notes_for_person = graphene.List( PersonalNoteType, person=graphene.ID(required=True), - term=graphene.ID(required=False), ) statistics_by_group = graphene.List( StatisticsByPersonType, group=graphene.ID(required=True), - term=graphene.ID(required=False), ) def resolve_documentations_by_course_id(root, info, course_id, **kwargs): @@ -108,7 +109,7 @@ class Query(graphene.ObjectType): ) ) ) - return documentations + return graphene_django_optimizer.query(documentations, info) def resolve_documentations_for_coursebook( root, @@ -156,6 +157,10 @@ class Query(graphene.ObjectType): } ) + school_term = get_active_school_term(info.context) + date_start = date_start if date_start > school_term.date_start else school_term.date_start + date_end = date_end if date_end < school_term.date_end else school_term.date_end + events = LessonEvent.get_single_events( datetime.combine(date_start, datetime.min.time()), datetime.combine(date_end, datetime.max.time()), @@ -171,7 +176,6 @@ class Query(graphene.ObjectType): events, incomplete, absences_exist, - info.context, ) return docs + dummies @@ -186,8 +190,10 @@ class Query(graphene.ObjectType): else: return [] + school_term = get_active_school_term(info.context) + return ( - Group.objects.for_current_school_term_or_all() + Group.objects.for_school_term(school_term) .filter( pk__in=Group.objects.filter(members=person) .values_list("id", flat=True) @@ -215,6 +221,9 @@ class Query(graphene.ObjectType): person = info.context.user.person else: return [] + + school_term = get_active_school_term(info.context) + return Course.objects.filter( pk__in=( Course.objects.filter(teachers=person) @@ -227,20 +236,22 @@ class Query(graphene.ObjectType): ) ) ) - ).filter(groups__in=Group.objects.for_current_school_term_or_all()) + ).filter(groups__in=Group.objects.for_school_term(school_term)) @staticmethod def resolve_absence_creation_persons(root, info, **kwargs): if not info.context.user.has_perm("alsijil.register_absence"): group_types = get_site_preferences()["alsijil__group_types_register_absence"] + school_term = get_active_school_term(info.context) if group_types: return Person.objects.filter( - member_of__in=Group.objects.filter( + member_of__in=Group.objects.for_school_term(school_term).filter( owners=info.context.user.person, group_type__in=group_types ) ) else: - return Person.objects.filter(member_of__owners=info.context.user.person) + qs = Person.objects.filter(member_of__owners=info.context.user.person) + return filter_active_school_term(info.context, qs, "member_of__school_term") return Person.objects.all() @staticmethod @@ -259,13 +270,18 @@ class Query(graphene.ObjectType): person, start, end, - info.context, ) lessons_for_person.append(LessonsForPersonType(id=person, lessons=docs + dummies)) return lessons_for_person + @staticmethod + def resolve_extra_marks(root, info, **kwargs): + if info.context.user.has_perm("alsijil.fetch_extramarks_rule"): + return ExtraMark.objects.all() + raise [] + @staticmethod def resolve_coursebook_absence_reasons(root, info, **kwargs): if not info.context.user.has_perm("kolego.fetch_absencereasons_rule"): @@ -273,53 +289,62 @@ class Query(graphene.ObjectType): return AbsenceReason.objects.filter(tags__short_name="class_register") @staticmethod - def resolve_statistics_by_person(root, info, person, term): + def resolve_statistics_by_person(root, info, person): person = Person.objects.get(pk=person) if not info.context.user.has_perm("alsijil.view_person_statistics_rule", person): return None - school_term = SchoolTerm.objects.get(id=term) - return annotate_person_statistics_for_school_term( - Person.objects.filter(id=person.id), school_term - ).first() + school_term = get_active_school_term(info.context) + return graphene_django_optimizer.query( + annotate_person_statistics_for_school_term( + Person.objects.filter(id=person.id), school_term + ).first(), + info, + ) @staticmethod - def resolve_participations_of_person(root, info, person, term=None): + def resolve_participations_of_person(root, info, person): person = Person.objects.get(pk=person) if not info.context.user.has_perm("alsijil.view_person_statistics_rule", person): return [] - school_term = SchoolTerm.objects.get(id=term) - return ParticipationStatus.objects.filter( - person=person, - absence_reason__isnull=False, - datetime_start__date__gte=school_term.date_start, - datetime_end__date__lte=school_term.date_end, - ).order_by("-related_documentation__datetime_start") + school_term = get_active_school_term(info.context) + return graphene_django_optimizer.query( + ParticipationStatus.objects.filter( + person=person, + absence_reason__isnull=False, + datetime_start__date__gte=school_term.date_start, + datetime_end__date__lte=school_term.date_end, + ).order_by("-related_documentation__datetime_start"), + info, + ) @staticmethod - def resolve_personal_notes_for_person(root, info, person, term=None): + def resolve_personal_notes_for_person(root, info, person): person = Person.objects.get(pk=person) if not info.context.user.has_perm("alsijil.view_person_statistics_rule", person): return [] - school_term = SchoolTerm.objects.get(id=term) - return NewPersonalNote.objects.filter( - person=person, - documentation__in=Documentation.objects.filter( - datetime_start__date__gte=school_term.date_start, - datetime_end__date__lte=school_term.date_end, - ), - ).order_by("-documentation__datetime_start") + school_term = get_active_school_term(info.context) + return graphene_django_optimizer.query( + NewPersonalNote.objects.filter( + person=person, + documentation__in=Documentation.objects.filter( + datetime_start__date__gte=school_term.date_start, + datetime_end__date__lte=school_term.date_end, + ), + ).order_by("-documentation__datetime_start"), + info, + ) @staticmethod - def resolve_statistics_by_group(root, info, group, term=None): + def resolve_statistics_by_group(root, info, group): group = Group.objects.get(pk=group) if not info.context.user.has_perm("alsijil.view_group_statistics_rule", group): return [] - school_term = ( - SchoolTerm.objects.get(id=term) if term is not None else SchoolTerm.get_current() - ) + school_term = get_active_school_term(info.context) members = group.members.all() - return annotate_person_statistics_for_school_term(members, school_term, group=group) + return graphene_django_optimizer.query( + annotate_person_statistics_for_school_term(members, school_term, group=group), info + ) class Mutation(graphene.ObjectType): diff --git a/aleksis/apps/alsijil/schema/absences.py b/aleksis/apps/alsijil/schema/absences.py index ab006e1d875e053e061d04c9c800ae0c3ef15f2e..28eca0172e7caf203214d6db27eee5c31d6c0b09 100644 --- a/aleksis/apps/alsijil/schema/absences.py +++ b/aleksis/apps/alsijil/schema/absences.py @@ -2,7 +2,6 @@ import datetime from typing import List from django.core.exceptions import PermissionDenied -from django.db.models import Q import graphene @@ -43,41 +42,18 @@ class AbsencesForPersonsCreateMutation(graphene.Mutation): if not info.context.user.has_perm("alsijil.register_absence_rule", person): raise PermissionDenied() - # Check if there is an existing absence with overlapping datetime - absences = Absence.objects.filter( - Q(datetime_start__lte=start) | Q(date_start__lte=start.date()), - Q(datetime_end__gte=end) | Q(date_end__gte=end.date()), + kolego_absence = Absence.get_for_person_by_datetimes( + datetime_start=start, + datetime_end=end, reason_id=reason, person=person, + defaults={"comment": comment}, ) - if len(absences) > 0: - kolego_absence = absences.first() - else: - # Check for same times and create otherwise - kolego_absence, __ = Absence.objects.get_or_create( - datetime_start=start, - datetime_end=end, - reason_id=reason, - person=person, - defaults={"comment": comment}, - ) - - events = ParticipationStatus.get_single_events( - start, - end, - None, - {"person": person}, - with_reference_object=True, + participation_statuses += ParticipationStatus.set_from_kolego_by_datetimes( + kolego_absence=kolego_absence, person=person, start=start, end=end ) - for event in events: - participation_status = event["REFERENCE_OBJECT"] - participation_status.absence_reason_id = reason - participation_status.base_absence = kolego_absence - participation_status.save() - participation_statuses.append(participation_status) - return AbsencesForPersonsCreateMutation( ok=True, participation_statuses=participation_statuses ) diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py index b833de336b2a2add64ac5f0e0cf6dd7fdd8ae0c5..6b0c3e1ffb0c7a328eadb159c9407c5f8a2d74fa 100644 --- a/aleksis/apps/alsijil/schema/documentation.py +++ b/aleksis/apps/alsijil/schema/documentation.py @@ -1,6 +1,7 @@ from django.core.exceptions import PermissionDenied import graphene +from graphene_django import bypass_get_queryset from graphene_django.types import DjangoObjectType from reversion import create_revision, set_comment, set_user @@ -32,7 +33,6 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp fields = ( "id", "course", - "amends", "subject", "topic", "homework", @@ -62,6 +62,14 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp old_id = graphene.ID(required=False) @staticmethod + @bypass_get_queryset + def resolve_amends(root: Documentation, info, **kwargs): + if hasattr(root, "_amends_prefetched"): + return root._amends_prefetched + return root.amends + + @staticmethod + @bypass_get_queryset def resolve_teachers(root: Documentation, info, **kwargs): if not str(root.pk).startswith("DUMMY") and hasattr(root, "teachers"): return root.teachers @@ -100,6 +108,7 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp ) @staticmethod + @bypass_get_queryset def resolve_participations(root: Documentation, info, **kwargs): # A dummy documentation will not have any participations if str(root.pk).startswith("DUMMY") or not hasattr(root, "participations"): @@ -112,6 +121,11 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp p for p in root.participations.all() if p.person == info.context.user.person ] return [] + + # Annotate participations with prefetched documentation data for personal notes + for participation in root.participations.all(): + participation._prefetched_documentation = root + return root.participations.all() diff --git a/aleksis/apps/alsijil/schema/extra_marks.py b/aleksis/apps/alsijil/schema/extra_marks.py index 2b1e3723d2c559e70ddd6793f5f2a89d9c1e6abe..be062b7ed3248a6836c45ee0d4d75616ed65fa54 100644 --- a/aleksis/apps/alsijil/schema/extra_marks.py +++ b/aleksis/apps/alsijil/schema/extra_marks.py @@ -23,12 +23,6 @@ class ExtraMarkType( model = ExtraMark fields = ("id", "short_name", "name", "colour_fg", "colour_bg", "show_in_coursebook") - @classmethod - def get_queryset(cls, queryset, info): - if info.context.user.has_perm("alsijil.fetch_extramarks_rule"): - return queryset - raise PermissionDenied() - class ExtraMarkBatchCreateMutation(BaseBatchCreateMutation): class Meta: diff --git a/aleksis/apps/alsijil/schema/participation_status.py b/aleksis/apps/alsijil/schema/participation_status.py index 22e5820594994b2d1bb4c81ae4f5a83f615bea0b..2f6a830e2e292edd79c8cd0fce30fb14ce0f536b 100644 --- a/aleksis/apps/alsijil/schema/participation_status.py +++ b/aleksis/apps/alsijil/schema/participation_status.py @@ -1,6 +1,7 @@ import datetime from django.core.exceptions import PermissionDenied +from django.utils.formats import date_format from django.utils.translation import gettext_lazy as _ import graphene @@ -41,6 +42,12 @@ class ParticipationStatusType( @staticmethod def resolve_notes_with_extra_mark(root: ParticipationStatus, info, **kwargs): + if hasattr(root, "_prefetched_documentation"): + return [ + p + for p in root._prefetched_documentation.personal_notes.all() + if p.person_id == root.person_id and p.extra_mark + ] return NewPersonalNote.objects.filter( person=root.person, documentation=root.related_documentation, @@ -49,6 +56,12 @@ class ParticipationStatusType( @staticmethod def resolve_notes_with_note(root: ParticipationStatus, info, **kwargs): + if hasattr(root, "_prefetched_documentation"): + return [ + p + for p in root._prefetched_documentation.personal_notes.all() + if p.person_id == root.person_id and p.note + ] return NewPersonalNote.objects.filter( person=root.person, documentation=root.related_documentation, @@ -93,60 +106,57 @@ class ExtendParticipationStatusToAbsenceBatchMutation(graphene.Mutation): if participation.date_end: end_date = participation.date_end else: - end_date = ParticipationStatus.value_end_datetime(participation).date() + end_date = participation.datetime_end.date() end_datetime = datetime.datetime.combine( end_date, datetime.time.max, participation.timezone ) - if participation.base_absence: - # Update the base absence to increase length if needed - absence = participation.base_absence - - if absence.date_end: - if absence.date_end < end_date: - absence.date_end = end_date - absence.save() - - return participation, absence - - # Absence uses a datetime - if absence.datetime_end.astimezone(absence.timezone) < end_datetime: - # The end date ends after the previous absence end - absence.datetime_end = end_datetime - absence.save() - - return participation, absence + data = dict( + reason=participation.absence_reason if participation.absence_reason else None, + person=participation.person, + ) + if participation.date_start: + data["date_start"] = participation.date_start + data["date_end"] = end_date + start_datetime = datetime.datetime.combine( + participation.date_start, datetime.time.min, participation.timezone + ) else: - # No base absence, simply create one if absence reason is given - data = dict( - reason_id=participation.absence_reason.id if participation.absence_reason else None, - person=participation.person, + data["datetime_start"] = participation.datetime_start + data["datetime_end"] = end_datetime + start_datetime = participation.datetime_start + + defaults = dict( + comment=_("Extended by {full_name} on {datetime}").format( + full_name=info.context.user.person.full_name, + datetime=date_format(participation.date_start or participation.datetime_start), ) + ) - if participation.date_start: - data["date_start"] = participation.date_start - data["date_end"] = end_date - else: - data["datetime_start"] = ParticipationStatus.value_start_datetime(participation) - data["datetime_end"] = end_datetime - - absence, __ = Absence.objects.get_or_create(**data) + absence = Absence.get_for_person_by_datetimes(**data, defaults=defaults) - participation.base_absence = absence - participation.save() + participations = ParticipationStatus.set_from_kolego_by_datetimes( + kolego_absence=absence, + person=participation.person, + start=start_datetime, + end=end_datetime, + ) - return participation, absence + return participations, absence @classmethod def mutate(cls, root, info, input): # noqa with create_revision(): set_user(info.context.user) set_comment(_("Extended absence reason from coursebook.")) - participations, absences = zip( - *[cls.create_absence(info, participation_id) for participation_id in input] - ) + participations = [] + absences = [] + for participation_id in input: + p, a = cls.create_absence(info, participation_id) + participations += p + absences.append(a) return ExtendParticipationStatusToAbsenceBatchMutation( participations=participations, absences=absences diff --git a/aleksis/apps/alsijil/schema/personal_note.py b/aleksis/apps/alsijil/schema/personal_note.py index e37e5f0580add9255c460a7089d1e877247273eb..7a9e20edadc426c7bf6d846b002c8d04ac9d7967 100644 --- a/aleksis/apps/alsijil/schema/personal_note.py +++ b/aleksis/apps/alsijil/schema/personal_note.py @@ -23,7 +23,6 @@ class PersonalNoteType( "id", "note", "extra_mark", - # TODO: permissions? "documentation", ) diff --git a/pyproject.toml b/pyproject.toml index 2f82bc3ed9291cd3d7a9b04d725ff6b67216942b..202de98868fb53f6d7ea481783aa616f00646aeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "AlekSIS-App-Alsijil" -version = "4.0.0.dev8" +version = "4.0.0.dev9" packages = [ { include = "aleksis" } ]