Skip to content
Snippets Groups Projects
Verified Commit 75e57468 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Implement detailed students list and include it on several positions

- My groups
- My students
- Links in week view
parent c94a142a
No related branches found
No related tags found
1 merge request!96Resolve "Improve "My Students""
Pipeline #4122 failed
Showing
with 531 additions and 92 deletions
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
......@@ -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"))
......
......@@ -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;
}
{# -*- 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 %}
{# -*- 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 %}
{# -*- 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 %}
......@@ -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">
......
{% 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>
{% 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 }} &times;</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>
......@@ -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"),
......
......@@ -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),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment