Skip to content
Snippets Groups Projects
views.py 13 KiB
Newer Older
Tom Teichler's avatar
Tom Teichler committed
from datetime import date, datetime, timedelta
from typing import Optional

Jonathan Weth's avatar
Jonathan Weth committed
from aleksis.apps.chronos.managers import TimetableType
from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
Tom Teichler's avatar
Tom Teichler committed
from django.core.exceptions import PermissionDenied
from django.db.models import Count, Exists, OuterRef, Q, Sum
Jonathan Weth's avatar
Jonathan Weth committed
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect, render
Nik | Klampfradler's avatar
Nik | Klampfradler committed
from django.urls import reverse
from django.utils.translation import ugettext as _
Nik | Klampfradler's avatar
Nik | Klampfradler committed

Tom Teichler's avatar
Tom Teichler committed
from calendarweek import CalendarWeek
from django_tables2 import RequestConfig
from rules.contrib.views import permission_required
from aleksis.apps.chronos.models import LessonPeriod
from aleksis.core.models import Group, Person, SchoolYear
from aleksis.core.util import messages
from aleksis.core.util.core_helpers import objectgetter_optional
from .forms import (
    LessonDocumentationForm,
    PersonalNoteFilterForm,
    PersonalNoteFormSet,
    RegisterAbsenceForm,
    SelectForm,
)
from .models import LessonDocumentation, PersonalNoteFilter
from .tables import PersonalNoteFilterTable
from .util.alsijil_helpers import get_lesson_period_by_pk, get_lesson_periods_by_pk
Nik | Klampfradler's avatar
Nik | Klampfradler committed

