diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue index aed34488e6cf200f9b1c6c4ae051a845feef0c87..9aa082419edc04af6158e1cb492bb1fb21ca15a4 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/Coursebook.vue @@ -70,7 +70,10 @@ </template> </infinite-scrolling-date-sorted-c-r-u-d-iterator> <v-scale-transition> - <absence-creation-dialog v-if="pageType === 'absences'" /> + <absence-creation-dialog + v-if="pageType === 'absences'" + :absence-reasons="absenceReasons" + /> </v-scale-transition> </div> </template> diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue index 7a6b4c73f5e3dbe6b6dbfa86df69b96b4a8d74a2..7a4b17c803ac4193f70ef413e913bd4a330c5733 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(); 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/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/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/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/models.py b/aleksis/apps/alsijil/models.py index 7d869c497a9ecbefc699fb1da379af635bd9fe79..9e2d50dead158fcf5f408ffdff59327cda942a01 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -139,7 +139,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. @@ -149,11 +148,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": @@ -179,6 +189,7 @@ class Documentation(CalendarEvent): ) ): continue + doc._amends_prefetched = event_reference_obj docs.append(doc) elif not absences_exist: if event_reference_obj.amends: @@ -214,7 +225,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. @@ -233,7 +243,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( diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py index cb0c6b1d16c5dab002794872afd177fa9da1b281..604cda6cd229533086533d855563a892f440d572 100644 --- a/aleksis/apps/alsijil/schema/__init__.py +++ b/aleksis/apps/alsijil/schema/__init__.py @@ -3,6 +3,7 @@ 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 @@ -13,10 +14,15 @@ 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_active_school_term, 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) ) @@ -104,7 +109,7 @@ class Query(graphene.ObjectType): ) ) ) - return documentations + return graphene_django_optimizer.query(documentations, info) def resolve_documentations_for_coursebook( root, @@ -152,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()), @@ -167,7 +176,6 @@ class Query(graphene.ObjectType): events, incomplete, absences_exist, - info.context, ) return docs + dummies @@ -182,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) @@ -211,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) @@ -223,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 @@ -255,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"): @@ -274,9 +294,12 @@ class Query(graphene.ObjectType): if not info.context.user.has_perm("alsijil.view_person_statistics_rule", person): return None school_term = get_active_school_term(info.context) - return annotate_person_statistics_for_school_term( - Person.objects.filter(id=person.id), school_term - ).first() + 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): @@ -284,12 +307,15 @@ class Query(graphene.ObjectType): if not info.context.user.has_perm("alsijil.view_person_statistics_rule", person): return [] school_term = get_active_school_term(info.context) - 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") + 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): @@ -297,13 +323,16 @@ class Query(graphene.ObjectType): if not info.context.user.has_perm("alsijil.view_person_statistics_rule", person): return [] school_term = get_active_school_term(info.context) - 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") + 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): @@ -313,7 +342,9 @@ class Query(graphene.ObjectType): 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/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..bb0e24e0564ab5f87a3fe1a25c85f4d8c5766241 100644 --- a/aleksis/apps/alsijil/schema/participation_status.py +++ b/aleksis/apps/alsijil/schema/participation_status.py @@ -41,6 +41,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 +55,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,