From 75e5746822bf4d25a8cd5d0c0a291c8a75645383 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Thu, 29 Oct 2020 21:23:27 +0100 Subject: [PATCH] Implement detailed students list and include it on several positions - My groups - My students - Links in week view --- aleksis/apps/alsijil/model_extensions.py | 78 +++++++++++- aleksis/apps/alsijil/rules.py | 8 ++ .../alsijil/static/css/alsijil/alsijil.css | 23 ++++ .../alsijil/class_register/groups.html | 87 +++++++++++-- .../alsijil/class_register/persons.html | 62 ++++++++-- .../alsijil/class_register/students_list.html | 49 ++++++++ .../alsijil/class_register/week_view.html | 38 ++++-- .../templates/alsijil/partials/legend.html | 55 +++++++++ .../alsijil/partials/persons_with_stats.html | 114 ++++++++++++++++++ aleksis/apps/alsijil/urls.py | 1 + aleksis/apps/alsijil/views.py | 108 ++++++++--------- 11 files changed, 531 insertions(+), 92 deletions(-) create mode 100644 aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html create mode 100644 aleksis/apps/alsijil/templates/alsijil/partials/legend.html create mode 100644 aleksis/apps/alsijil/templates/alsijil/partials/persons_with_stats.html diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index 8b4d4ff42..a4ab382d6 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -1,8 +1,8 @@ from datetime import date -from typing import Dict, Iterator, Optional, Union +from typing import Dict, Iterable, Iterator, Optional, Union from django.db.models import Exists, OuterRef, Q, QuerySet -from django.db.models.aggregates import Count +from django.db.models.aggregates import Count, Sum from django.utils.translation import gettext as _ import reversion @@ -282,3 +282,77 @@ def get_owner_groups_with_lessons(self: Person): Groups which have child groups with related lessons are also included. """ return Group.get_groups_with_lessons().filter(owners=self) + + +@Group.method +def generate_person_list_with_class_register_statistics( + self: Group, persons: Optional[Iterable] = None +) -> QuerySet: + """Get with class register statistics annotated list of all members.""" + persons = persons or self.members.all() + persons = persons.filter( + personal_notes__groups_of_person=self, + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ).annotate( + absences_count=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ), + ), + excused=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__excused=True, + personal_notes__excuse_type__isnull=True, + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ), + ), + unexcused=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__excused=False, + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ), + ), + tardiness=Sum("personal_notes__late"), + tardiness_count=Count( + "personal_notes", + filter=~Q(personal_notes__late=0) + & Q( + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ), + ), + ) + + for extra_mark in ExtraMark.objects.all(): + persons = persons.annotate( + **{ + extra_mark.count_label: Count( + "personal_notes", + filter=Q( + personal_notes__extra_marks=extra_mark, + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ), + ) + } + ) + + for excuse_type in ExcuseType.objects.all(): + persons = persons.annotate( + **{ + excuse_type.count_label: Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__excuse_type=excuse_type, + personal_notes__lesson_period__lesson__validity__school_term=self.school_term, + ), + ) + } + ) + + return persons diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index cb2bda333..a295a1dbd 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -146,6 +146,14 @@ add_perm("alsijil.view_my_students", view_my_students_predicate) view_my_groups_predicate = has_person & is_teacher add_perm("alsijil.view_my_groups", view_my_groups_predicate) +# View students list +view_students_list_predicate = view_my_groups_predicate & ( + is_group_owner + | has_global_perm("alsijil.view_personalnote") + | has_object_perm("core.view_personalnote_group") +) +add_perm("alsijil.view_students_list", view_students_list_predicate) + # View person overview view_person_overview_predicate = has_person & ( (is_current_person & is_site_preference_set("alsijil", "view_own_personal_notes")) diff --git a/aleksis/apps/alsijil/static/css/alsijil/alsijil.css b/aleksis/apps/alsijil/static/css/alsijil/alsijil.css index 892df4810..3e14ca014 100644 --- a/aleksis/apps/alsijil/static/css/alsijil/alsijil.css +++ b/aleksis/apps/alsijil/static/css/alsijil/alsijil.css @@ -35,3 +35,26 @@ table a.tr-link { margin-bottom: 10px; } } + +.collapsible li .show-on-active { + display: none; +} + +.collapsible li.active .show-on-active { + display: block; +} + +th.chip-height { + height: 67px; + line-height: 2.2; +} + +.collection-item.chip-height { + height: 52px; + line-height: 2.2; +} + +li.collection-item.button-height { + height: 58px; + line-height: 2.5; +} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html b/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html index 87385476b..a2bf6799d 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html @@ -1,24 +1,93 @@ {# -*- engine:django -*- #} {% extends "core/base.html" %} -{% load i18n %} +{% load i18n static %} {% block browser_title %}{% blocktrans %}My groups{% endblocktrans %}{% endblock %} - {% block page_title %} {% blocktrans %}My groups{% endblocktrans %} {% endblock %} +{% block extra_head %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/> +{% endblock %} + {% block content %} - <div class="collection"> + <table class="highlight responsive-table hide-on-med-and-down"> + <thead> + <tr> + <th>{% trans "Name" %}</th> + <th>{% trans "Students" %}</th> + <th></th> + </tr> + </thead> {% for group in groups %} - <a class="collection-item" href="{% url "week_view" "group" group.pk %}"> - {{ group }} - </a> + <tr> + <td> + {{ group }} + </td> + <td>{{ group.students_count }}</td> + <td> + <div class="right"> + <a class="btn primary-color waves-effect waves-light" href="{% url "students_list" group.pk %}"> + <i class="material-icons left">people</i> + {% trans "Students list" %} + </a> + <a class="btn secondary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">view_week</i> + {% trans "Week view" %} + </a> + <a class="btn primary waves-effect waves-light" href="{% url "full_register_group" group.pk %}" + target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </div> + </td> + </tr> {% empty %} - <li class="collection-item flow-text"> - {% blocktrans %}No groups available.{% endblocktrans %} - </li> + <tr> + <td class="flow-text" colspan="3"> + {% blocktrans %}No groups available.{% endblocktrans %} + </td> + </tr> {% endfor %} + </table> + + <div class="hide-on-large-only"> + <ul class="collection"> + {% for group in groups %} + <li class="collection-item"> + <span class="title">{{ group }}</span> + <p> + {{ group.students_count }} {% trans "students" %} + </p> + <p> + <a class="btn primary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">people</i> + {% trans "Students list" %} + </a> + </p> + <p> + <a class="btn secondary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">view_week</i> + {% trans "Week view" %} + </a> + </p> + <p> + <a class="btn primary waves-effect waves-light" href="{% url "full_register_group" group.pk %}" + target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </p> + </li> + {% empty %} + <li class="collection-item flow-text"> + {% blocktrans %}No groups available.{% endblocktrans %} + </li> + {% endfor %} + </ul> </div> {% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html b/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html index c3f5e35d4..84461c183 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/persons.html @@ -1,8 +1,6 @@ {# -*- engine:django -*- #} {% extends "core/base.html" %} -{% load data_helpers %} -{% load week_helpers %} -{% load i18n %} +{% load i18n week_helpers data_helpers static time_helpers %} {% block browser_title %}{% blocktrans %}My students{% endblocktrans %}{% endblock %} @@ -11,16 +9,56 @@ {% blocktrans %}My students{% endblocktrans %} {% endblock %} +{% block extra_head %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/> +{% endblock %} + + {% block content %} - <div class="collection"> - {% for person in persons %} - <a class="collection-item" href="{% url "overview_person" person.pk %}"> - {{ person }} - </a> - {% empty %} - <li class="collection-item flow-text"> - {% blocktrans %}No students available.{% endblocktrans %} + <ul class="collapsible"> + {% for group, persons in groups %} + <li {% if forloop.first %}class="active"{% endif %}> + <div class="collapsible-header"> + <div class="hundred-percent"> + <span class="right show-on-active hide-on-small-and-down"> + <a class="btn primary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">view_week</i> + {% trans "Week view" %} + </a> + <a class="btn waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </span> + + <h6>{{ group.name }} + <span class="chip">{{ group.school_term }}</span> + </h6> + + <p class="show-on-active hide-on-med-and-up"> + <a class="btn primary-color waves-effect waves-light hundred-percent" + href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">view_week</i> + {% trans "Week view" %} + </a> + </p> + <p class="show-on-active hide-on-med-and-up"> + <a class="btn waves-effect waves-light hundred-percent" href="{% url "full_register_group" group.pk %}" + target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </p> + </div> + </div> + + <div class="collapsible-body"> + {% include "alsijil/partials/persons_with_stats.html" with persons=persons %} + </div> </li> {% endfor %} - </div> + </ul> + + {% include "alsijil/partials/legend.html" %} {% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html b/aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html new file mode 100644 index 000000000..245addc83 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/students_list.html @@ -0,0 +1,49 @@ +{# -*- engine:django -*- #} +{% extends "core/base.html" %} +{% load static time_helpers data_helpers week_helpers i18n %} + +{% block browser_title %}{% blocktrans with group=group %}Students list: {{ group }}{% endblocktrans %}{% endblock %} + +{% block page_title %} + <a href="{% url "my_groups" %}" + class="btn-flat primary-color-text waves-light waves-effect"> + <i class="material-icons left">chevron_left</i> {% trans "Back" %} + </a> + {% blocktrans with group=group %}Students list: {{ group }}{% endblocktrans %} + <span class="right show-on-active hide-on-small-and-down"> + <a class="btn primary-color waves-effect waves-light" href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">view_week</i> + {% trans "Week view" %} + </a> + <a class="btn waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </span> +{% endblock %} + +{% block extra_head %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/> +{% endblock %} + +{% block content %} + <p class="show-on-active hide-on-med-and-up"> + <a class="btn primary-color waves-effect waves-light hundred-percent" + href="{% url "week_view" "group" group.pk %}"> + <i class="material-icons left">view_week</i> + {% trans "Week view" %} + </a> + </p> + <p class="show-on-active hide-on-med-and-up"> + <a class="btn waves-effect waves-light hundred-percent" href="{% url "full_register_group" group.pk %}" + target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </p> + + {% include "alsijil/partials/persons_with_stats.html" with persons=persons %} + + {% include "alsijil/partials/legend.html" %} +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html index 8d51886fa..c9278671b 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html @@ -15,14 +15,7 @@ {{ week_select|json_script:"week_select" }} <script type="text/javascript" src="{% static "js/chronos/week_select.js" %}"></script> <div class="row"> - {% if group %} - <div class="col s12 m2 push-m10 l1 push-l11"> - <a class="col s12 btn waves-effect waves-light right" href="{% url 'full_register_group' group.id %}"> - <i class="material-icons center">print</i> - </a> - </div> - {% endif %} - <div class="col s12 {% if group %}m10 pull-m2 l11 pull-l1 {% endif %}"> + <div class="col s12"> <form method="post" action=""> {% csrf_token %} {% form form=select_form %}{% endform %} @@ -34,12 +27,39 @@ </div> - <div class="row"> + <div class="row no-margin"> <h4 class="col s12 m6">{% blocktrans with el=el week=week.week %}CW {{ week }}: {{ instance }}{% endblocktrans %} </h4> {% include "chronos/partials/week_select.html" with wanted_week=week %} </div> + {% if group %} + <p class="hide-on-med-and-down"> + <a class="btn primary-color waves-effect waves-light" href="{% url "students_list" group.pk %}"> + <i class="material-icons left">people</i> + {% trans "Students list" %} + </a> + <a class="btn waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </p> + + <p class="hide-on-med-and-up"> + <a class="btn primary-color waves-effect waves-light hundred-percent" href="{% url "students_list" group.pk %}"> + <i class="material-icons left">people</i> + {% trans "Students list" %} + </a> + </p> + <p class="hide-on-med-and-up"> + <a class="btn waves-effect waves-light hundred-percent" href="{% url "full_register_group" group.pk %}" + target="_blank"> + <i class="material-icons left">print</i> + {% trans "Generate printout" %} + </a> + </p> + {% endif %} + {% if lesson_periods %} <div class="row"> <div class="col s12"> diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/legend.html b/aleksis/apps/alsijil/templates/alsijil/partials/legend.html new file mode 100644 index 000000000..a2c6ba1aa --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/partials/legend.html @@ -0,0 +1,55 @@ +{% load i18n %} +<div class="card"> + <div class="card-content"> + <div class="card-title">{% trans "Legend" %}</div> + <div class="row"> + <div class="col s12 m12 l4"> + <h6>{% trans "General" %}</h6> + <ul class="collection"> + <li class="collection-item chip-height"> + <strong>(a)</strong> {% trans "Absences" %} + <span class="chip secondary-color white-text right">0</span> + </li> + <li class="collection-item chip-height"> + <strong>(u)</strong> {% trans "Unexcused absences" %} + <span class="chip red white-text right">0</span> + </li> + <li class="collection-item chip-height"> + <strong>(e)</strong> {% trans "Excused absences" %} + <span class="chip green white-text right">0</span> + </li> + </ul> + </div> + + {% if excuse_types %} + <div class="col s12 m12 l4"> + <h6>{% trans "Excuse types" %}</h6> + + <ul class="collection"> + {% for excuse_type in excuse_types %} + <li class="collection-item chip-height"> + <strong>({{ excuse_type.short_name }})</strong> {{ excuse_type.name }} + <span class="chip grey white-text right">0</span> + </li> + {% endfor %} + </ul> + </div> + {% endif %} + + {% if extra_marks %} + <div class="col s12 m12 l4"> + <h6>{% trans "Extra marks" %}</h6> + + <ul class="collection"> + {% for extra_mark in extra_marks %} + <li class="collection-item chip-height"> + <strong>{{ extra_mark.short_name }}</strong> {{ extra_mark.name }} + <span class="chip grey white-text right">0</span> + </li> + {% endfor %} + </ul> + </div> + {% endif %} + </div> + </div> +</div> diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/persons_with_stats.html b/aleksis/apps/alsijil/templates/alsijil/partials/persons_with_stats.html new file mode 100644 index 000000000..7da999cff --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/partials/persons_with_stats.html @@ -0,0 +1,114 @@ +{% load data_helpers time_helpers i18n %} + +{% if not persons %} + <div class="alert primary"> + <div> + <i class="material-icons left">warning</i> + {% blocktrans %}No students available.{% endblocktrans %} + </div> + </div> +{% else %} + <table class="highlight responsive-table"> + <thead> + <tr class="hide-on-med-and-down"> + <th rowspan="2">{% trans "Name" %}</th> + <th rowspan="2">{% trans "Primary group" %}</th> + <th colspan="{{ excuse_types.count|add:3 }}">{% trans "Absences" %}</th> + <th rowspan="2">{% trans "Tardiness" %}</th> + {% if extra_marks %} + <th colspan="{{ extra_marks.count }}">{% trans "Extra marks" %}</th> + {% endif %} + <th rowspan="2"></th> + </tr> + <tr class="hide-on-large-only"> + <th class="truncate">{% trans "Name" %}</th> + <th class="truncate">{% trans "Primary group" %}</th> + <th class="truncate chip-height">{% trans "Absences" %}</th> + <th class="chip-height">{% trans "(e)" %}</th> + {% for excuse_type in excuse_types %} + <th class="chip-height"> + ({{ excuse_type.short_name }}) + </th> + {% endfor %} + <th class="chip-height">{% trans "(u)" %}</th> + <th class="truncate chip-height">{% trans "Tardiness" %}</th> + {% for extra_mark in extra_marks %} + <th class="chip-height"> + {{ extra_mark.short_name }} + </th> + {% endfor %} + <th rowspan="2"></th> + </tr> + <tr class="hide-on-med-and-down"> + <th>{% trans "Sum" %}</th> + <th>{% trans "(e)" %}</th> + {% for excuse_type in excuse_types %} + <th> + ({{ excuse_type.short_name }}) + </th> + {% endfor %} + <th>{% trans "(u)" %}</th> + {% for extra_mark in extra_marks %} + <th> + {{ extra_mark.short_name }} + </th> + {% endfor %} + </tr> + </thead> + {% for person in persons %} + <tr> + <td> + <a href="{% url "overview_person" person.pk %}"> + {{ person }} + </a> + </td> + <td> + {% firstof person.primary_group "–" %} + </td> + <td> + <span class="chip secondary-color white-text" title="{% trans "Absences" %}"> + {{ person.absences_count }} + </span> + </td> + <td class="green-text"> + <span class="chip green white-text" title="{% trans "Excused" %}"> + {{ person.excused }} + </span> + </td> + {% for excuse_type in excuse_types %} + <td> + <span class="chip grey white-text" title="{{ excuse_type.name }}"> + {{ person|get_dict:excuse_type.count_label }} + </span> + </td> + {% endfor %} + <td class="red-text"> + <span class="chip red white-text" title="{% trans "Unexcused" %}"> + {{ person.unexcused }} + </span> + </td> + <td> + <span class="chip orange white-text" title="{% trans "Tardiness" %}"> + {% firstof person.tardiness|to_time|time:"H\h i\m" "–" %} + </span> + <span class="chip orange white-text" title="{% trans "Count of tardiness" %}">{{ person.tardiness_count }} ×</span> + </td> + {% for extra_mark in extra_marks %} + <td> + <span class="chip grey white-text" title="{{ extra_mark.name }}"> + {{ person|get_dict:extra_mark.count_label }} + </span> + </td> + {% endfor %} + + <td> + <a class="btn primary waves-effect waves-light" href="{% url "overview_person" person.pk %}"> + <i class="material-icons left">insert_chart</i> + <span class="hide-on-med-and-down"> {% trans "Show more details" %}</span> + <span class="hide-on-large-only">{% trans "Details" %}</span> + </a> + </td> + </tr> + {% endfor %} +{% endif %} +</table> diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index af972edd5..4d4c490a2 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ "print/group/<int:id_>", views.full_register_group, name="full_register_group" ), path("groups/", views.my_groups, name="my_groups"), + path("groups/<int:pk>/", views.StudentsList.as_view(), name="students_list"), path("persons/", views.my_students, name="my_students"), path("persons/<int:id_>/", views.overview_person, name="overview_person"), path("me/", views.overview_person, name="overview_me"), diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index d72c58563..e8e135a04 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -436,63 +436,18 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: (lesson_period, documentations, notes, substitution) ) - persons = ( - Person.objects.prefetch_related( - "personal_notes", - "personal_notes__excuse_type", - "personal_notes__extra_marks", - "personal_notes__lesson_period__lesson__subject", - "personal_notes__lesson_period__substitutions", - "personal_notes__lesson_period__substitutions__subject", - "personal_notes__lesson_period__substitutions__teachers", - "personal_notes__lesson_period__lesson__teachers", - "personal_notes__lesson_period__period", - ) - .filter( - personal_notes__groups_of_person=group, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, - ) - .annotate( - absences_count=Count( - "personal_notes__absent", - filter=Q( - personal_notes__absent=True, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, - ), - ), - excused=Count( - "personal_notes__absent", - filter=Q( - personal_notes__absent=True, - personal_notes__excused=True, - personal_notes__excuse_type__isnull=True, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, - ), - ), - unexcused=Count( - "personal_notes__absent", - filter=Q( - personal_notes__absent=True, - personal_notes__excused=False, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, - ), - ), - tardiness=Sum("personal_notes__late"), - ) + persons = Person.objects.prefetch_related( + "personal_notes", + "personal_notes__excuse_type", + "personal_notes__extra_marks", + "personal_notes__lesson_period__lesson__subject", + "personal_notes__lesson_period__substitutions", + "personal_notes__lesson_period__substitutions__subject", + "personal_notes__lesson_period__substitutions__teachers", + "personal_notes__lesson_period__lesson__teachers", + "personal_notes__lesson_period__period", ) - - for extra_mark in ExtraMark.objects.all(): - persons = persons.annotate( - **{ - extra_mark.count_label: Count( - "personal_notes", - filter=Q( - personal_notes__extra_marks=extra_mark, - personal_notes__lesson_period__lesson__validity__school_term=current_school_term, - ), - ) - } - ) + persons = group.generate_person_list_with_class_register_statistics(persons) for excuse_type in ExcuseType.objects.all(): persons = persons.annotate( @@ -535,19 +490,52 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: @permission_required("alsijil.view_my_students") def my_students(request: HttpRequest) -> HttpResponse: context = {} - relevant_groups = request.user.person.get_owner_groups_with_lessons() - persons = Person.objects.filter(member_of__in=relevant_groups) - context["persons"] = persons + relevant_groups = ( + request.user.person.get_owner_groups_with_lessons() + .filter(members__isnull=False) + .prefetch_related("members") + .distinct() + ) + relevant_groups = sorted( + relevant_groups, key=lambda g: g.name if g.parent_groups else "0" + ) + + new_groups = [] + for group in relevant_groups: + persons = group.generate_person_list_with_class_register_statistics() + new_groups.append((group, persons)) + + context["groups"] = new_groups + context["excuse_types"] = ExcuseType.objects.all() + context["extra_marks"] = ExtraMark.objects.all() return render(request, "alsijil/class_register/persons.html", context) @permission_required("alsijil.view_my_groups",) def my_groups(request: HttpRequest) -> HttpResponse: context = {} - context["groups"] = request.user.person.get_owner_groups_with_lessons() + context["groups"] = request.user.person.get_owner_groups_with_lessons().annotate( + students_count=Count("members") + ) return render(request, "alsijil/class_register/groups.html", context) +class StudentsList(PermissionRequiredMixin, DetailView): + model = Group + template_name = "alsijil/class_register/students_list.html" + permission_required = "alsijil.view_students_list" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["group"] = self.object + context[ + "persons" + ] = self.object.generate_person_list_with_class_register_statistics() + context["extra_marks"] = ExtraMark.objects.all() + context["excuse_types"] = ExcuseType.objects.all() + return context + + @permission_required( "alsijil.view_person_overview", fn=objectgetter_optional(Person, "request.user.person", True), -- GitLab