diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 3d7f554eb2750784c3217eeaff4c09baae330520..88127e98c45d71fbe22ec9572fc04d89891489a7 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -11,7 +11,7 @@ from material import Layout, Row from aleksis.apps.chronos.managers import TimetableType from aleksis.core.models import Group, Person -from .models import LessonDocumentation, PersonalNote, PersonalNoteFilter +from .models import ExcuseType, LessonDocumentation, PersonalNote, PersonalNoteFilter class LessonDocumentationForm(forms.ModelForm): @@ -28,7 +28,7 @@ class LessonDocumentationForm(forms.ModelForm): class PersonalNoteForm(forms.ModelForm): class Meta: model = PersonalNote - fields = ["absent", "late", "excused", "remarks"] + fields = ["absent", "late", "excused", "excuse_type", "remarks"] person_name = forms.CharField(disabled=True) @@ -47,10 +47,7 @@ class SelectForm(forms.Form): layout = Layout(Row("group", "teacher")) group = forms.ModelChoiceField( - queryset=None, - label=_("Group"), - required=False, - widget=Select2Widget, + queryset=None, label=_("Group"), required=False, widget=Select2Widget, ) teacher = forms.ModelChoiceField( queryset=Person.objects.annotate( @@ -81,8 +78,10 @@ class SelectForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields["group"].queryset = Group.objects.for_current_school_term_or_all().annotate(lessons_count=Count("lessons")).filter( - lessons_count__gt=0 + self.fields["group"].queryset = ( + Group.objects.for_current_school_term_or_all() + .annotate(lessons_count=Count("lessons")) + .filter(lessons_count__gt=0) ) @@ -116,3 +115,11 @@ class PersonalNoteFilterForm(forms.ModelForm): class Meta: model = PersonalNoteFilter fields = ["identifier", "description", "regex"] + + +class ExcuseTypeForm(forms.ModelForm): + layout = Layout("short_name", "name") + + class Meta: + model = ExcuseType + fields = ["short_name", "name"] diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py index 2b84cb6f7f70b644a34f79f170dfc4cb6751f773..c5fea59e3790fba0407d55d760434ac2e3dd0d3d 100644 --- a/aleksis/apps/alsijil/menus.py +++ b/aleksis/apps/alsijil/menus.py @@ -36,6 +36,12 @@ MENUS = { "icon": "filter_list", "validators": ["menu_generator.validators.is_superuser"], }, + { + "name": _("Excuse types"), + "url": "excuse_types", + "icon": "label", + "validators": ["menu_generator.validators.is_superuser"], + }, ], } ] diff --git a/aleksis/apps/alsijil/migrations/0002_excuse_type.py b/aleksis/apps/alsijil/migrations/0002_excuse_type.py new file mode 100644 index 0000000000000000000000000000000000000000..6e4df12f7219969a1059abcd0757be1999a4fb6a --- /dev/null +++ b/aleksis/apps/alsijil/migrations/0002_excuse_type.py @@ -0,0 +1,73 @@ +# Generated by Django 3.0.8 on 2020-07-10 10:46 + +import django.contrib.postgres.fields.jsonb +import django.contrib.sites.managers +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("sites", "0002_alter_domain_unique"), + ("alsijil", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="ExcuseType", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "extended_data", + django.contrib.postgres.fields.jsonb.JSONField( + default=dict, editable=False + ), + ), + ( + "short_name", + models.CharField( + max_length=255, unique=True, verbose_name="Short name" + ), + ), + ( + "name", + models.CharField(max_length=255, unique=True, verbose_name="Name"), + ), + ( + "site", + models.ForeignKey( + default=1, + editable=False, + on_delete=django.db.models.deletion.CASCADE, + to="sites.Site", + ), + ), + ], + options={ + "verbose_name": "Excuse type", + "verbose_name_plural": "Excuse types", + "ordering": ["name"], + }, + managers=[("objects", django.contrib.sites.managers.CurrentSiteManager()),], + ), + migrations.AddField( + model_name="personalnote", + name="excuse_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="alsijil.ExcuseType", + verbose_name="Excuse type", + ), + ), + ] diff --git a/aleksis/apps/alsijil/model_extensions.py b/aleksis/apps/alsijil/model_extensions.py index 57768babbc029e3da42359550120a01fb5cf6517..a09aa38c9be47c6a73e5d1e86caf6f375c9045e1 100644 --- a/aleksis/apps/alsijil/model_extensions.py +++ b/aleksis/apps/alsijil/model_extensions.py @@ -8,7 +8,7 @@ from calendarweek import CalendarWeek from aleksis.apps.chronos.models import LessonPeriod from aleksis.core.models import Group, Person -from .models import LessonDocumentation, PersonalNote +from .models import ExcuseType, LessonDocumentation, PersonalNote @Person.method @@ -18,6 +18,7 @@ def mark_absent( from_period: int = 0, absent: bool = True, excused: bool = False, + excuse_type: Optional[ExcuseType] = None, remarks: str = "", ): """Mark a person absent for all lessons in a day, optionally starting with a selected period number. @@ -44,7 +45,7 @@ def mark_absent( person=self, lesson_period=lesson_period, week=wanted_week.week, - defaults={"absent": absent, "excused": excused}, + defaults={"absent": absent, "excused": excused, "excuse_type": excuse_type}, ) if remarks: diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index eb1bc4eb67f37744e589c8daf9d496ff6913f6d3..d10243b0deabba12b49d4b3d28211be925f80f79 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -8,6 +8,30 @@ def isidentifier(value: str) -> bool: return value.isidentifier() +class ExcuseType(ExtensibleModel): + """An type of excuse. + + Can be used to count different types of absences separately. + """ + + short_name = models.CharField( + max_length=255, unique=True, verbose_name=_("Short name") + ) + name = models.CharField(max_length=255, unique=True, verbose_name=_("Name")) + + def __str__(self): + return f"{self.name} ({self.short_name})" + + @property + def count_label(self): + return f"{self.short_name}_count" + + class Meta: + ordering = ["name"] + verbose_name = _("Excuse type") + verbose_name_plural = _("Excuse types") + + class PersonalNote(ExtensibleModel): """A personal note about a single person. @@ -27,9 +51,21 @@ class PersonalNote(ExtensibleModel): absent = models.BooleanField(default=False) late = models.IntegerField(default=0) excused = models.BooleanField(default=False) + excuse_type = models.ForeignKey( + ExcuseType, + on_delete=models.SET_NULL, + null=True, + blank=True, + verbose_name=_("Excuse type"), + ) remarks = models.CharField(max_length=200, blank=True) + def save(self, *args, **kwargs): + if self.excuse_type: + self.excused = True + super().save(*args, **kwargs) + class Meta: verbose_name = _("Personal note") verbose_name_plural = _("Personal notes") diff --git a/aleksis/apps/alsijil/static/css/alsijil/full_register.css b/aleksis/apps/alsijil/static/css/alsijil/full_register.css index 2c7327003c36e7dde492103eb5ad1b4cb5a65801..9a3dc493aa468c989ed766817436d20b40cd0bff 100644 --- a/aleksis/apps/alsijil/static/css/alsijil/full_register.css +++ b/aleksis/apps/alsijil/static/css/alsijil/full_register.css @@ -1,4 +1,4 @@ -table.small-print { +table.small-print, td.small-print, th.small-print { font-size: 10pt; } @@ -25,7 +25,7 @@ tr.lessons-day-first { border-top: 3px solid rgba(0, 0, 0, 0.3); } -th.lessons-day-head { +th.lessons-day-head, td.rotate, th.rotate { text-align: center; transform: rotate(-90deg); } diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py index bad0dc9f3923421035d0701251012461df13fec8..3a6762c3744ead9d02e9917aa975b530b5e448f0 100644 --- a/aleksis/apps/alsijil/tables.py +++ b/aleksis/apps/alsijil/tables.py @@ -17,3 +17,23 @@ class PersonalNoteFilterTable(tables.Table): text=_("Edit"), attrs={"a": {"class": "btn-flat waves-effect waves-orange"}}, ) + + +class ExcuseTypeTable(tables.Table): + class Meta: + attrs = {"class": "highlight"} + + name = tables.LinkColumn("edit_excuse_type", args=[A("id")]) + short_name = tables.Column() + edit = tables.LinkColumn( + "edit_excuse_type", + args=[A("id")], + text=_("Edit"), + attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}}, + ) + delete = tables.LinkColumn( + "delete_excuse_type", + args=[A("id")], + text=_("Delete"), + attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}}, + ) diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html index 5748e84a69f478a8a26d0a51cb54cdc8d981a992..6545bf9c3a79d8ab1efaf8e7544c30451bc4b508 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html @@ -144,6 +144,7 @@ <th>{% blocktrans %}Absent{% endblocktrans %}</th> <th>{% blocktrans %}Tardiness{% endblocktrans %}</th> <th>{% blocktrans %}Excused{% endblocktrans %}</th> + <th>{% blocktrans %}Excuse type{% endblocktrans %}</th> <th>{% blocktrans %}Remarks{% endblocktrans %}</th> </tr> </thead> @@ -172,6 +173,14 @@ <span></span> </label> </td> + <td> + <div class="input-field"> + {{ form.excuse_type }} + <label for="{{ form.excuse_type.id_for_label }}"> + {% trans "Excuse type" %} + </label> + </div> + </td> <td> <div class="input-field"> {{ form.remarks }} diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html new file mode 100644 index 0000000000000000000000000000000000000000..6fc6faefb2543cecf32b02e7dcb7ea2a40f3d73b --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/create.html @@ -0,0 +1,18 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Create excuse type{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Create excuse type{% endblocktrans %}{% endblock %} + +{% block content %} + {% include "alsijil/excuse_type/warning.html" %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..78396ed66264cc19abdac2085d1cc89ff931bb38 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/edit.html @@ -0,0 +1,17 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Edit excuse type{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit excuse type{% endblocktrans %}{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html new file mode 100644 index 0000000000000000000000000000000000000000..2be1f28c96e70f35e63fe4f5cefb50c232e38e0a --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/list.html @@ -0,0 +1,20 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Excuse types{% endblocktrans %}{% endblock %} + +{% block content %} + {% include "alsijil/excuse_type/warning.html" %} + + <a class="btn green waves-effect waves-light" href="{% url 'create_excuse_type' %}"> + <i class="material-icons left">add</i> + {% trans "Create excuse type" %} + </a> + + {% render_table table %} +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html b/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html new file mode 100644 index 0000000000000000000000000000000000000000..d90d2e8205b1c91c18e74e02654fde3daebc4971 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/excuse_type/warning.html @@ -0,0 +1,10 @@ +{% load i18n %} +<div class="alert warning"> + <p> + <i class="material-icons left">warning</i> + {% blocktrans %} + This function should only be used to define alternatives to the default excuse which also will be counted extra. + Don't use this to create a default excuse or if you don't divide between different types of excuse. + {% endblocktrans %} + </p> +</div> diff --git a/aleksis/apps/alsijil/templates/alsijil/partials/absences.html b/aleksis/apps/alsijil/templates/alsijil/partials/absences.html index 6584142bf36e2828471bbb73367442c80f572aee..6deaa3891c480026b22fbad2cfc75a4f2b260110 100644 --- a/aleksis/apps/alsijil/templates/alsijil/partials/absences.html +++ b/aleksis/apps/alsijil/templates/alsijil/partials/absences.html @@ -1,6 +1,6 @@ {% load i18n %} {% for note in notes %} <span class="{% if note.excused %}green-text{% else %}red-text{% endif %}">{{ note.person }} - {% if note.excused %}{% trans "(e)" %}{% else %}{% trans "(u)" %}{% endif %}{% if not forloop.last %},{% endif %} + {% if note.excused %}{% if note.excuse_type %}({{ note.excuse_type.short_name }}){% else %}{% trans "(e)" %}{% endif %}{% else %}{% trans "(u)" %}{% endif %}{% if not forloop.last %},{% endif %} </span> {% endfor %} diff --git a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html index 6e7c704073c918d4a548fab948e25260bfcc71e9..8cd9938b62e4bd39c0feead34b46fe4360987ec0 100644 --- a/aleksis/apps/alsijil/templates/alsijil/print/full_register.html +++ b/aleksis/apps/alsijil/templates/alsijil/print/full_register.html @@ -66,6 +66,40 @@ <div class="page-break"> </div> + <h4>{% trans "Abbreviations" %}</h4> + + <h5>{% trans "General" %}</h5> + + <ul class="collection"> + <li class="collection-item"> + <strong>(a)</strong> {% trans "Absent" %} + </li> + <li class="collection-item"> + <strong>(b)</strong> {% trans "Late" %} + </li> + <li class="collection-item"> + <strong>(u)</strong> {% trans "Unexcused" %} + </li> + <li class="collection-item"> + <strong>(e)</strong> {% trans "Excused" %} + </li> + </ul> + + {% if excuse_types %} + <h5>{% trans "Custom excuse types" %}</h5> + + <ul class="collection"> + {% for excuse_type in excuse_types %} + <li class="collection-item"> + <strong>({{ excuse_type.short_name }})</strong> {{ excuse_type.name }} + </li> + {% endfor %} + </ul> + {% endif %} + + <div class="page-break"> </div> + + <h4>{% trans 'Persons in group' %} {{ group.name }}</h4> <table id="persons"> @@ -76,9 +110,13 @@ <th>{% trans 'First name' %}</th> <th>{% trans 'Sex' %}</th> <th>{% trans 'Date of birth' %}</th> - <th>{% trans 'Absences' %}</th> - <th>{% trans 'Unexcused' %}</th> - <th>{% trans 'Tard.' %}</th> + <th>{% trans '(a)' %}</th> + <th>{% trans "(e)" %}</th> + {% for excuse_type in excuse_types %} + <th>({{ excuse_type.short_name }})</th> + {% endfor %} + <th>{% trans '(u)' %}</th> + <th>{% trans '(b)' %}</th> </tr> </thead> @@ -91,6 +129,10 @@ <td>{{ person.get_sex_display }}</td> <td>{{ person.date_of_birth }}</td> <td>{{ person.absences_count }}</td> + <td>{{ person.excused }}</td> + {% for excuse_type in excuse_types %} + <td>{{ person|get_dict:excuse_type.count_label }}</td> + {% endfor %} <td>{{ person.unexcused }}</td> <td>{{ person.tardiness }}'</td> </tr> @@ -228,21 +270,28 @@ <h5>{% trans 'Absences and tardiness' %}</h5> <table> - <thead> <tr> - <th>{% trans 'Absences' %}</th> - <th>{% trans 'Unexcused' %}</th> - <th>{% trans 'Tardiness' %}</th> + <th colspan="2">{% trans 'Absences' %}</th> + <td>{{ person.absences_count }}</td> </tr> - </thead> - - <tbody> <tr> - <td>{{ person.absences_count }}</td> + <td rowspan="{{ excuse_types.count|add:2 }}" style="width: 16mm;" + class="rotate small-print">{% trans "thereof" %}</td> + <th>{% trans 'Excused' %}</th> + <td>{{ person.excused }}</td> + </tr> + {% for excuse_type in excuse_types %} + <th>{{ excuse_type.name }}</th> + <td>{{ person|get_dict:excuse_type.count_label }}</td> + {% endfor %} + <tr> + <th>{% trans 'Unexcused' %}</th> <td>{{ person.unexcused }}</td> + </tr> + <tr> + <th colspan="2">{% trans 'Tardiness' %}</th> <td>{{ person.tardiness }}'</td> </tr> - </tbody> </table> <h5>{% trans 'Relevant personal notes' %}</h5> @@ -271,8 +320,12 @@ <td> {% if note.absent %} {% trans 'Yes' %} - {% if note.escused %} - ({% trans 'e' %}) + {% if note.excused %} + {% if note.excuse_type %} + ({{ note.excuse_type.short_name }}) + {% else %} + ({% trans 'e' %}) + {% endif %} {% endif %} {% endif %} </td> @@ -349,8 +402,12 @@ {{ note.person.last_name }}, {{ note.person.first_name|slice:"0:1" }}. {% if note.excused %} <span class="lesson-note-excused"> - ({% trans 'e' %}) - </span> + {% if note.excuse_type %} + ({{ note.excuse_type.short_name }}) + {% else %} + ({% trans 'e' %}) + {% endif %} + </span> {% endif %} </span> {% endif %} @@ -360,8 +417,12 @@ ({{ note.late }}′) {% if note.excused %} <span class="lesson-note-excused"> - ({% trans 'e' %}) - </span> + {% if note.excuse_type %} + ({{ note.excuse_type.short_name }}) + {% else %} + ({% trans 'e' %}) + {% endif %} + </span> {% endif %} </span> {% endif %} diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index 4406a06852ae2489eaf5b84c66a28098d91f0010..89ab0ffe5f47c8cfebb015a6f76e597af4dd11dd 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -41,4 +41,20 @@ urlpatterns = [ views.delete_personal_note_filter, name="delete_personal_note_filter", ), + path("excuse_types/", views.ExcuseTypeListView.as_view(), name="excuse_types"), + path( + "excuse_types/create/", + views.ExcuseTypeCreateView.as_view(), + name="create_excuse_type", + ), + path( + "excuse_types/<int:pk>/edit/", + views.ExcuseTypeEditView.as_view(), + name="edit_excuse_type", + ), + path( + "excuse_types/<int:pk>/delete/", + views.ExcuseTypeDeleteView.as_view(), + name="delete_excuse_type", + ), ] diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 574c2bd06e2482947079334f3274665bbb50a05d..efad0b58c07a8c87cd258e14f728f71c9340786a 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -5,27 +5,31 @@ from django.core.exceptions import PermissionDenied from django.db.models import Count, Exists, F, OuterRef, Q, Subquery, Sum from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse +from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext as _ from calendarweek import CalendarWeek -from django_tables2 import RequestConfig +from django_tables2 import RequestConfig, SingleTableView +from reversion.views import RevisionMixin +from rules.contrib.views import PermissionRequiredMixin from aleksis.apps.chronos.managers import TimetableType from aleksis.apps.chronos.models import LessonPeriod from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk +from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.util import messages from .forms import ( + ExcuseTypeForm, LessonDocumentationForm, PersonalNoteFilterForm, PersonalNoteFormSet, RegisterAbsenceForm, SelectForm, ) -from .models import LessonDocumentation, PersonalNoteFilter -from .tables import PersonalNoteFilterTable +from .models import ExcuseType, LessonDocumentation, PersonalNoteFilter +from .tables import ExcuseTypeTable, PersonalNoteFilterTable def lesson( @@ -99,6 +103,8 @@ def lesson( if lesson_documentation_form.is_valid(): lesson_documentation_form.save() + messages.success(request, _("The lesson documentation has been saved.")) + if personal_note_formset.is_valid(): instances = personal_note_formset.save() @@ -109,8 +115,16 @@ def lesson( lesson_period.period.period + 1, instance.absent, instance.excused, + instance.excuse_type, ) + messages.success(request, _("The personal notes have been saved.")) + + # Regenerate form here to ensure that programmatically changed data will be shown correctly + personal_note_formset = PersonalNoteFormSet( + None, queryset=persons_qs, prefix="personal_notes" + ) + context["lesson_documentation"] = lesson_documentation context["lesson_documentation_form"] = lesson_documentation_form context["personal_note_formset"] = personal_note_formset @@ -324,6 +338,14 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: absences_count=Count( "personal_notes__absent", filter=Q(personal_notes__absent=True) ), + excused=Count( + "personal_notes__absent", + filter=Q( + personal_notes__absent=True, + personal_notes__excused=True, + personal_notes__excuse_type__isnull=True, + ), + ), unexcused=Count( "personal_notes__absent", filter=Q(personal_notes__absent=True, personal_notes__excused=False), @@ -331,6 +353,19 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: tardiness=Sum("personal_notes__late"), ) + 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, + ), + ) + } + ) + # FIXME Move to manager personal_note_filters = PersonalNoteFilter.objects.all() for personal_note_filter in personal_note_filters: @@ -349,6 +384,7 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: context["school_term"] = current_school_term context["persons"] = persons context["personal_note_filters"] = personal_note_filters + context["excuse_types"] = ExcuseType.objects.all() context["group"] = group context["weeks"] = weeks context["periods_by_day"] = periods_by_day @@ -439,3 +475,44 @@ def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse: context["personal_note_filter"] = personal_note_filter return redirect("list_personal_note_filters") + + +class ExcuseTypeListView(SingleTableView, PermissionRequiredMixin): + """Table of all excuse types.""" + + model = ExcuseType + table_class = ExcuseTypeTable + permission_required = "core.view_excusetype" + template_name = "alsijil/excuse_type/list.html" + + +class ExcuseTypeCreateView(AdvancedCreateView, PermissionRequiredMixin): + """Create view for excuse types.""" + + model = ExcuseType + form_class = ExcuseTypeForm + permission_required = "core.create_excusetype" + template_name = "alsijil/excuse_type/create.html" + success_url = reverse_lazy("excuse_types") + success_message = _("The excuse type has been created.") + + +class ExcuseTypeEditView(AdvancedEditView, PermissionRequiredMixin): + """Edit view for excuse types.""" + + model = ExcuseType + form_class = ExcuseTypeForm + permission_required = "core.edit_excusetype" + template_name = "alsijil/excuse_type/edit.html" + success_url = reverse_lazy("excuse_types") + success_message = _("The excuse type has been saved.") + + +class ExcuseTypeDeleteView(AdvancedDeleteView, PermissionRequiredMixin, RevisionMixin): + """Delete view for excuse types""" + + model = ExcuseType + permission_required = "core.delete_excusetype" + template_name = "core/pages/delete.html" + success_url = reverse_lazy("excuse_types") + success_message = _("The excuse type has been deleted.")