diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 0f0e72bb864590684c8eab5e90eed5eb5f444546..c21c138bdd337cba114e473b79675ac1a4e0ff7a 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -69,6 +69,9 @@ class PersonalNote(ExtensibleModel): 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: diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/person.html b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html new file mode 100644 index 0000000000000000000000000000000000000000..e73234c1fcfc7d830eb3955681649399577dc256 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/person.html @@ -0,0 +1,144 @@ +{# -*- engine:django -*- #} +{% extends "core/base.html" %} +{% 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 %} + {% period_to_date note.week note.lesson_period.period as note_date %} + <li class="collection-item"> + <i class="material-icons left red-text">warning</i> + <p class="no-margin">{{ note_date }}, {{ note.lesson_period }}</p> + {% if note.remarks %} + <p class="no-margin"><em>{{ note.remarks }}</em></p> + {% endif %} + </li> + {% empty %} + <li class="collection-item flow-text"> + {% trans "There are unexcused lessons." %} + </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 %} + + {% period_to_date note.week note.lesson_period.period as note_date %} + {% ifchanged note_date %} + <li class="collection-item"> + <form action="" method="post" class="right" style="margin-top: -7px;"> + {% csrf_token %} + {% trans "Mark all as" %} + <input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date"> + <button type="submit" class="btn-flat" name="excuse_type" value="e"> + {% trans "e" %} + </button> + {% for excuse_type in excuse_types %} + <button type="submit" class="btn-flat" value="{{ excuse_type.pk }}" name="excuse_type"> + {{ excuse_type.short_name }} + </button> + {% endfor %} + </form> + <i class="material-icons left">schedule</i> + {{ note_date }} + + + </li> + {% endifchanged %} + + <li class="collection-item"> + <div class="row no-margin"> + <div class="col s12 m1"> + {{ note.lesson_period.period.period }}. + </div> + + <div class="col s12 m4"> + <i class="material-icons left">event_note</i> + {{ note.lesson_period.get_subject.name }}, + {{ note.lesson_period.get_teacher_names }} + </div> + + <div class="col s12 m7"> + {% if note.absent and not note.excused %} + <form action="" method="post" class="right" style="margin-top: -7px;"> + {% csrf_token %} + {% trans "Mark all as" %} + <input type="hidden" value="{{ note.pk }}" name="personal_note"> + <button type="submit" class="btn-flat" name="excuse_type" value="e"> + {% trans "e" %} + </button> + {% for excuse_type in excuse_types %} + <button type="submit" class="btn-flat" value="{{ excuse_type.pk }}" name="excuse_type"> + {{ excuse_type.short_name }} + </button> + {% endfor %} + </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> + </li> + {% endfor %} + </li> + </ul> + </div> + </div> +{% 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 d0761f8102f60e1ea899a8b1ba1d11c1e2402535..ea549f7f15783769f79a9d598304b856481adb18 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/week_view.html @@ -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" %}) diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index 7e7139c6c26fe7aa600b6b311b55ae731389c13b..2fc61a1c275592d90f8326b954b150b6c18a83bd 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -26,6 +26,8 @@ urlpatterns = [ path( "print/group/<int:id_>", views.full_register_group, name="full_register_group" ), + path("person/<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( diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 9c90ede28c1db9d34a2e7f1bff53e89cb53e3757..5a471b20a91fdb3fff5cfaa43ad43d69f53e9c94 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -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 @@ -445,6 +445,84 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: return render(request, "alsijil/print/full_register.html", context) +def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: + context = {} + person = objectgetter_optional(Person, default_eval="request.user.person")( + 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() + return render(request, "alsijil/class_register/person.html", context) + + def register_absence(request: HttpRequest) -> HttpResponse: context = {}