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 (17)
Showing
with 447 additions and 53 deletions
...@@ -11,7 +11,7 @@ from material import Layout, Row ...@@ -11,7 +11,7 @@ from material import Layout, Row
from aleksis.apps.chronos.managers import TimetableType from aleksis.apps.chronos.managers import TimetableType
from aleksis.core.models import Group, Person from aleksis.core.models import Group, Person
from .models import LessonDocumentation, PersonalNote, PersonalNoteFilter from .models import ExcuseType, LessonDocumentation, PersonalNote, PersonalNoteFilter
class LessonDocumentationForm(forms.ModelForm): class LessonDocumentationForm(forms.ModelForm):
...@@ -28,7 +28,7 @@ class LessonDocumentationForm(forms.ModelForm): ...@@ -28,7 +28,7 @@ class LessonDocumentationForm(forms.ModelForm):
class PersonalNoteForm(forms.ModelForm): class PersonalNoteForm(forms.ModelForm):
class Meta: class Meta:
model = PersonalNote model = PersonalNote
fields = ["absent", "late", "excused", "remarks"] fields = ["absent", "late", "excused", "excuse_type", "remarks"]
person_name = forms.CharField(disabled=True) person_name = forms.CharField(disabled=True)
...@@ -47,10 +47,7 @@ class SelectForm(forms.Form): ...@@ -47,10 +47,7 @@ class SelectForm(forms.Form):
layout = Layout(Row("group", "teacher")) layout = Layout(Row("group", "teacher"))
group = forms.ModelChoiceField( group = forms.ModelChoiceField(
queryset=None, queryset=None, label=_("Group"), required=False, widget=Select2Widget,
label=_("Group"),
required=False,
widget=Select2Widget,
) )
teacher = forms.ModelChoiceField( teacher = forms.ModelChoiceField(
queryset=Person.objects.annotate( queryset=Person.objects.annotate(
...@@ -81,8 +78,10 @@ class SelectForm(forms.Form): ...@@ -81,8 +78,10 @@ class SelectForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields["group"].queryset = Group.objects.for_current_school_term_or_all().annotate(lessons_count=Count("lessons")).filter( self.fields["group"].queryset = (
lessons_count__gt=0 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): ...@@ -116,3 +115,11 @@ class PersonalNoteFilterForm(forms.ModelForm):
class Meta: class Meta:
model = PersonalNoteFilter model = PersonalNoteFilter
fields = ["identifier", "description", "regex"] 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 = { ...@@ -36,6 +36,12 @@ MENUS = {
"icon": "filter_list", "icon": "filter_list",
"validators": ["menu_generator.validators.is_superuser"], "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 ...@@ -8,7 +8,7 @@ from calendarweek import CalendarWeek
from aleksis.apps.chronos.models import LessonPeriod from aleksis.apps.chronos.models import LessonPeriod
from aleksis.core.models import Group, Person from aleksis.core.models import Group, Person
from .models import LessonDocumentation, PersonalNote from .models import ExcuseType, LessonDocumentation, PersonalNote
@Person.method @Person.method
...@@ -18,6 +18,7 @@ def mark_absent( ...@@ -18,6 +18,7 @@ def mark_absent(
from_period: int = 0, from_period: int = 0,
absent: bool = True, absent: bool = True,
excused: bool = False, excused: bool = False,
excuse_type: Optional[ExcuseType] = None,
remarks: str = "", remarks: str = "",
): ):
"""Mark a person absent for all lessons in a day, optionally starting with a selected period number. """Mark a person absent for all lessons in a day, optionally starting with a selected period number.
...@@ -44,7 +45,7 @@ def mark_absent( ...@@ -44,7 +45,7 @@ def mark_absent(
person=self, person=self,
lesson_period=lesson_period, lesson_period=lesson_period,
week=wanted_week.week, week=wanted_week.week,
defaults={"absent": absent, "excused": excused}, defaults={"absent": absent, "excused": excused, "excuse_type": excuse_type},
) )
if remarks: if remarks:
......
...@@ -8,6 +8,30 @@ def isidentifier(value: str) -> bool: ...@@ -8,6 +8,30 @@ def isidentifier(value: str) -> bool:
return value.isidentifier() 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): class PersonalNote(ExtensibleModel):
"""A personal note about a single person. """A personal note about a single person.
...@@ -27,9 +51,21 @@ class PersonalNote(ExtensibleModel): ...@@ -27,9 +51,21 @@ class PersonalNote(ExtensibleModel):
absent = models.BooleanField(default=False) absent = models.BooleanField(default=False)
late = models.IntegerField(default=0) late = models.IntegerField(default=0)
excused = models.BooleanField(default=False) 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) 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: class Meta:
verbose_name = _("Personal note") verbose_name = _("Personal note")
verbose_name_plural = _("Personal notes") verbose_name_plural = _("Personal notes")
......
table.small-print { table.small-print, td.small-print, th.small-print {
font-size: 10pt; font-size: 10pt;
} }
...@@ -25,7 +25,7 @@ tr.lessons-day-first { ...@@ -25,7 +25,7 @@ tr.lessons-day-first {
border-top: 3px solid rgba(0, 0, 0, 0.3); 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; text-align: center;
transform: rotate(-90deg); transform: rotate(-90deg);
} }
......
...@@ -17,3 +17,23 @@ class PersonalNoteFilterTable(tables.Table): ...@@ -17,3 +17,23 @@ class PersonalNoteFilterTable(tables.Table):
text=_("Edit"), text=_("Edit"),
attrs={"a": {"class": "btn-flat waves-effect waves-orange"}}, 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 @@ ...@@ -144,6 +144,7 @@
<th>{% blocktrans %}Absent{% endblocktrans %}</th> <th>{% blocktrans %}Absent{% endblocktrans %}</th>
<th>{% blocktrans %}Tardiness{% endblocktrans %}</th> <th>{% blocktrans %}Tardiness{% endblocktrans %}</th>
<th>{% blocktrans %}Excused{% endblocktrans %}</th> <th>{% blocktrans %}Excused{% endblocktrans %}</th>
<th>{% blocktrans %}Excuse type{% endblocktrans %}</th>
<th>{% blocktrans %}Remarks{% endblocktrans %}</th> <th>{% blocktrans %}Remarks{% endblocktrans %}</th>
</tr> </tr>
</thead> </thead>
...@@ -172,6 +173,14 @@ ...@@ -172,6 +173,14 @@
<span></span> <span></span>
</label> </label>
</td> </td>
<td>
<div class="input-field">
{{ form.excuse_type }}
<label for="{{ form.excuse_type.id_for_label }}">
{% trans "Excuse type" %}
</label>
</div>
</td>
<td> <td>
<div class="input-field"> <div class="input-field">
{{ form.remarks }} {{ form.remarks }}
......
...@@ -88,15 +88,15 @@ ...@@ -88,15 +88,15 @@
{% blocktrans %}Personal notes{% endblocktrans %} {% blocktrans %}Personal notes{% endblocktrans %}
</span> </span>
{% for person in persons %} {% 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"> <p class="card-text">
{% trans "Absent" %}: {{ person.absences_count }} {% trans "Absent" %}: {{ person.person.absences_count }}
({{ person.unexcused_count }} {% trans "unexcused" %}) ({{ person.person.unexcused_count }} {% trans "unexcused" %})
</p> </p>
<p class="card-text"> <p class="card-text">
{% trans "Summed up tardiness" %}: {{ person.tardiness_sum }}' {% trans "Summed up tardiness" %}: {{ person.person.tardiness_sum }}'
</p> </p>
{% for note in person.personal_notes|only_week:week %} {% for note in person.personal_notes %}
{% if note.remarks %} {% if note.remarks %}
<blockquote> <blockquote>
{{ note.remarks }} {{ note.remarks }}
...@@ -118,11 +118,11 @@ ...@@ -118,11 +118,11 @@
<div class="card red darken-1"> <div class="card red darken-1">
<div class="card-content white-text"> <div class="card-content white-text">
<span class="card-title"> <span class="card-title">
{% blocktrans %}No group selected{% endblocktrans %} {% blocktrans %}No lessons available{% endblocktrans %}
</span> </span>
<p> <p>
{% blocktrans %} {% 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 %} {% endblocktrans %}
</p> </p>
</div> </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 %} {% load i18n %}
{% for note in notes %} {% for note in notes %}
<span class="{% if note.excused %}green-text{% else %}red-text{% endif %}">{{ note.person }} <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> </span>
{% endfor %} {% endfor %}
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
<div class="center-align"> <div class="center-align">
<h1>{% trans 'Class register' %}</h1> <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 %} {% static "img/aleksis-banner.svg" as aleksis_banner %}
<img src="{% firstof request.site.preferences.theme__logo.url 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"> alt="{{ request.site.preferences.general__title }} – Logo" class="max-size-600 center">
...@@ -64,6 +66,40 @@ ...@@ -64,6 +66,40 @@
<div class="page-break">&nbsp;</div> <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> <h4>{% trans 'Persons in group' %} {{ group.name }}</h4>
<table id="persons"> <table id="persons">
...@@ -74,9 +110,13 @@ ...@@ -74,9 +110,13 @@
<th>{% trans 'First name' %}</th> <th>{% trans 'First name' %}</th>
<th>{% trans 'Sex' %}</th> <th>{% trans 'Sex' %}</th>
<th>{% trans 'Date of birth' %}</th> <th>{% trans 'Date of birth' %}</th>
<th>{% trans 'Absences' %}</th> <th>{% trans '(a)' %}</th>
<th>{% trans 'Unexcused' %}</th> <th>{% trans "(e)" %}</th>
<th>{% trans 'Tard.' %}</th> {% for excuse_type in excuse_types %}
<th>({{ excuse_type.short_name }})</th>
{% endfor %}
<th>{% trans '(u)' %}</th>
<th>{% trans '(b)' %}</th>
</tr> </tr>
</thead> </thead>
...@@ -89,6 +129,10 @@ ...@@ -89,6 +129,10 @@
<td>{{ person.get_sex_display }}</td> <td>{{ person.get_sex_display }}</td>
<td>{{ person.date_of_birth }}</td> <td>{{ person.date_of_birth }}</td>
<td>{{ person.absences_count }}</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.unexcused }}</td>
<td>{{ person.tardiness }}'</td> <td>{{ person.tardiness }}'</td>
</tr> </tr>
...@@ -117,8 +161,8 @@ ...@@ -117,8 +161,8 @@
<tr> <tr>
<td>{{ lesson.subject.name }}</td> <td>{{ lesson.subject.name }}</td>
<td>{{ lesson.teachers.all|join:', ' }}</td> <td>{{ lesson.teachers.all|join:', ' }}</td>
<td>{{ lesson.date_start }}</td> <td>{{ lesson.validity.date_start }}</td>
<td>{{ lesson.date_end }}</td> <td>{{ lesson.validity.date_end }}</td>
<td>{{ lesson.lesson_periods.count }}</td> <td>{{ lesson.lesson_periods.count }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -150,8 +194,8 @@ ...@@ -150,8 +194,8 @@
<td>{{ child_group.name }}</td> <td>{{ child_group.name }}</td>
<td>{{ lesson.subject.name }}</td> <td>{{ lesson.subject.name }}</td>
<td>{{ lesson.teachers.all|join:', ' }}</td> <td>{{ lesson.teachers.all|join:', ' }}</td>
<td>{{ lesson.date_start }}</td> <td>{{ lesson.validity.date_start }}</td>
<td>{{ lesson.date_end }}</td> <td>{{ lesson.validity.date_end }}</td>
<td>{{ lesson.lesson_periods.count }}</td> <td>{{ lesson.lesson_periods.count }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
...@@ -226,21 +270,28 @@ ...@@ -226,21 +270,28 @@
<h5>{% trans 'Absences and tardiness' %}</h5> <h5>{% trans 'Absences and tardiness' %}</h5>
<table> <table>
<thead>
<tr> <tr>
<th>{% trans 'Absences' %}</th> <th colspan="2">{% trans 'Absences' %}</th>
<th>{% trans 'Unexcused' %}</th> <td>{{ person.absences_count }}</td>
<th>{% trans 'Tardiness' %}</th>
</tr> </tr>
</thead>
<tbody>
<tr> <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> <td>{{ person.unexcused }}</td>
</tr>
<tr>
<th colspan="2">{% trans 'Tardiness' %}</th>
<td>{{ person.tardiness }}'</td> <td>{{ person.tardiness }}'</td>
</tr> </tr>
</tbody>
</table> </table>
<h5>{% trans 'Relevant personal notes' %}</h5> <h5>{% trans 'Relevant personal notes' %}</h5>
...@@ -269,8 +320,12 @@ ...@@ -269,8 +320,12 @@
<td> <td>
{% if note.absent %} {% if note.absent %}
{% trans 'Yes' %} {% trans 'Yes' %}
{% if note.escused %} {% if note.excused %}
({% trans 'e' %}) {% if note.excuse_type %}
({{ note.excuse_type.short_name }})
{% else %}
({% trans 'e' %})
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
...@@ -347,8 +402,12 @@ ...@@ -347,8 +402,12 @@
{{ note.person.last_name }}, {{ note.person.first_name|slice:"0:1" }}. {{ note.person.last_name }}, {{ note.person.first_name|slice:"0:1" }}.
{% if note.excused %} {% if note.excused %}
<span class="lesson-note-excused"> <span class="lesson-note-excused">
({% trans 'e' %}) {% if note.excuse_type %}
</span> ({{ note.excuse_type.short_name }})
{% else %}
({% trans 'e' %})
{% endif %}
</span>
{% endif %} {% endif %}
</span> </span>
{% endif %} {% endif %}
...@@ -358,8 +417,12 @@ ...@@ -358,8 +417,12 @@
({{ note.late }}′) ({{ note.late }}′)
{% if note.excused %} {% if note.excused %}
<span class="lesson-note-excused"> <span class="lesson-note-excused">
({% trans 'e' %}) {% if note.excuse_type %}
</span> ({{ note.excuse_type.short_name }})
{% else %}
({% trans 'e' %})
{% endif %}
</span>
{% endif %} {% endif %}
</span> </span>
{% endif %} {% endif %}
......
...@@ -41,4 +41,20 @@ urlpatterns = [ ...@@ -41,4 +41,20 @@ urlpatterns = [
views.delete_personal_note_filter, views.delete_personal_note_filter,
name="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 ...@@ -5,27 +5,31 @@ from django.core.exceptions import PermissionDenied
from django.db.models import Count, Exists, F, OuterRef, Q, Subquery, Sum from django.db.models import Count, Exists, F, OuterRef, Q, Subquery, Sum
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect, render 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 django.utils.translation import ugettext as _
from calendarweek import CalendarWeek 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.managers import TimetableType
from aleksis.apps.chronos.models import LessonPeriod from aleksis.apps.chronos.models import LessonPeriod
from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk 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.models import Group, Person, SchoolTerm
from aleksis.core.util import messages from aleksis.core.util import messages
from .forms import ( from .forms import (
ExcuseTypeForm,
LessonDocumentationForm, LessonDocumentationForm,
PersonalNoteFilterForm, PersonalNoteFilterForm,
PersonalNoteFormSet, PersonalNoteFormSet,
RegisterAbsenceForm, RegisterAbsenceForm,
SelectForm, SelectForm,
) )
from .models import LessonDocumentation, PersonalNoteFilter from .models import ExcuseType, LessonDocumentation, PersonalNoteFilter
from .tables import PersonalNoteFilterTable from .tables import ExcuseTypeTable, PersonalNoteFilterTable
def lesson( def lesson(
...@@ -99,6 +103,8 @@ def lesson( ...@@ -99,6 +103,8 @@ def lesson(
if lesson_documentation_form.is_valid(): if lesson_documentation_form.is_valid():
lesson_documentation_form.save() lesson_documentation_form.save()
messages.success(request, _("The lesson documentation has been saved."))
if personal_note_formset.is_valid(): if personal_note_formset.is_valid():
instances = personal_note_formset.save() instances = personal_note_formset.save()
...@@ -109,8 +115,16 @@ def lesson( ...@@ -109,8 +115,16 @@ def lesson(
lesson_period.period.period + 1, lesson_period.period.period + 1,
instance.absent, instance.absent,
instance.excused, 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"] = lesson_documentation
context["lesson_documentation_form"] = lesson_documentation_form context["lesson_documentation_form"] = lesson_documentation_form
context["personal_note_formset"] = personal_note_formset context["personal_note_formset"] = personal_note_formset
...@@ -185,16 +199,25 @@ def week_view( ...@@ -185,16 +199,25 @@ def week_view(
if lesson_periods: if lesson_periods:
# Aggregate all personal notes for this group and week # Aggregate all personal notes for this group and week
persons = ( lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
Person.objects.filter(is_active=True)
.filter(member_of__lessons__lesson_periods__in=lesson_periods) persons_qs = Person.objects.filter(is_active=True)
.distinct()
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") .prefetch_related("personal_notes")
.annotate( .annotate(
absences_count=Count( absences_count=Count(
"personal_notes", "personal_notes",
filter=Q( 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__week=wanted_week.week,
personal_notes__absent=True, personal_notes__absent=True,
), ),
...@@ -203,7 +226,7 @@ def week_view( ...@@ -203,7 +226,7 @@ def week_view(
unexcused_count=Count( unexcused_count=Count(
"personal_notes", "personal_notes",
filter=Q( 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__week=wanted_week.week,
personal_notes__absent=True, personal_notes__absent=True,
personal_notes__excused=False, personal_notes__excused=False,
...@@ -213,7 +236,7 @@ def week_view( ...@@ -213,7 +236,7 @@ def week_view(
tardiness_sum=Subquery( tardiness_sum=Subquery(
Person.objects.filter( Person.objects.filter(
pk=OuterRef("pk"), pk=OuterRef("pk"),
personal_notes__lesson_period__in=lesson_periods, personal_notes__lesson_period__in=lesson_periods_pk,
personal_notes__week=wanted_week.week, personal_notes__week=wanted_week.week,
) )
.distinct() .distinct()
...@@ -222,6 +245,17 @@ def week_view( ...@@ -222,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: else:
persons = None persons = None
...@@ -304,6 +338,14 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -304,6 +338,14 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
absences_count=Count( absences_count=Count(
"personal_notes__absent", filter=Q(personal_notes__absent=True) "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( unexcused=Count(
"personal_notes__absent", "personal_notes__absent",
filter=Q(personal_notes__absent=True, personal_notes__excused=False), filter=Q(personal_notes__absent=True, personal_notes__excused=False),
...@@ -311,6 +353,19 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -311,6 +353,19 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
tardiness=Sum("personal_notes__late"), 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 # FIXME Move to manager
personal_note_filters = PersonalNoteFilter.objects.all() personal_note_filters = PersonalNoteFilter.objects.all()
for personal_note_filter in personal_note_filters: for personal_note_filter in personal_note_filters:
...@@ -326,8 +381,10 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -326,8 +381,10 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
} }
) )
context["school_term"] = current_school_term
context["persons"] = persons context["persons"] = persons
context["personal_note_filters"] = personal_note_filters context["personal_note_filters"] = personal_note_filters
context["excuse_types"] = ExcuseType.objects.all()
context["group"] = group context["group"] = group
context["weeks"] = weeks context["weeks"] = weeks
context["periods_by_day"] = periods_by_day context["periods_by_day"] = periods_by_day
...@@ -418,3 +475,44 @@ def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -418,3 +475,44 @@ def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse:
context["personal_note_filter"] = personal_note_filter context["personal_note_filter"] = personal_note_filter
return redirect("list_personal_note_filters") 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.")