Skip to content
Snippets Groups Projects
Commit 7d35d6c0 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch '93-add-students-list-and-view-with-further-information' into 'master'

Resolve "Add student view with further information"

Closes #93 und #92

See merge request !80
parents 151dd117 71bf7438
No related branches found
No related tags found
1 merge request!80Resolve "Add student view with further information"
Pipeline #3516 passed
......@@ -24,6 +24,18 @@ MENUS = {
"icon": "view_week",
"validators": ["menu_generator.validators.is_authenticated"],
},
{
"name": _("My overview"),
"url": "overview_me",
"icon": "insert_chart",
"validators": ["menu_generator.validators.is_authenticated"],
},
{
"name": _("My students"),
"url": "my_students",
"icon": "people",
"validators": ["menu_generator.validators.is_authenticated"],
},
{
"name": _("Register absence"),
"url": "register_absence",
......
......@@ -77,6 +77,9 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
def save(self, *args, **kwargs):
if self.excuse_type:
self.excused = True
if not self.absent:
self.excused = False
self.excuse_type = None
super().save(*args, **kwargs)
class Meta:
......
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load data_helpers %}
{% load week_helpers %}
{% load i18n %}
{% block browser_title %}{% blocktrans %}Class register: person{% endblocktrans %}{% endblock %}
{% block page_title %}
{% blocktrans with person=person %}
Class register overview for {{ person }}
{% endblocktrans %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col s12 m12 l6">
<h5>{% trans "Unexcused absences" %}</h5>
<ul class="collection">
{% for note in unexcused_absences %}
{% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
<li class="collection-item">
<form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
{% csrf_token %}
{% trans "Mark as" %}
<input type="hidden" value="{{ note.pk }}" name="personal_note">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
<i class="material-icons left red-text">warning</i>
<p class="no-margin">
<a href="{% url "lesson_by_week_and_period" note.year note.week note.lesson_period.pk %}">{{ note_date }}, {{ note.lesson_period }}</a>
</p>
{% if note.remarks %}
<p class="no-margin"><em>{{ note.remarks }}</em></p>
{% endif %}
<form action="" method="post" class="hide-on-med-and-up">
{% csrf_token %}
{% trans "Mark as" %}
<input type="hidden" value="{{ note.pk }}" name="personal_note">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
</li>
{% empty %}
<li class="collection-item flow-text">
{% trans "There are unexcused lessons." %}
</li>
{% endfor %}
</ul>
<h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
<ul class="collapsible">
{% for school_term, stat in stats %}
<li {% if forloop.first %}class="active"{% endif %}>
<div class="collapsible-header">
<i class="material-icons">date_range</i>{{ school_term }}</div>
<div class="collapsible-body">
<table>
<tr>
<th colspan="2">{% trans 'Absences' %}</th>
<td>{{ stat.absences_count }}</td>
</tr>
<tr>
<td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
<td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
<th class="truncate">{% trans 'Excused' %}</th>
<td>{{ stat.excused }}</td>
</tr>
{% for excuse_type in excuse_types %}
<th>{{ excuse_type.name }}</th>
<td>{{ stat|get_dict:excuse_type.count_label }}</td>
{% endfor %}
<tr>
<th>{% trans 'Unexcused' %}</th>
<td>{{ stat.unexcused }}</td>
</tr>
<tr>
<th colspan="2">{% trans 'Tardiness' %}</th>
<td>{{ stat.tardiness }}'</td>
</tr>
{% for extra_mark in extra_marks %}
<tr>
<th colspan="2">{{ extra_mark.name }}</th>
<td>{{ stat|get_dict:extra_mark.count_label }}</td>
</tr>
{% endfor %}
</table>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="col s12 m12 l6">
<h5>{% trans "Relevant personal notes" %}</h5>
<ul class="collapsible">
<li>
<div>
<ul>
{% for note in personal_notes %}
{% ifchanged note.lesson_period.lesson.validity.school_term %}</ul></div></li>
<li {% if forloop.first %}class="active"{% endif %}>
<div class="collapsible-header"><i
class="material-icons">date_range</i>{{ note.lesson_period.lesson.validity.school_term }}</div>
<div class="collapsible-body">
<ul class="collection">
{% endifchanged %}
{% ifchanged note.week %}
<li class="collection-item">
<strong>{% blocktrans with week=note.week %}Week {{ week }}{% endblocktrans %}</strong>
</li>
{% endifchanged %}
{% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
{% ifchanged note_date %}
<li class="collection-item">
<form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
{% csrf_token %}
{% trans "Mark all as" %}
<input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
<i class="material-icons left">schedule</i>
{{ note_date }}
<form action="" method="post" class="hide-on-med-and-up">
{% csrf_token %}
{% trans "Mark all as" %}
<input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
</li>
{% endifchanged %}
<li class="collection-item">
<div class="row no-margin">
<div class="col s2 m1">
{{ note.lesson_period.period.period }}.
</div>
<div class="col s10 m4">
<i class="material-icons left">event_note</i>
<a href="{% url "lesson_by_week_and_period" note.year note.week note.lesson_period.pk %}">
{{ note.lesson_period.get_subject.name }}<br/>
{{ note.lesson_period.get_teacher_names }}
</a>
</div>
<div class="col s12 m7 no-padding">
{% if note.absent and not note.excused %}
<form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
{% csrf_token %}
{% trans "Mark as" %}
<input type="hidden" value="{{ note.pk }}" name="personal_note">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
{% endif %}
{% if note.absent %}
<div class="chip red white-text">
{% trans 'Absent' %}
</div>
{% endif %}
{% if note.excused %}
<div class="chip green white-text">
{% if note.excuse_type %}
{{ note.excuse_type.name }}
{% else %}
{% trans 'Excused' %}
{% endif %}
</div>
{% endif %}
{% if note.late %}
<div class="chip orange white-text">
{% blocktrans with late=note.late %}{{ late }}' late{% endblocktrans %}
</div>
{% endif %}
{% for extra_mark in note.extra_marks.all %}
<div class="chip">{{ extra_mark.name }}</div>
{% endfor %}
<em>{{ note.remarks }}</em>
</div>
<div class="col s12 hide-on-med-and-up">
{% if note.absent and not note.excused %}
<form action="" method="post">
{% csrf_token %}
{% trans "Mark as" %}
<input type="hidden" value="{{ note.pk }}" name="personal_note">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
{% endif %}
</div>
</li>
{% endfor %}
</li>
</ul>
</div>
</div>
{% endblock %}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load data_helpers %}
{% load week_helpers %}
{% load i18n %}
{% block browser_title %}{% blocktrans %}My students{% endblocktrans %}{% endblock %}
{% block page_title %}
{% blocktrans %}My students{% endblocktrans %}
{% 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 %}
</li>
{% endfor %}
</div>
{% endblock %}
......@@ -35,7 +35,8 @@
<div class="row">
<h4 class="col s12 m6">{% blocktrans with el=el week=week.week %}CW {{ week }}: {{ instance }}{% endblocktrans %} </h4>
<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>
......@@ -111,7 +112,9 @@
{% blocktrans %}Personal notes{% endblocktrans %}
</span>
{% for person in persons %}
<h5 class="card-title">{{ person.person.full_name }}</h5>
<h5 class="card-title">
<a href="{% url "overview_person" person.person.pk %}">{{ person.person.full_name }}</a>
</h5>
<p class="card-text">
{% trans "Absent" %}: {{ person.person.absences_count }}
({{ person.person.unexcused_count }} {% trans "unexcused" %})
......
{% load i18n %}
<button type="submit" class="btn-flat tooltipped" name="excuse_type" value="e" title="{% trans "Excused" %}"
data-position="bottom" data-tooltip="{% trans "Excused" %}" style="width: 50px;">
{% trans "e" %}
</button>
{% for excuse_type in excuse_types %}
<button type="submit" class="btn-flat tooltipped" value="{{ excuse_type.pk }}" name="excuse_type"
title="{{ excuse_type.name }}" data-position="bottom" data-tooltip="{{ excuse_type.name }}"
style="width: 50px;">
{{ excuse_type.short_name }}
</button>
{% endfor %}
......@@ -319,7 +319,7 @@
{% for note in person.personal_notes.all %}
{% if note.lesson_period in lesson_periods %}
{% if note.absent or note.late or note.remarks or note.extra_marks.all %}
{% period_to_date note.week note.lesson_period.period as note_date %}
{% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
<tr>
<td>{{ note_date }}</td>
<td>{{ note.lesson_period.period.period }}</td>
......
......@@ -26,6 +26,9 @@ urlpatterns = [
path(
"print/group/<int:id_>", views.full_register_group, name="full_register_group"
),
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"),
path("absence/new", views.register_absence, name="register_absence"),
path("extra_marks/", views.ExtraMarkListView.as_view(), name="extra_marks"),
path(
......
......@@ -20,7 +20,7 @@ from aleksis.apps.chronos.util.date import get_weeks_for_year, week_weekday_to_d
from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from aleksis.core.models import Group, Person, SchoolTerm
from aleksis.core.util import messages
from aleksis.core.util.core_helpers import get_site_preferences
from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional
from .forms import (
ExcuseTypeForm,
......@@ -30,7 +30,7 @@ from .forms import (
RegisterAbsenceForm,
SelectForm,
)
from .models import ExcuseType, ExtraMark, LessonDocumentation
from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
from .tables import ExcuseTypeTable, ExtraMarkTable
......@@ -458,6 +458,141 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
return render(request, "alsijil/print/full_register.html", context)
def my_students(request: HttpRequest) -> HttpResponse:
context = {}
relevant_groups = (
Group.objects.for_current_school_term_or_all()
.annotate(lessons_count=Count("lessons"))
.filter(lessons_count__gt=0, owners=request.user.person)
)
persons = Person.objects.filter(member_of__in=relevant_groups)
context["persons"] = persons
return render(request, "alsijil/class_register/persons.html", context)
def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
context = {}
person = objectgetter_optional(
Person, default="request.user.person", default_eval=True
)(request, id_)
context["person"] = person
if request.method == "POST":
if request.POST.get("excuse_type"):
# Get excuse type
excuse_type = request.POST["excuse_type"]
found = False
if excuse_type == "e":
excuse_type = None
found = True
else:
try:
excuse_type = ExcuseType.objects.get(pk=int(excuse_type))
found = True
except (ExcuseType.DoesNotExist, ValueError):
pass
if found:
if request.POST.get("date"):
# Mark absences on date as excused
try:
date = datetime.strptime(
request.POST["date"], "%Y-%m-%d"
).date()
notes = person.personal_notes.filter(
week=date.isocalendar()[1],
lesson_period__period__weekday=date.weekday(),
lesson_period__lesson__validity__date_start__lte=date,
lesson_period__lesson__validity__date_end__gte=date,
absent=True,
excused=False,
)
notes.update(excused=True, excuse_type=excuse_type)
messages.success(
request, _("The absences have been marked as excused.")
)
except ValueError:
pass
elif request.POST.get("personal_note"):
# Mark specific absence as excused
try:
note = PersonalNote.objects.get(
pk=int(request.POST["personal_note"])
)
if note.absent:
note.excused = True
note.excuse_type = excuse_type
note.save()
messages.success(
request, _("The absence has been marked as excused.")
)
except (PersonalNote.DoesNotExist, ValueError):
pass
person.refresh_from_db()
unexcused_absences = person.personal_notes.filter(absent=True, excused=False)
context["unexcused_absences"] = unexcused_absences
personal_notes = person.personal_notes.filter(
Q(absent=True) | Q(late__gt=0) | ~Q(remarks="") | Q(extra_marks__isnull=False)
).order_by(
"-lesson_period__lesson__validity__date_start",
"-week",
"lesson_period__period__weekday",
"lesson_period__period__period",
)
context["personal_notes"] = personal_notes
context["excuse_types"] = ExcuseType.objects.all()
school_terms = SchoolTerm.objects.all().order_by("-date_start")
stats = []
for school_term in school_terms:
stat = {}
personal_notes = PersonalNote.objects.filter(
person=person, lesson_period__lesson__validity__school_term=school_term
)
if not personal_notes.exists():
continue
stat.update(
personal_notes.filter(absent=True).aggregate(absences_count=Count("absent"))
)
stat.update(
personal_notes.filter(
absent=True, excused=True, excuse_type__isnull=True
).aggregate(excused=Count("absent"))
)
stat.update(
personal_notes.filter(absent=True, excused=False).aggregate(
unexcused=Count("absent")
)
)
stat.update(personal_notes.aggregate(tardiness=Sum("late")))
for extra_mark in ExtraMark.objects.all():
stat.update(
personal_notes.filter(extra_marks=extra_mark).aggregate(
**{extra_mark.count_label: Count("pk")}
)
)
for excuse_type in ExcuseType.objects.all():
stat.update(
personal_notes.filter(absent=True, excuse_type=excuse_type).aggregate(
**{excuse_type.count_label: Count("absent")}
)
)
stats.append((school_term, stat))
context["stats"] = stats
context["excuse_types"] = ExcuseType.objects.all()
context["extra_marks"] = ExtraMark.objects.all()
return render(request, "alsijil/class_register/person.html", context)
def register_absence(request: HttpRequest) -> HttpResponse:
context = {}
......
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