From 46a485e80b08955f4a2296d279d9e21d997b36a0 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 16 Jun 2024 17:02:18 +0200 Subject: [PATCH] Add first real queries for class register statistics --- .../statistics/StatisticsForPersonCard.vue | 3 + .../coursebook/statistics/statistics.graphql | 3 +- aleksis/apps/alsijil/model_extensions.py | 83 ++++++++++++++++++- aleksis/apps/alsijil/schema/__init__.py | 23 +++-- aleksis/apps/alsijil/schema/statistics.py | 32 +++---- 5 files changed, 112 insertions(+), 32 deletions(-) diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue index 86ff28480..a961e6892 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonCard.vue @@ -91,6 +91,9 @@ export default { ...term, }; }, + skip() { + return !this.schoolTerm; + }, }, }, methods: { diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql index 821f961c3..1da470c12 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/statistics.graphql @@ -1,5 +1,4 @@ fragment statistics on StatisticsByPersonType { - schoolTerm participationCount absenceCount absenceReasons { @@ -27,7 +26,7 @@ fragment statistics on StatisticsByPersonType { } } -query statisticsByPerson($person: ID!, $term: ID) { +query statisticsByPerson($person: ID!, $term: ID!) { statistics: statisticsByPerson(person: $person, term: $term) { ...statistics } diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index abbedd709..14b135321 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -10,10 +10,11 @@ from calendarweek import CalendarWeek from aleksis.apps.alsijil.managers import PersonalNoteQuerySet from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod -from aleksis.core.models import Group, Person +from aleksis.apps.kolego.models import AbsenceReason +from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.util.core_helpers import get_site_preferences -from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote +from .models import Documentation, ExcuseType, ExtraMark, LessonDocumentation, PersonalNote def alsijil_url( @@ -493,3 +494,81 @@ def generate_person_list_with_class_register_statistics( ) return persons + + +def annotate_person_statistics( + persons: QuerySet[Person], participations_filter: Q, personal_notes_filter: Q +) -> 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("filtered_participation_statuses__tardiness"), + # tardiness_count=Count( + # "filtered_personal_notes", + # filter=Q(filtered_personal_notes__tardiness__gt=0), + # distinct=True, + # ), + ) + persons = persons.order_by("last_name", "first_name") + + for absence_reason in AbsenceReason.objects.all(): + persons = persons.annotate( + **{ + absence_reason.count_label: Count( + "filtered_participation_statuses", + filter=Q( + filtered_participation_statuses__absence_reason=absence_reason, + ), + distinct=True, + ) + } + ) + + for extra_mark in ExtraMark.objects.all(): + persons = persons.annotate( + **{ + extra_mark.count_label: Count( + "filtered_personal_notes", + filter=Q(filtered_personal_notes__extra_mark=extra_mark), + distinct=True, + ) + } + ) + + return persons + + +def annotate_person_statistics_for_school_term( + persons: QuerySet[Person], school_term: SchoolTerm +) -> QuerySet[Person]: + """Annotate a queryset of persons with class register statistics for a school term.""" + documentations = Documentation.objects.filter( + participations__person__in=persons, + datetime_start__date__gte=school_term.date_start, + datetime_end__date__lte=school_term.date_end, + ) + docs = list(documentations.values_list("pk", flat=True)) + + return annotate_person_statistics( + persons, + Q(participations__related_documentation__in=docs), + Q(new_personal_notes__documentation__in=docs), + ) diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py index ef274760f..986258b9f 100644 --- a/aleksis/apps/alsijil/schema/__init__.py +++ b/aleksis/apps/alsijil/schema/__init__.py @@ -8,11 +8,12 @@ import graphene from aleksis.apps.chronos.models import LessonEvent from aleksis.apps.cursus.models import Course from aleksis.apps.cursus.schema import CourseType -from aleksis.core.models import Group, Person +from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.schema.base import FilterOrderList from aleksis.core.schema.group import GroupType from aleksis.core.util.core_helpers import has_person +from ..model_extensions import annotate_person_statistics_for_school_term from ..models import Documentation from .absences import ( AbsencesBatchCreateMutation, @@ -58,7 +59,7 @@ class Query(graphene.ObjectType): statistics_by_person = graphene.Field( StatisticsByPersonType, person=graphene.ID(required=True), - term=graphene.ID(required=False), + term=graphene.ID(required=True), ) documentations_by_person = graphene.List( DocumentationByPersonType, @@ -68,7 +69,7 @@ class Query(graphene.ObjectType): statistics_by_group = graphene.List( StatisticsByPersonType, group=graphene.ID(required=True), - term=graphene.ID(required=False), + term=graphene.ID(required=True), ) def resolve_documentations_by_course_id(root, info, course_id, **kwargs): @@ -190,9 +191,11 @@ class Query(graphene.ObjectType): return lessons_for_person @staticmethod - def resolve_statistics_by_person(root, info, person, term=None): - # TODO: Annotate person with necessary information for term. - return Person.objects.get(id=person) + def resolve_statistics_by_person(root, info, person, term): + school_term = SchoolTerm.objects.get(id=term) + return annotate_person_statistics_for_school_term( + Person.objects.filter(id=person), school_term + ).first() @staticmethod def resolve_documentations_by_person(root, info, person, term=None): @@ -200,9 +203,11 @@ class Query(graphene.ObjectType): return Person.objects.get(id=person) @staticmethod - def resolve_statistics_by_group(root, info, group, term=None): - # TODO: Annotate persons with necessary information for term. - return Group.objects.get(id=group).members.all() + def resolve_statistics_by_group(root, info, group, term): + school_term = SchoolTerm.objects.get(id=term) + + members = Group.objects.get(id=group).members.all() + return annotate_person_statistics_for_school_term(members, school_term) class Mutation(graphene.ObjectType): diff --git a/aleksis/apps/alsijil/schema/statistics.py b/aleksis/apps/alsijil/schema/statistics.py index 4b9188a7a..4efe5e70a 100644 --- a/aleksis/apps/alsijil/schema/statistics.py +++ b/aleksis/apps/alsijil/schema/statistics.py @@ -1,5 +1,6 @@ import graphene +from aleksis.apps.cursus.models import Subject from aleksis.apps.cursus.schema import SubjectType from aleksis.apps.kolego.models.absence import AbsenceReason from aleksis.apps.kolego.schema.absence import AbsenceReasonType @@ -15,10 +16,10 @@ class AbsenceReasonWithCountType(graphene.ObjectType): count = graphene.Int() def resolve_absence_reason(root, info): - return root + return root["absence_reason"] def resolve_count(root, info): - return 6 + return root["count"] class ExtraMarkWithCountType(graphene.ObjectType): @@ -26,14 +27,13 @@ class ExtraMarkWithCountType(graphene.ObjectType): count = graphene.Int() def resolve_extra_mark(root, info): - return root + return root["extra_mark"] def resolve_count(root, info): - return 7 + return root["count"] class StatisticsByPersonType(graphene.ObjectType): - school_term = graphene.Int() participation_count = graphene.Int() absence_count = graphene.Int() absence_reasons = graphene.List(AbsenceReasonWithCountType) @@ -41,19 +41,11 @@ class StatisticsByPersonType(graphene.ObjectType): tardiness_count = graphene.Int() extra_marks = graphene.List(ExtraMarkWithCountType) - def resolve_school_term(root, info): - return 4 - - def resolve_participation_count(root, info): - return 3 - - def resolve_absence_count(root, info): - return 1 - def resolve_absence_reasons(root, info): - # TODO: Return actual AbsenceReasons - # Needed by resolve_absence_count as well. - return AbsenceReason.objects.all() + return [ + dict(absence_reason=reason, count=getattr(root, reason.count_label)) + for reason in AbsenceReason.objects.all() + ] def resolve_tardiness_sum(root, info): return 17 @@ -62,8 +54,10 @@ class StatisticsByPersonType(graphene.ObjectType): return 5 def resolve_extra_marks(root, info): - # TODO: Return actual ExtraMarks - return ExtraMark.objects.all() + return [ + dict(extra_mark=extra_mark, count=getattr(root, extra_mark.count_label)) + for extra_mark in ExtraMark.objects.all() + ] class DocumentationByPersonType(graphene.ObjectType): -- GitLab