Skip to content
Snippets Groups Projects
Commit 9cff3804 authored by Hangzhi Yu's avatar Hangzhi Yu
Browse files

Merge branch 'master' into 73-add-rules-and-permissions

# Conflicts:
#	aleksis/apps/alsijil/templates/alsijil/class_register/lesson.html
#	aleksis/apps/alsijil/views.py
parents 0292d973 579f7532
No related branches found
No related tags found
1 merge request!49Resolve "Add rules and permissions"
Showing
with 620 additions and 192 deletions
......@@ -11,13 +11,13 @@ 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):
class Meta:
model = LessonDocumentation
fields = ["topic", "homework"]
fields = ["topic", "homework", "group_note"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
......@@ -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,12 +47,7 @@ class SelectForm(forms.Form):
layout = Layout(Row("group", "teacher"))
group = forms.ModelChoiceField(
queryset=Group.objects.annotate(lessons_count=Count("lessons")).filter(
lessons_count__gt=0
),
label=_("Group"),
required=False,
widget=Select2Widget,
queryset=None, label=_("Group"), required=False, widget=Select2Widget,
)
teacher = forms.ModelChoiceField(
queryset=Person.objects.annotate(
......@@ -81,6 +76,14 @@ class SelectForm(forms.Form):
data["instance"] = instance
return data
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)
)
PersonalNoteFormSet = forms.modelformset_factory(
PersonalNote, form=PersonalNoteForm, max_num=0, extra=0
......@@ -112,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"]
......@@ -56,6 +56,12 @@ MENUS = {
),
],
},
{
"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",
),
),
]
# Generated by Django 3.0.8 on 2020-07-10 16:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('alsijil', '0002_excuse_type'),
]
operations = [
migrations.AddField(
model_name='lessondocumentation',
name='group_note',
field=models.CharField(blank=True, max_length=200, verbose_name='Group note'),
),
]
......@@ -9,7 +9,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
......@@ -19,6 +19,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.
......@@ -45,7 +46,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,15 +51,27 @@ 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")
unique_together = [["lesson_period", "week", "person"]]
ordering = [
"lesson_period__lesson__date_start",
"lesson_period__lesson__validity__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
......@@ -57,13 +93,14 @@ class LessonDocumentation(ExtensibleModel):
topic = models.CharField(verbose_name=_("Lesson topic"), max_length=200, blank=True)
homework = models.CharField(verbose_name=_("Homework"), max_length=200, blank=True)
group_note = models.CharField(verbose_name=_("Group note"), max_length=200, blank=True)
class Meta:
verbose_name = _("Lesson documentation")
verbose_name_plural = _("Lesson documentations")
unique_together = [["lesson_period", "week"]]
ordering = [
"lesson_period__lesson__date_start",
"lesson_period__lesson__validity__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
......
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"}},
)
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load week_helpers material_form i18n static rules %}
{% load week_helpers %}
{% load material_form i18n static rules %}
{% block browser_title %}{% blocktrans %}Lesson{% endblocktrans %}{% endblock %}
......@@ -61,6 +62,7 @@
</div>
</div>
{% csrf_token %}
<div class="row">
<div class="col s12 m12 l6 xl8">
{% with prev_lesson=lesson_period.prev prev_doc=prev_lesson.get_lesson_documentation %}
......@@ -90,6 +92,13 @@
</tr>
{% endif %}
{% if prev_doc.group_note %}
<tr>
<th class="collection-item">{% trans "Group notes for previous lesson:" %}</th>
<td>{{ prev_doc.group_note }}</td>
</tr>
{% endif %}
{% if absences %}
<tr>
<th>{% trans "Absent persons:" %}</th>
......@@ -172,6 +181,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>
......@@ -201,6 +211,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 }}
......@@ -209,16 +227,17 @@
</label>
</div>
</td>
</tr>
{% else %}
<tr>
<td>{{ form.person_name.value }}</td>
<td>{{ form.absent.value }}</td>
<td>{{ form.late.value }}</td>
<td>{{ form.excused.value }}</td>
<td>{{ form.remarks.value }}</td>
</tr>
{% endif %}
</tr>
{% else %}
<tr>
<td>{{ form.person_name.value }}</td>
<td>{{ form.absent.value }}</td>
<td>{{ form.late.value }}</td>
<td>{{ form.excused.value }}</td>
<td>{{ form.excuse_type.value }}</td>
<td>{{ form.remarks.value }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
......
......@@ -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 %}
{% extends "core/base_print.html" %}
{% load static i18n cropping data_helpers week_helpers %}
{% load static i18n data_helpers week_helpers %}
{% block page_title %}
{% trans "Class register:" %} {{ group.name }}
......@@ -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 %}
......@@ -169,8 +213,7 @@
<tr>
<td rowspan="6" class="person-img">
{% if person.photo %}
<img src="{% cropped_thumbnail person 'photo_cropping' max_size='300x400' %}"
alt="{{ person.first_name }} {{ person.last_name }}"/>
<img src="{{ person.photo.url }}" alt="{{ person.first_name }} {{ person.last_name }}"/>
{% else %}
<img src="{% static 'img/fallback.png' %}" alt="{{ person.first_name }} {{ person.last_name }}"/>
{% endif %}
......@@ -227,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>
......@@ -270,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>
......@@ -342,14 +396,19 @@
</td>
<td class="lesson-homework">{{ documentations.0.homework }}</td>
<td class="lesson-notes">
{{ documentations.0.group_note }}
{% for note in notes %}
{% if note.absent %}
<span class="lesson-note-absent">
{{ 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 %}
......@@ -359,8 +418,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",
),
]
......@@ -2,31 +2,36 @@ from datetime import date, datetime, timedelta
from typing import Optional
from django.core.exceptions import PermissionDenied
from django.db.models import Count, Exists, 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.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 rules.contrib.views import permission_required
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 aleksis.core.util.core_helpers import objectgetter_optional
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
from .util.alsijil_helpers import get_instance_by_pk, get_lesson_period_by_pk
......@@ -66,8 +71,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
......@@ -105,6 +109,8 @@ def lesson(
):
lesson_documentation_form.save()
messages.success(request, _("The lesson documentation has been saved."))
if personal_note_formset.is_valid() and request.user.has_perm(
"alsijil.edit_personalnote", lesson_period
):
......@@ -117,8 +123,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
......@@ -196,14 +210,19 @@ def week_view(
# Aggregate all personal notes for this group and week
lesson_periods_pk = list(lesson_periods.values_list("pk", flat=True))
persons = Person.objects.filter(is_active=True)
persons_qs = Person.objects.filter(is_active=True)
if not request.user.has_perm("alsijil.view_week_personalnote", instance):
persons = persons.filter(pk=request.user.pk)
persons_qs = persons_qs.filter(pk=request.user.pk)
elif 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 = (
persons.filter(member_of__lessons__lesson_periods__in=lesson_periods_pk)
.distinct()
persons_qs = (
persons_qs.distinct()
.prefetch_related("personal_notes")
.annotate(
absences_count=Count(
......@@ -237,6 +256,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
......@@ -295,7 +325,11 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
for week in weeks:
day = week[lesson_period.period.weekday]
if lesson_period.lesson.date_start <= day <= lesson_period.lesson.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,
......@@ -318,6 +352,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),
......@@ -325,6 +367,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:
......@@ -340,8 +395,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
......@@ -408,8 +465,8 @@ def edit_personal_note_filter(
) -> HttpResponse:
context = {}
if id:
personal_note_filter = PersonalNoteFilter.objects.get(id=id)
if id_:
personal_note_filter = PersonalNoteFilter.objects.get(id=id_)
context["personal_note_filter"] = personal_note_filter
personal_note_filter_form = PersonalNoteFilterForm(
request.POST or None, instance=personal_note_filter
......@@ -444,3 +501,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.")
This diff is collapsed.
......@@ -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"
......
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