diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/DocumentationAbsences.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/DocumentationAbsences.vue index ef97f2ddb73cd0eb5714e1a8818e97fb54fde67a..8dbe68847629ba81e0bca1da804c516de4562bf4 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/DocumentationAbsences.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/DocumentationAbsences.vue @@ -15,6 +15,7 @@ <lesson-notes class="span-2" v-bind="documentationPartProps" /> <participation-list + v-if="documentation.canEditParticipationStatus" :include-present="false" class="participation-list" v-bind="documentationPartProps" diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue index 0fb5b638c873bd3f20b16ea62ab438693c9f6c8a..1e9988be2a02b91eab0eceb0a11156ce31a39ce1 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue @@ -14,6 +14,7 @@ import { } from "./participationStatus.graphql"; import SlideIterator from "aleksis.core/components/generic/SlideIterator.vue"; import PersonalNotes from "../personal_notes/PersonalNotes.vue"; +import PersonalNoteChip from "../personal_notes/PersonalNoteChip.vue"; import ExtraMarkChip from "../../extra_marks/ExtraMarkChip.vue"; import TardinessChip from "./TardinessChip.vue"; import TardinessField from "./TardinessField.vue"; @@ -31,6 +32,7 @@ export default { AbsenceReasonGroupSelect, AbsenceReasonButtons, PersonalNotes, + PersonalNoteChip, LessonInformation, MessageBox, MobileFullscreenDialog, @@ -255,18 +257,12 @@ export default { small :absence-reason="item.absenceReason" /> - <v-chip + <personal-note-chip v-for="note in item.notesWithNote" :key="'text-note-note-overview-' + note.id" + :note="note" small - > - <v-avatar left> - <v-icon small>mdi-note-outline</v-icon> - </v-avatar> - <span class="text-truncate" style="max-width: 30ch"> - {{ note.note }} - </span> - </v-chip> + /> <extra-mark-chip v-for="note in item.notesWithExtraMark" :key="'extra-mark-note-overview-' + note.id" diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue index f587b636aab2a5f63cd9386fb22f63f5a7981ea4..963367cbddb77174251a3b2deb21f65102e3c960 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsTrigger.vue @@ -57,6 +57,17 @@ export default { ); }, }, + computed: { + showLabel() { + return !!this.labelKey || !this.canOpenParticipation; + }, + innerLabelKey() { + if (this.documentation.futureNoticeParticipationStatus) { + return "alsijil.coursebook.notes.future"; + } + return this.labelKey; + }, + }, }; </script> @@ -77,9 +88,9 @@ export default { v-on="on" @click="touchDocumentation" > - <v-icon :left="!!labelKey">mdi-account-edit-outline</v-icon> - <template v-if="labelKey"> - {{ $t(labelKey) }} + <v-icon :left="showLabel">mdi-account-edit-outline</v-icon> + <template v-if="showLabel"> + {{ $t(innerLabelKey) }} </template> </v-chip> </template> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql index 2ea83d8e6a8687a16d871979592a7d231d363444..a49b73f149129fa86e131b804e027908c84f65db 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/coursebook.graphql @@ -112,6 +112,9 @@ query documentationsForCoursebook( canEdit futureNotice canDelete + futureNoticeParticipationStatus + canEditParticipationStatus + canViewParticipationStatus } } diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue index 15f4b371783f02a3efc95f7099e52e27430f0331..1a70d655e00a3bc6c85f002d417497282387af7a 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/Documentation.vue @@ -100,6 +100,6 @@ export default { gap: 1em; } .vertical { - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); } </style> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue index b91abacfdaa8557c1c9349ef25dc52c669cb4f3b..a412d39deed9232c8668e60716a3825096394347 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/documentation/LessonNotes.vue @@ -1,96 +1,238 @@ <script setup> import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip.vue"; import ExtraMarkChip from "../../extra_marks/ExtraMarkChip.vue"; +import ExtraMarksNote from "../personal_notes/ExtraMarksNote.vue"; import TardinessChip from "../absences/TardinessChip.vue"; +import PersonalNoteChip from "../personal_notes/PersonalNoteChip.vue"; +import TextNoteCard from "../personal_notes/TextNoteCard.vue"; </script> <template> - <div - class="d-flex align-center justify-space-between justify-md-end flex-wrap gap" - > - <v-chip dense color="success" outlined v-if="total > 0"> - {{ $t("alsijil.coursebook.present_number", { present, total }) }} - </v-chip> - <absence-reason-chip - v-for="[reasonId, participations] in Object.entries(absences)" - :key="'reason-' + reasonId" - :absence-reason="participations[0].absenceReason" - dense + <div> + <div + class="d-flex align-center justify-space-between justify-md-end flex-wrap gap" + v-if="compact || documentation.canViewParticipationStatus" > - <template #append> - <span - >: - <span> - {{ - participations - .slice(0, 5) - .map((participation) => participation.person.firstName) - .join(", ") - }} - </span> - <span v-if="participations.length > 5"> - <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> - +{{ participations.length - 5 }} - <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> - </span> - </span> + <v-chip + dense + color="success" + outlined + v-if="total > 0 && documentation.canViewParticipationStatus" + > + {{ + $t("alsijil.coursebook.participations.present_number", { + present, + total, + }) + }} + </v-chip> + <v-chip + dense + color="success" + outlined + @click="$emit('open')" + v-bind="dialogActivator.attrs" + v-on="dialogActivator.on" + v-else-if=" + total == 1 && + present == 1 && + !documentation.canViewParticipationStatus + " + > + {{ $t("alsijil.coursebook.participations.present") }} + </v-chip> + + <template v-if="documentation.canViewParticipationStatus"> + <absence-reason-chip + v-for="[reasonId, participations] in Object.entries(absences)" + :key="'reason-' + reasonId" + :absence-reason="participations[0].absenceReason" + dense + > + <template #append> + <span + >: + <span> + {{ + participations + .slice(0, 5) + .map((participation) => participation.person.firstName) + .join(", ") + }} + </span> + <span v-if="participations.length > 5"> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> + +{{ participations.length - 5 }} + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> + </span> + </span> + </template> + </absence-reason-chip> + </template> + <template v-else> + <absence-reason-chip + v-for="[reasonId, participations] in Object.entries(absences)" + :key="'reason-' + reasonId" + :absence-reason="participations[0].absenceReason" + dense + @click="$emit('open')" + v-bind="dialogActivator.attrs" + v-on="dialogActivator.on" + /> </template> - </absence-reason-chip> - <extra-mark-chip - v-for="[markId, [mark, ...participations]] in Object.entries( - extraMarkChips, - )" - :key="'extra-mark-' + markId" - :extra-mark="mark" - dense - > - <template #append> - <span - >: - <span> - {{ - participations - .slice(0, 5) - .map((participation) => participation.person.firstName) - .join(", ") - }} - </span> - <span v-if="participations.length > 5"> - <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> - +{{ participations.length - 5 }} - <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> - </span> - </span> + <template v-if="documentation.canViewParticipationStatus"> + <extra-mark-chip + v-for="[markId, [mark, ...participations]] in Object.entries( + extraMarkChips, + )" + :key="'extra-mark-' + markId" + :extra-mark="mark" + dense + > + <template #append> + <span + >: + <span> + {{ + participations + .slice(0, 5) + .map((participation) => participation.person.firstName) + .join(", ") + }} + </span> + <span v-if="participations.length > 5"> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> + +{{ participations.length - 5 }} + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> + </span> + </span> + </template> + </extra-mark-chip> + </template> + <template v-else> + <extra-mark-chip + v-for="[markId, [mark, ...participations]] in Object.entries( + extraMarkChips, + )" + :key="'extra-mark-' + markId" + :extra-mark="mark" + dense + @click="$emit('open')" + v-bind="dialogActivator.attrs" + v-on="dialogActivator.on" + /> </template> - </extra-mark-chip> - <tardiness-chip v-if="tardyParticipations.length > 0"> - {{ $t("alsijil.personal_notes.late") }} + <template v-if="documentation.canViewParticipationStatus"> + <tardiness-chip v-if="tardyParticipations.length > 0"> + <template #default> + {{ $t("alsijil.personal_notes.late") }} + </template> - <template #append> - <span - >: - {{ - tardyParticipations - .slice(0, 5) - .map((participation) => participation.person.firstName) - .join(", ") - }} + <template #append> + <span + >: + {{ + tardyParticipations + .slice(0, 5) + .map((participation) => participation.person.firstName) + .join(", ") + }} + + <span v-if="tardyParticipations.length > 5"> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> + +{{ tardyParticipations.length - 5 }} + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> + </span> + </span> + </template> + </tardiness-chip> + </template> + <template v-else> + <tardiness-chip + v-if="tardyParticipations.length > 0" + :tardiness=" + tardyParticipations.length == 1 + ? tardyParticipations[0].tardiness + : undefined + " + @click="$emit('open')" + v-bind="dialogActivator.attrs" + v-on="dialogActivator.on" + /> + </template> - <span v-if="tardyParticipations.length > 5"> - <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> - +{{ tardyParticipations.length - 5 }} - <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> - </span> - </span> + <template v-if="!documentation.canViewParticipationStatus && total == 1"> + <personal-note-chip + v-for="note in documentation?.participations[0]?.notesWithNote" + :key="'text-note-note-' + note.id" + :note="note" + @click="$emit('open')" + v-bind="dialogActivator.attrs" + v-on="dialogActivator.on" + /> </template> - </tardiness-chip> - <manage-students-trigger - :label-key="total == 0 ? 'alsijil.coursebook.notes.show_list' : ''" - v-bind="documentationPartProps" - /> + <manage-students-trigger + v-if="documentation.canEditParticipationStatus" + :label-key="manageStudentsLabelKey" + v-bind="documentationPartProps" + /> + </div> + + <!-- not compact --> + <div class="main-body" v-else> + <template + v-if=" + tardyParticipations.length > 0 || Object.entries(absences).length > 0 + " + > + <v-divider /> + <div + class="d-flex align-center justify-space-between justify-md-end flex-wrap gap" + > + <tardiness-chip + v-if="tardyParticipations.length > 0" + :tardiness=" + tardyParticipations.length == 1 + ? tardyParticipations[0].tardiness + : undefined + " + /> + <absence-reason-chip + v-for="[reasonId, participations] in Object.entries(absences)" + :key="'reason-' + reasonId" + :absence-reason="participations[0].absenceReason" + dense + /> + </div> + </template> + <template v-if="total == 1"> + <v-divider /> + <extra-marks-note + v-bind="documentationPartProps" + :participation="documentation?.participations[0]" + :value="documentation?.participations[0].notesWithExtraMark" + :disabled="true" + /> + </template> + <template + v-if=" + total == 1 && + documentation?.participations[0]?.notesWithNote.length > 0 + " + > + <v-divider /> + <div> + <text-note-card + v-for="note in documentation?.participations[0]?.notesWithNote" + :key="'text-note-note-' + note.id" + :note="note" + /> + </div> + </template> + </div> </div> </template> @@ -161,6 +303,12 @@ export default { tardyParticipations() { return this.documentation.participations.filter((p) => p.tardiness); }, + manageStudentsLabelKey() { + if (this.total == 0) { + return "alsijil.coursebook.notes.show_list"; + } + return ""; + }, }, }; </script> @@ -169,4 +317,9 @@ export default { .gap { gap: 0.25em; } +.main-body { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 1em; +} </style> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarkNoteCheckbox.vue b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarkNoteCheckbox.vue index 1f31f9ee1cf6c557ee2fe7b86134793629f9063f..e6985a1f854b9915d6effb282674b0ca9bd478f0 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarkNoteCheckbox.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarkNoteCheckbox.vue @@ -88,7 +88,7 @@ export default { :label="value.name" :value="value.id" v-model="model" - :disabled="loading" + :disabled="$attrs?.disabled || loading" :true-value="true" :false-value="false" > diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarksNote.vue b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarksNote.vue index fd5f2c6d20597375e3f1e126cb7b78c7835adbbe..f6bed5bb8f03e7442aebad04ec01346eee801fbd 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarksNote.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/ExtraMarksNote.vue @@ -20,7 +20,7 @@ export default { <extra-mark-note-checkbox v-for="extraMark in extraMarks" :key="'checkbox-extramark-' + extraMark.id" - v-bind="personalNoteRelatedProps" + v-bind="{ ...personalNoteRelatedProps, ...$attrs }" :value="extraMark" :personal-note="value.find((pn) => pn.extraMark.id === extraMark.id)" /> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/PersonalNoteChip.vue b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/PersonalNoteChip.vue new file mode 100644 index 0000000000000000000000000000000000000000..5d7326894ecbd367b83ac08b385732d17e2a5310 --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/PersonalNoteChip.vue @@ -0,0 +1,45 @@ +<script> +export default { + name: "PersonalNoteChip", + props: { + note: { + type: Object, + required: true, + }, + loading: { + type: Boolean, + required: false, + default: false, + }, + }, + extends: "v-chip", +}; +</script> + +<template> + <v-tooltip bottom> + <template #activator="{ on, attrs }"> + <v-chip + dense + outlined + v-bind="{ ...$attrs, ...attrs }" + v-on="{ ...$listeners, ...on }" + > + <v-avatar left> + <v-icon small>mdi-note-outline</v-icon> + </v-avatar> + <slot name="prepend" /> + <slot> + <span class="text-truncate" style="max-width: 30ch"> + {{ note.note }} + </span> + </slot> + <slot name="append" /> + <v-avatar right v-if="loading"> + <v-progress-circular indeterminate :size="16" :width="2" /> + </v-avatar> + </v-chip> + </template> + <span v-text="note.note" /> + </v-tooltip> +</template> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/TextNoteCard.vue b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/TextNoteCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..f180603b7563f36f5ecd59bcce134d586bae0bda --- /dev/null +++ b/aleksis/apps/alsijil/frontend/components/coursebook/personal_notes/TextNoteCard.vue @@ -0,0 +1,27 @@ +<template> + <v-card + outlined + dense + rounded="lg" + class="mb-2" + v-bind="$attrs" + v-on="$listeners" + > + <v-card-title class="text-subtitle-2 pb-1 font-weight-medium"> + {{ $t("alsijil.personal_notes.card.title") }} + </v-card-title> + <v-card-text>{{ note.note || "–" }}</v-card-text> + </v-card> +</template> + +<script> +export default { + name: "TextNoteCard", + props: { + note: { + type: Object, + required: true, + }, + }, +}; +</script> diff --git a/aleksis/apps/alsijil/frontend/messages/en.json b/aleksis/apps/alsijil/frontend/messages/en.json index 0d39f282a34715bfc115554003999f943b9ca547..706ffbea77429f694154d96390aab829148e35db 100644 --- a/aleksis/apps/alsijil/frontend/messages/en.json +++ b/aleksis/apps/alsijil/frontend/messages/en.json @@ -77,7 +77,8 @@ } }, "notes": { - "show_list": "List of participants" + "show_list": "List of participants", + "future": "Lesson is in the future" }, "notices": { "future": "Editing this lesson isn't allowed as this lesson is in the future.", @@ -100,9 +101,12 @@ "field": "Edit subject" } }, - "present_number": "{present}/{total} present", "no_data": "No lessons for the selected groups and courses in this period", "no_results": "No search results for {search}", + "participations": { + "present_number": "{present}/{total} present", + "present": "Present" + }, "absences": { "action_for_selected": "Mark selected participant as: | Mark {count} selected participants as", "title": "Register absences", @@ -119,6 +123,9 @@ } }, "personal_notes": { + "card": { + "title": "Personal note" + }, "note": "Note", "create_personal_note": "Add another note", "tardiness": "Tardiness", diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index ed1da5ee7538b53ac64b5a9d152ce38f1425ebe7..5303a40e57e3bf7ac6bbeba98da2f6e206be6007 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -535,6 +535,7 @@ 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. @@ -566,11 +567,22 @@ class Documentation(CalendarEvent): doc = next(existing_documentations_event, None) if doc: - if (incomplete and doc.topic) or ( - absences_exist - and ( - not doc.participations.all() - or not [d for d in doc.participations.all() if d.absence_reason] + if ( + (incomplete and doc.topic) + or ( + not request.user.has_perm( + "alsijil.edit_participation_status_for_documentation_rule", doc + ) + and not doc.participations.filter( + person__pk=request.user.person.pk, absence_reason__isnull=False + ).exists() + ) + or ( + absences_exist + and ( + not doc.participations.all() + or not [d for d in doc.participations.all() if d.absence_reason] + ) ) ): continue @@ -609,6 +621,7 @@ 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. @@ -627,7 +640,7 @@ class Documentation(CalendarEvent): with_reference_object=True, ) - return Documentation.get_documentations_for_events(start, end, events, incomplete) + return Documentation.get_documentations_for_events(start, end, events, incomplete, request) @classmethod def parse_dummy( diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index 8b8e15e7b0250c76651144190ea4153b4e5717df..171a0765737f8b68164af78b3823c96481eef044 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -431,11 +431,19 @@ add_perm( view_participation_status_for_documentation_predicate, ) -edit_participation_status_for_documentation_predicate = ( +edit_participation_status_for_documentation_with_time_range_predicate = ( has_person & (has_global_perm("alsijil.change_participationstatus") | can_edit_participation_status) & is_in_allowed_time_range_for_participation_status ) +add_perm( + "alsijil.edit_participation_status_for_documentation_with_time_range_rule", + edit_participation_status_for_documentation_with_time_range_predicate, +) + +edit_participation_status_for_documentation_predicate = has_person & ( + has_global_perm("alsijil.change_participationstatus") | can_edit_participation_status +) add_perm( "alsijil.edit_participation_status_for_documentation_rule", edit_participation_status_for_documentation_predicate, diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py index 97ec4f187e036a246772b709c75942018fb1d4d9..ceb9bbd4bd9e1593f7dd19a21795dd5192ad2ee3 100644 --- a/aleksis/apps/alsijil/schema/__init__.py +++ b/aleksis/apps/alsijil/schema/__init__.py @@ -141,6 +141,7 @@ class Query(graphene.ObjectType): events, incomplete, absences_exist, + info.context, ) return docs + dummies @@ -218,6 +219,7 @@ class Query(graphene.ObjectType): person, start, end, + info.context, ) lessons_for_person.append(LessonsForPersonType(id=person, lessons=docs + dummies)) diff --git a/aleksis/apps/alsijil/schema/documentation.py b/aleksis/apps/alsijil/schema/documentation.py index 4f6436297e9ecdb0cc93223bbe960286ca54fe9f..cdb2eaca122330f70bd75023a463f2dc1dc0e8e7 100644 --- a/aleksis/apps/alsijil/schema/documentation.py +++ b/aleksis/apps/alsijil/schema/documentation.py @@ -4,7 +4,14 @@ import graphene from graphene_django.types import DjangoObjectType from reversion import create_revision, set_comment, set_user -from aleksis.apps.alsijil.util.predicates import can_edit_documentation, is_in_allowed_time_range +from aleksis.apps.alsijil.util.predicates import ( + can_edit_documentation, + is_in_allowed_time_range, + is_in_allowed_time_range_for_participation_status, +) +from aleksis.apps.alsijil.util.predicates import ( + can_edit_participation_status as can_edit_participation_status_predicate, +) from aleksis.apps.chronos.schema import LessonEventType from aleksis.apps.cursus.models import Subject from aleksis.apps.cursus.schema import CourseType, SubjectType @@ -13,6 +20,7 @@ from aleksis.core.schema.base import ( DjangoFilterMixin, PermissionsTypeMixin, ) +from aleksis.core.util.core_helpers import has_person from ..models import Documentation from .participation_status import ParticipationStatusType @@ -46,6 +54,10 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp participations = graphene.List(ParticipationStatusType, required=False) future_notice = graphene.Boolean(required=False) + future_notice_participation_status = graphene.Boolean(required=False) + + can_edit_participation_status = graphene.Boolean(required=False) + can_view_participation_status = graphene.Boolean(required=False) old_id = graphene.ID(required=False) @@ -68,15 +80,36 @@ class DocumentationType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectTyp ) @staticmethod - def resolve_participations(root: Documentation, info, **kwargs): - if not info.context.user.has_perm( + def resolve_future_notice_participation_status(root: Documentation, info, **kwargs): + """Shows whether the user can edit all participation statuses based on the current time. + + This checks whether the documentation is in the future. + """ + return not is_in_allowed_time_range_for_participation_status(info.context.user, root) + + @staticmethod + def resolve_can_edit_participation_status(root: Documentation, info, **kwargs): + """Shows whether the user can edit all participation statuses of the documentation""" + return can_edit_participation_status_predicate(info.context.user, root) + + @staticmethod + def resolve_can_view_participation_status(root: Documentation, info, **kwargs): + """Shows whether the user can view all participation statuses of the documentation""" + return info.context.user.has_perm( "alsijil.view_participation_status_for_documentation_rule", root - ): - return [] + ) + @staticmethod + 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"): return [] + elif not info.context.user.has_perm( + "alsijil.view_participation_status_for_documentation_rule", root + ): + if has_person(info.context.user): + return root.participations.filter(person=info.context.user.person) + return [] return root.participations.all() @@ -147,7 +180,8 @@ class TouchDocumentationMutation(graphene.Mutation): ) if not info.context.user.has_perm( - "alsijil.edit_participation_status_for_documentation_rule", documentation + "alsijil.edit_participation_status_for_documentation_with_time_range_rule", + documentation, ): raise PermissionDenied() diff --git a/aleksis/apps/alsijil/schema/participation_status.py b/aleksis/apps/alsijil/schema/participation_status.py index 2b9ebb25f37fabcc58415da613980d171114996d..fcf15df815619fcc808770116347458d8f7daf02 100644 --- a/aleksis/apps/alsijil/schema/participation_status.py +++ b/aleksis/apps/alsijil/schema/participation_status.py @@ -73,7 +73,8 @@ class ParticipationStatusBatchPatchMutation(BaseBatchPatchMutation): @classmethod def after_update_obj(cls, root, info, input, obj, full_input): # noqa: A002 if not info.context.user.has_perm( - "alsijil.edit_participation_status_for_documentation_rule", obj.related_documentation + "alsijil.edit_participation_status_for_documentation_with_time_range_rule", + obj.related_documentation, ): raise PermissionDenied()