Newer
Older
from typing import Optional
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, reverse_lazy
from django.utils.translation import ugettext as _
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
LessonDocumentationForm,
PersonalNoteFilterForm,
PersonalNoteFormSet,
RegisterAbsenceForm,
SelectForm,
)
from .models import ExtraMark, LessonDocumentation, PersonalNoteFilter
from .tables import ExtraMarkTable, PersonalNoteFilterTable
def lesson(
request: HttpRequest,
year: Optional[int] = None,
week: Optional[int] = None,
period_id: Optional[int] = None,
) -> HttpResponse:
context = {}
if year and week and period_id:
wanted_week = CalendarWeek(year=year, week=week)
lesson_period = LessonPeriod.objects.annotate_week(wanted_week).get(
pk=period_id
)
lesson_period = (
LessonPeriod.objects.at_time().filter_teacher(request.user.person).first()
)
wanted_week = CalendarWeek()
if lesson_period:
"lesson_by_week_and_period",
wanted_week.year,
wanted_week.week,
lesson_period.pk,
"You either selected an invalid lesson or "
"there is currently no lesson in progress."
wanted_week[lesson_period.period.weekday], lesson_period.period.time_start,
)
> datetime.now()
and not request.user.is_superuser
):
raise PermissionDenied(
_(
"You are not allowed to create a lesson documentation for a lesson in the future."
)
context["lesson_period"] = lesson_period
context["week"] = wanted_week
context["day"] = wanted_week[lesson_period.period.weekday]
# Create or get lesson documentation object; can be empty when first opening lesson

Jonathan Weth
committed
lesson_documentation = lesson_period.get_or_create_lesson_documentation(wanted_week)
lesson_documentation_form = LessonDocumentationForm(
request.POST or None,
instance=lesson_documentation,
prefix="lesson_documentation",
# Create a formset that holds all personal notes for all persons in this lesson
persons_qs = lesson_period.get_personal_notes(wanted_week)
personal_note_formset = PersonalNoteFormSet(
request.POST or None, queryset=persons_qs, prefix="personal_notes"
)
if lesson_documentation_form.is_valid():
lesson_documentation_form.save()
if personal_note_formset.is_valid():
instances = personal_note_formset.save()
# Iterate over personal notes and carry changed absences to following lessons
for instance in instances:
instance.person.mark_absent(
wanted_week[lesson_period.period.weekday],
instance.absent,
context["lesson_documentation"] = lesson_documentation
context["lesson_documentation_form"] = lesson_documentation_form
context["personal_note_formset"] = personal_note_formset
return render(request, "alsijil/class_register/lesson.html", context)
request: HttpRequest,
year: Optional[int] = None,
week: Optional[int] = None,
type_: Optional[str] = None,
id_: Optional[int] = None,
context = {}
if year and week:
wanted_week = CalendarWeek(year=year, week=week)
else:
wanted_week = CalendarWeek()
lesson_periods = LessonPeriod.objects.annotate(
has_documentation=Exists(
LessonDocumentation.objects.filter(
~Q(topic__exact=""), lesson_period=OuterRef("pk"), week=wanted_week.week
)
)
group = None
if type_ and id_:
instance = get_el_by_pk(request, type_, id_)
if isinstance(instance, HttpResponseNotFound):
return HttpResponseNotFound()
type_ = TimetableType.from_string(type_)
if type_ == TimetableType.GROUP:
group = instance
lesson_periods = lesson_periods.filter_from_type(type_, instance)
elif hasattr(request, "user") and hasattr(request.user, "person"):
instance = request.user.person
if request.user.person.lessons_as_teacher.exists():
lesson_periods = lesson_periods.filter_teacher(request.user.person)
else:
lesson_periods = lesson_periods.filter_participant(request.user.person)
# Add a form to filter the view
if type_:
initial = {type_.value: instance}
else:
initial = {}
select_form = SelectForm(request.POST or None, initial=initial)
if request.method == "POST":
if select_form.is_valid():
if "type_" not in select_form.cleaned_data:
return redirect("week_view_by_week", wanted_week.year, wanted_week.week)
else:
return redirect(
"week_view_by_week",
wanted_week.year,
wanted_week.week,
select_form.cleaned_data["type_"].value,
select_form.cleaned_data["instance"].pk,
)
# 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()
.prefetch_related("personal_notes")
.annotate(
"personal_notes",
personal_notes__lesson_period__in=lesson_periods,
personal_notes__week=wanted_week.week,
personal_notes__absent=True,
),
distinct=True,
"personal_notes",
personal_notes__lesson_period__in=lesson_periods,
personal_notes__week=wanted_week.week,
personal_notes__absent=True,
personal_notes__excused=False,
),
distinct=True,
tardiness_sum=Subquery(
Person.objects.filter(
pk=OuterRef("pk"),
personal_notes__lesson_period__in=lesson_periods,
personal_notes__week=wanted_week.week,
)
.distinct()
.annotate(tardiness_sum=Sum("personal_notes__late"))
.values("tardiness_sum")
for extra_mark in ExtraMark.objects.all():
persons = persons.annotate(
**{
extra_mark.count_label: Count(
"personal_notes",
filter=Q(
personal_notes__lesson_period__in=lesson_periods,
personal_notes__week=wanted_week.week,
personal_notes__extra_marks=extra_mark,
),
distinct=True,
)
}
)
else:
persons = None
# Resort lesson periods manually because an union queryset doesn't support order_by
lesson_periods = sorted(
lesson_periods, key=lambda x: (x.period.weekday, x.period.period)
)
context["extra_marks"] = ExtraMark.objects.all()
context["week"] = wanted_week
context["lesson_periods"] = lesson_periods
context["persons"] = persons
context["group"] = group
context["select_form"] = select_form
week_prev = wanted_week - 1
week_next = wanted_week + 1
context["url_prev"] = "%s?%s" % (
reverse("week_view_by_week", args=[week_prev.year, week_prev.week]),
request.GET.urlencode(),
)
context["url_next"] = "%s?%s" % (
reverse("week_view_by_week", args=[week_next.year, week_next.week]),
request.GET.urlencode(),
)
return render(request, "alsijil/class_register/week_view.html", context)
def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
context = {}
group = get_object_or_404(Group, pk=id_)
# Get all lesson periods for the selected group
lesson_periods = (
LessonPeriod.objects.filter_group(group)
.distinct()
.prefetch_related("documentations", "personal_notes")
current_school_term = SchoolTerm.current
if not current_school_term:
return HttpResponseNotFound(_("There is no current school term."))
current_school_term.date_start, current_school_term.date_end,
periods_by_day = {}
for lesson_period in lesson_periods:
for week in weeks:
if (
lesson_period.lesson.validity.date_start
<= day
<= lesson_period.lesson.validity.date_end
):
filter(
lambda d: d.week == week.week,
lesson_period.documentations.all(),
)
filter(
lambda d: d.week == week.week,
lesson_period.personal_notes.all(),
)
substitution = lesson_period.get_substitution(week.week)
periods_by_day.setdefault(day, []).append(
(lesson_period, documentations, notes, substitution)
)
persons = group.members.annotate(
absences_count=Count(
"personal_notes__absent", filter=Q(personal_notes__absent=True)
),
unexcused=Count(
"personal_notes__absent",
filter=Q(personal_notes__absent=True, personal_notes__excused=False),
),
tardiness=Sum("personal_notes__late"),

Jonathan Weth
committed
for extra_mark in ExtraMark.objects.all():
persons = persons.annotate(
**{
extra_mark.count_label: Count(
"personal_notes", filter=Q(personal_notes__extra_marks=extra_mark,),
)
}
)

Nik | Klampfradler
committed
# FIXME Move to manager
personal_note_filters = PersonalNoteFilter.objects.all()
for personal_note_filter in personal_note_filters:
persons = persons.annotate(
**{
"_personal_notes_with_%s"
% personal_note_filter.identifier: Count(
"personal_notes__remarks",
filter=Q(
personal_notes__remarks__iregex=personal_note_filter.regex
),

Nik | Klampfradler
committed
)

Jonathan Weth
committed
context["extra_marks"] = ExtraMark.objects.all()
context["persons"] = persons
context["personal_note_filters"] = personal_note_filters
context["group"] = group
context["weeks"] = weeks
context["periods_by_day"] = periods_by_day
context["today"] = date.today()
return render(request, "alsijil/print/full_register.html", context)
def register_absence(request: HttpRequest) -> HttpResponse:
register_absence_form = RegisterAbsenceForm(request.POST or None)
if register_absence_form.is_valid():
person = register_absence_form.cleaned_data["person"]
start_date = register_absence_form.cleaned_data["date_start"]
end_date = register_absence_form.cleaned_data["date_end"]
from_period = register_absence_form.cleaned_data["from_period"]
absent = register_absence_form.cleaned_data["absent"]
excused = register_absence_form.cleaned_data["excused"]
remarks = register_absence_form.cleaned_data["remarks"]
# Mark person as absent
delta = end_date - start_date
from_period = from_period if i == 0 else 0
person.mark_absent(day, from_period, absent, excused, remarks)
messages.success(request, _("The absence has been saved."))
return redirect("index")
context["register_absence_form"] = register_absence_form
return render(request, "alsijil/absences/register.html", context)
def list_personal_note_filters(request: HttpRequest) -> HttpResponse:
context = {}
personal_note_filters = PersonalNoteFilter.objects.all()
# Prepare table
personal_note_filters_table = PersonalNoteFilterTable(personal_note_filters)
RequestConfig(request).configure(personal_note_filters_table)
context["personal_note_filters_table"] = personal_note_filters_table
return render(request, "alsijil/personal_note_filter/list.html", context)
def edit_personal_note_filter(
request: HttpRequest, id_: Optional["int"] = None
) -> HttpResponse:
context = {}
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
)
personal_note_filter_form = PersonalNoteFilterForm(request.POST or None)
if request.method == "POST":
if personal_note_filter_form.is_valid():
personal_note_filter_form.save(commit=True)
messages.success(request, _("The filter has been saved"))
return redirect("list_personal_note_filters")
context["personal_note_filter_form"] = personal_note_filter_form
return render(request, "alsijil/personal_note_filter/manage.html", context)
def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse:
context = {}
personal_note_filter = get_object_or_404(PersonalNoteFilter, pk=id_)
PersonalNoteFilter.objects.filter(pk=id_).delete()
messages.success(request, _("The filter has been deleted."))
context["personal_note_filter"] = personal_note_filter
return redirect("list_personal_note_filters")
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
class ExtraMarkListView(SingleTableView, PermissionRequiredMixin):
"""Table of all extra marks."""
model = ExtraMark
table_class = ExtraMarkTable
permission_required = "core.view_extramark"
template_name = "alsijil/extra_mark/list.html"
class ExtraMarkCreateView(AdvancedCreateView, PermissionRequiredMixin):
"""Create view for extra marks."""
model = ExtraMark
form_class = ExtraMarkForm
permission_required = "core.create_extramark"
template_name = "alsijil/extra_mark/create.html"
success_url = reverse_lazy("extra_marks")
success_message = _("The extra mark has been created.")
class ExtraMarkEditView(AdvancedEditView, PermissionRequiredMixin):
"""Edit view for extra marks."""
model = ExtraMark
form_class = ExtraMarkForm
permission_required = "core.edit_extramark"
template_name = "alsijil/extra_mark/edit.html"
success_url = reverse_lazy("extra_marks")
success_message = _("The extra mark has been saved.")
class ExtraMarkDeleteView(AdvancedDeleteView, PermissionRequiredMixin, RevisionMixin):
"""Delete view for extra marks"""
model = ExtraMark
permission_required = "core.delete_extramark"
template_name = "core/pages/delete.html"
success_url = reverse_lazy("extra_marks")
success_message = _("The extra mark has been deleted.")