@permission_required("alsijil.view_lesson", fn=get_lesson_period_by_pk)
def lesson(
    request: HttpRequest,
    year: Optional[int] = None,
    week: Optional[int] = None,
    period_id: Optional[int] = None,
) -> HttpResponse:
    lesson_period, wanted_week = get_lesson_period_by_pk(request, year, week, period_id)
    if not (year and week and period_id):
            return redirect(
                "lesson_by_week_and_period", wanted_week.year, wanted_week.week, lesson_period.pk,
            raise Http404(
                _(
                    "You either selected an invalid lesson or there is currently no lesson in progress."
                )
            )
    if (
        datetime.combine(
            wanted_week[lesson_period.period.weekday - 1], 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 - 1]
    # Create or get lesson documentation object; can be empty when first opening lesson
    lesson_documentation, created = LessonDocumentation.objects.get_or_create(
        lesson_period=lesson_period, week=wanted_week.week
    )
    lesson_documentation_form = LessonDocumentationForm(
Jonathan Weth's avatar
Jonathan Weth committed
        request.POST or None, instance=lesson_documentation, prefix="lesson_documentation",

    # Create a formset that holds all personal notes for all persons in this lesson
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    persons_qs = lesson_period.get_personal_notes(wanted_week)
    personal_note_formset = PersonalNoteFormSet(
        request.POST or None, queryset=persons_qs, prefix="personal_notes"
    )
    if request.method == "POST":
        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 - 1],
                    lesson_period.period.period + 1,
                    instance.excused,
    context["lesson_documentation"] = lesson_documentation
    context["lesson_documentation_form"] = lesson_documentation_form
    context["personal_note_formset"] = personal_note_formset
    return render(request, "alsijil/lesson.html", context)
@permission_required("alsijil.view_week", fn=get_lesson_periods_by_pk)
def week_view(
Jonathan Weth's avatar
Jonathan Weth committed
    request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None, type_: Optional[str] = None, id_: Optional[int] = None
) -> HttpResponse:
    lesson_periods, wanted_week, type_, instance = get_lesson_periods_by_pk(request, year, week, type_, id_)
Jonathan Weth's avatar
Jonathan Weth committed
    # 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)

    if type_ == TimetableType.GROUP:
        group = instance
    else:
        group = None

Nik | Klampfradler's avatar
Nik | Klampfradler committed
    if lesson_periods:
        # Aggregate all personal notes for this group and week
Jonathan Weth's avatar
Jonathan Weth committed
        lesson_periods_pk = lesson_periods.values_list("pk", flat=True)
        persons = (
            Person.objects.filter(is_active=True)
Jonathan Weth's avatar
Jonathan Weth committed
            .filter(member_of__lessons__lesson_periods__in=lesson_periods_pk)
            .distinct()
            .prefetch_related("personal_notes")
            .annotate(
Jonathan Weth's avatar
Jonathan Weth committed
                absences_count=Count(
                    "personal_notes__absent",
                    filter=Q(
Jonathan Weth's avatar
Jonathan Weth committed
                        personal_notes__lesson_period__in=lesson_periods_pk,
                        personal_notes__week=wanted_week.week,
                        personal_notes__absent=True,
                    ),
                ),
Jonathan Weth's avatar
Jonathan Weth committed
                unexcused_count=Count(
                    "personal_notes__absent",
                    filter=Q(
Jonathan Weth's avatar
Jonathan Weth committed
                        personal_notes__lesson_period__in=lesson_periods_pk,
                        personal_notes__week=wanted_week.week,
                        personal_notes__absent=True,
                        personal_notes__excused=False,
                    ),
                ),
Jonathan Weth's avatar
Jonathan Weth committed
                tardiness_sum=Sum(
                    "personal_notes__late",
                    filter=Q(
Jonathan Weth's avatar
Jonathan Weth committed
                        personal_notes__lesson_period__in=lesson_periods_pk,
                        personal_notes__week=wanted_week.week,
                    ),
                ),
            )
Jonathan Weth's avatar
Jonathan Weth committed
    # 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["week"] = wanted_week
    context["lesson_periods"] = lesson_periods
    context["persons"] = persons
    context["group"] = group
    context["select_form"] = select_form
Jonathan Weth's avatar
Jonathan Weth committed
    context["instance"] = instance
    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/week_view.html", context)
@permission_required("alsijil.full_register_group", fn=objectgetter_optional(Group, None, False))
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)
Julian's avatar
Julian committed
            .distinct()
            .prefetch_related("documentations", "personal_notes")
    weeks = CalendarWeek.weeks_within(
        SchoolYear.current.date_start,
        SchoolYear.current.date_end,
    )
    for lesson_period in lesson_periods:
        for week in weeks:
            day = week[lesson_period.period.weekday - 1]
Julian's avatar
Julian committed
            if lesson_period.lesson.date_start <= day <= lesson_period.lesson.date_end:
                documentations = list(
                    filter(lambda d: d.week == week.week, lesson_period.documentations.all(),)
                )
                notes = list(
                    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(
Julian's avatar
Julian committed
        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"),
Nik | Klampfradler's avatar
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),
    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)
@permission_required("alsijil.register_absence")
def register_absence(request: HttpRequest) -> HttpResponse:
Tom Teichler's avatar
Tom Teichler committed
    context = {}

    register_absence_form = RegisterAbsenceForm(request.POST or None)
    if request.method == "POST":
        if register_absence_form.is_valid():
Tom Teichler's avatar
Tom Teichler committed
            # Get data from form
            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"]
Tom Teichler's avatar
Tom Teichler committed
            # Mark person as absent
            delta = end_date - start_date
            for i in range(delta.days + 1):
                from_period = from_period if i == 0 else 0
Tom Teichler's avatar
Tom Teichler committed
                day = start_date + timedelta(days=i)
                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/register_absence.html", context)
@permission_required("alsijil.list_personal_note_filters")
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_filters.html", context)
@permission_required("alsijil.edit_personal_note_filter", fn=objectgetter_optional(PersonalNoteFilter, None, False))
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/manage_personal_note_filter.html", context)
@permission_required("alsijil.delete_personal_note_filter", fn=objectgetter_optional(PersonalNoteFilter, None, False))
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")