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 = { ...@@ -24,6 +24,18 @@ MENUS = {
"icon": "view_week", "icon": "view_week",
"validators": ["menu_generator.validators.is_authenticated"], "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"), "name": _("Register absence"),
"url": "register_absence", "url": "register_absence",
......
...@@ -77,6 +77,9 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin): ...@@ -77,6 +77,9 @@ class PersonalNote(ExtensibleModel, WeekRelatedMixin):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.excuse_type: if self.excuse_type:
self.excused = True self.excused = True
if not self.absent:
self.excused = False
self.excuse_type = None
super().save(*args, **kwargs) super().save(*args, **kwargs)
class Meta: 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 @@ ...@@ -35,7 +35,8 @@
<div class="row"> <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 %} {% include "chronos/partials/week_select.html" with wanted_week=week %}
</div> </div>
...@@ -111,7 +112,9 @@ ...@@ -111,7 +112,9 @@
{% blocktrans %}Personal notes{% endblocktrans %} {% blocktrans %}Personal notes{% endblocktrans %}
</span> </span>
{% for person in persons %} {% 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"> <p class="card-text">
{% trans "Absent" %}: {{ person.person.absences_count }} {% trans "Absent" %}: {{ person.person.absences_count }}
({{ person.person.unexcused_count }} {% trans "unexcused" %}) ({{ 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 @@ ...@@ -319,7 +319,7 @@
{% for note in person.personal_notes.all %} {% for note in person.personal_notes.all %}
{% if note.lesson_period in lesson_periods %} {% if note.lesson_period in lesson_periods %}
{% if note.absent or note.late or note.remarks or note.extra_marks.all %} {% 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> <tr>
<td>{{ note_date }}</td> <td>{{ note_date }}</td>
<td>{{ note.lesson_period.period.period }}</td> <td>{{ note.lesson_period.period.period }}</td>
......
...@@ -26,6 +26,9 @@ urlpatterns = [ ...@@ -26,6 +26,9 @@ urlpatterns = [
path( path(
"print/group/<int:id_>", views.full_register_group, name="full_register_group" "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("absence/new", views.register_absence, name="register_absence"),
path("extra_marks/", views.ExtraMarkListView.as_view(), name="extra_marks"), path("extra_marks/", views.ExtraMarkListView.as_view(), name="extra_marks"),
path( path(
......
...@@ -20,7 +20,7 @@ from aleksis.apps.chronos.util.date import get_weeks_for_year, week_weekday_to_d ...@@ -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.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.models import Group, Person, SchoolTerm
from aleksis.core.util import messages 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 ( from .forms import (
ExcuseTypeForm, ExcuseTypeForm,
...@@ -30,7 +30,7 @@ from .forms import ( ...@@ -30,7 +30,7 @@ from .forms import (
RegisterAbsenceForm, RegisterAbsenceForm,
SelectForm, SelectForm,
) )
from .models import ExcuseType, ExtraMark, LessonDocumentation from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
from .tables import ExcuseTypeTable, ExtraMarkTable from .tables import ExcuseTypeTable, ExtraMarkTable
...@@ -458,6 +458,141 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -458,6 +458,141 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
return render(request, "alsijil/print/full_register.html", context) 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: def register_absence(request: HttpRequest) -> HttpResponse:
context = {} 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