Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Alsijil
  • sunweaver/AlekSIS-App-Alsijil
  • 8tincsoVluke/AlekSIS-App-Alsijil
  • perfreicpo/AlekSIS-App-Alsijil
  • noifobarep/AlekSIS-App-Alsijil
  • 7ingannisdo/AlekSIS-App-Alsijil
  • unmruntartpa/AlekSIS-App-Alsijil
  • balrorebta/AlekSIS-App-Alsijil
  • comliFdifwa/AlekSIS-App-Alsijil
  • 3ranaadza/AlekSIS-App-Alsijil
10 results
Show changes
Commits on Source (21)
Showing
with 466 additions and 69 deletions
......@@ -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"]
......@@ -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"],
},
],
}
]
......
# 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",
),
),
]
......@@ -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:
......
......@@ -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")
......
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);
}
......
......@@ -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"}},
)
......@@ -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 }}
......
......@@ -88,15 +88,15 @@
{% blocktrans %}Personal notes{% endblocktrans %}
</span>
{% for person in persons %}
<h5 class="card-title">{{ person.full_name }}</h5>
<h5 class="card-title">{{ person.person.full_name }}</h5>
<p class="card-text">
{% trans "Absent" %}: {{ person.absences_count }}
({{ person.unexcused_count }} {% trans "unexcused" %})
{% trans "Absent" %}: {{ person.person.absences_count }}
({{ person.person.unexcused_count }} {% trans "unexcused" %})
</p>
<p class="card-text">
{% trans "Summed up tardiness" %}: {{ person.tardiness_sum }}'
{% trans "Summed up tardiness" %}: {{ person.person.tardiness_sum }}'
</p>
{% for note in person.personal_notes|only_week:week %}
{% for note in person.personal_notes %}
{% if note.remarks %}
<blockquote>
{{ note.remarks }}
......@@ -118,11 +118,11 @@
<div class="card red darken-1">
<div class="card-content white-text">
<span class="card-title">
{% blocktrans %}No group selected{% endblocktrans %}
{% blocktrans %}No lessons available{% endblocktrans %}
</span>
<p>
{% blocktrans %}
There are no lessons for the selected group, teacher or time.
There are no lessons for the selected group or teacher in this week.
{% endblocktrans %}
</p>
</div>
......
{# -*- 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 %}
{# -*- 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 %}
{# -*- 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 %}
{% 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>
{% 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 %}
......@@ -14,6 +14,8 @@
<div class="center-align">
<h1>{% trans 'Class register' %}</h1>
<h5>{{ school_term }}</h5>
<p>({{ school_term.date_start }}–{{ school_term.date_end }})</p>
{% static "img/aleksis-banner.svg" as aleksis_banner %}
<img src="{% firstof request.site.preferences.theme__logo.url aleksis_banner %}"
alt="{{ request.site.preferences.general__title }} – Logo" class="max-size-600 center">
......@@ -64,6 +66,40 @@
<div class="page-break">&nbsp;</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">&nbsp;</div>
<h4>{% trans 'Persons in group' %} {{ group.name }}</h4>
<table id="persons">
......@@ -74,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>
......@@ -89,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>
......@@ -117,8 +161,8 @@
<tr>
<td>{{ lesson.subject.name }}</td>
<td>{{ lesson.teachers.all|join:', ' }}</td>
<td>{{ lesson.date_start }}</td>
<td>{{ lesson.date_end }}</td>
<td>{{ lesson.validity.date_start }}</td>
<td>{{ lesson.validity.date_end }}</td>
<td>{{ lesson.lesson_periods.count }}</td>
</tr>
{% endfor %}
......@@ -150,8 +194,8 @@
<td>{{ child_group.name }}</td>
<td>{{ lesson.subject.name }}</td>
<td>{{ lesson.teachers.all|join:', ' }}</td>
<td>{{ lesson.date_start }}</td>
<td>{{ lesson.date_end }}</td>
<td>{{ lesson.validity.date_start }}</td>
<td>{{ lesson.validity.date_end }}</td>
<td>{{ lesson.lesson_periods.count }}</td>
</tr>
{% endfor %}
......@@ -226,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>
......@@ -269,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>
......@@ -347,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 %}
......@@ -358,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 %}
......
......@@ -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",
),
]
......@@ -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(
......@@ -39,7 +43,9 @@ def lesson(
if year and week and period_id:
# Get a specific lesson period if provided in URL
wanted_week = CalendarWeek(year=year, week=week)
lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(pk=period_id)
lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(
pk=period_id
)
else:
# Determine current lesson by current date and time
lesson_period = (
......@@ -64,8 +70,7 @@ def lesson(
if (
datetime.combine(
wanted_week[lesson_period.period.weekday],
lesson_period.period.time_start,
wanted_week[lesson_period.period.weekday], lesson_period.period.time_start,
)
> datetime.now()
and not request.user.is_superuser
......@@ -98,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()
......@@ -108,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
......@@ -184,16 +199,25 @@ def week_view(
if lesson_periods:
# Aggregate all personal notes for this group and week
persons = (
Person.objects.filter(is_active=True)
.filter(member_of__lessons__lesson_periods__in=lesson_periods)
.distinct()
lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
persons_qs = Person.objects.filter(is_active=True)
if group:
persons_qs = persons_qs.filter(member_of=group)
else:
persons_qs = persons_qs.filter(
member_of__lessons__lesson_periods__in=lesson_periods_pk
)
persons_qs = (
persons_qs.distinct()
.prefetch_related("personal_notes")
.annotate(
absences_count=Count(
"personal_notes",
filter=Q(
personal_notes__lesson_period__in=lesson_periods,
personal_notes__lesson_period__in=lesson_periods_pk,
personal_notes__week=wanted_week.week,
personal_notes__absent=True,
),
......@@ -202,7 +226,7 @@ def week_view(
unexcused_count=Count(
"personal_notes",
filter=Q(
personal_notes__lesson_period__in=lesson_periods,
personal_notes__lesson_period__in=lesson_periods_pk,
personal_notes__week=wanted_week.week,
personal_notes__absent=True,
personal_notes__excused=False,
......@@ -212,7 +236,7 @@ def week_view(
tardiness_sum=Subquery(
Person.objects.filter(
pk=OuterRef("pk"),
personal_notes__lesson_period__in=lesson_periods,
personal_notes__lesson_period__in=lesson_periods_pk,
personal_notes__week=wanted_week.week,
)
.distinct()
......@@ -221,6 +245,17 @@ def week_view(
),
)
)
persons = []
for person in persons_qs:
persons.append(
{
"person": person,
"personal_notes": person.personal_notes.filter(
week=wanted_week.week, lesson_period__in=lesson_periods_pk
),
}
)
else:
persons = None
......@@ -276,7 +311,11 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
for week in weeks:
day = week[lesson_period.period.weekday]
if lesson_period.lesson.validity.date_start <= day <= lesson_period.lesson.validity.date_end:
if (
lesson_period.lesson.validity.date_start
<= day
<= lesson_period.lesson.validity.date_end
):
documentations = list(
filter(
lambda d: d.week == week.week,
......@@ -299,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),
......@@ -306,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:
......@@ -321,8 +381,10 @@ 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
......@@ -413,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.")
......@@ -68,11 +68,11 @@ extras = ["phonenumbers"]
version = ">=3.0,<4.0"
[package.dependencies.django-two-factor-auth]
extras = ["sms", "yubikey", "call", "phonenumbers"]
extras = ["sms", "phonenumbers", "call", "yubikey"]
version = ">=1.11.0,<2.0.0"
[package.dependencies.dynaconf]
extras = ["yaml", "ini", "toml"]
extras = ["yaml", "toml", "ini"]
version = ">=2.0,<3.0"
[package.extras]
......@@ -1112,14 +1112,12 @@ category = "dev"
description = "A Python utility / library to sort Python imports."
name = "isort"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "4.3.21"
python-versions = ">=3.6,<4.0"
version = "5.0.3"
[package.extras]
pipfile = ["pipreqs", "requirementslib"]
pyproject = ["toml"]
requirements = ["pipreqs", "pip-api"]
xdg_home = ["appdirs (>=1.4.0)"]
pipfile_deprecated_finder = ["pipreqs", "requirementslib", "tomlkit (>=0.5.3)"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
[[package]]
category = "dev"
......@@ -1949,7 +1947,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
content-hash = "14697a0280b9caecec47c1bb76bc7c2a18fa04b715560b3d5699cf2efdce67f5"
content-hash = "c948e61801e83c7105de786b85616a1b61957e6950cd649039bd67f87193559c"
python-versions = "^3.7"
[metadata.files]
......@@ -2319,8 +2317,8 @@ importlib-metadata = [
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
]
isort = [
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
{file = "isort-5.0.3-py3-none-any.whl", hash = "sha256:3fbfad425b0a08a2969c5e1821d88785c210a08656c029c28931a1620f2d0f12"},
{file = "isort-5.0.3.tar.gz", hash = "sha256:4c48d4cd773a6226baaaa176839e6f7ff82ef7c7842f6c54374fe2b14df4024b"},
]
jinja2 = [
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
......
......@@ -44,7 +44,7 @@ flake8-docstrings = "^1.5.0"
flake8-rst-docstrings = "^0.0.13"
black = "^19.10b0"
flake8-black = "^0.2.0"
isort = "^4.3.21"
isort = "^5.0.0"
flake8-isort = "^3.0.0"
pytest-cov = "^2.8.1"
pytest-sugar = "^0.9.2"
......