Skip to content
Snippets Groups Projects
views.py 16.1 KiB
Newer Older
from datetime import datetime
from django.apps import apps
from django.db.models import FilteredRelation, Q
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
Tom Teichler's avatar
Tom Teichler committed
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
magicfelix's avatar
magicfelix committed
from django.utils.translation import gettext as _
Hangzhi Yu's avatar
Hangzhi Yu committed
from django.views.decorators.cache import never_cache
from django_tables2 import RequestConfig
from rules.contrib.views import permission_required
Hangzhi Yu's avatar
Hangzhi Yu committed
from aleksis.core.decorators import pwa_cache
from aleksis.core.models import Announcement
from aleksis.core.util import messages
from aleksis.core.util.core_helpers import has_person
from aleksis.core.util.pdf import render_pdf
from .filters import LessonPeriodFilter, SupervisionFilter
from .forms import LessonSubstitutionForm, SupervisionSubstitutionForm
from .managers import TimetableType
from .models import Holiday, LessonPeriod, Supervision, TimePeriod
from .tables import LessonsTable, SupervisionsTable
from .util.build import build_timetable, build_weekdays
from .util.change_tracker import TimetableDataChangeTracker
from .util.chronos_helpers import (
    get_classes,
    get_el_by_pk,
    get_rooms,
    get_substitution_by_id,
    get_substitutions_context_data,
from .util.date import CalendarWeek, get_weeks_for_year, week_weekday_to_date
Tom Teichler's avatar
Tom Teichler committed
from .util.js import date_unix
Nik | Klampfradler's avatar
Nik | Klampfradler committed

Hangzhi Yu's avatar
Hangzhi Yu committed
@pwa_cache
Hangzhi Yu's avatar
Hangzhi Yu committed
@permission_required("chronos.view_timetable_overview_rule")
def all_timetables(request: HttpRequest) -> HttpResponse:
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    """View all timetables for persons, groups and rooms."""
    user = request.user
    teachers, classes, rooms = get_teachers(user), get_classes(user), get_rooms(user)
    context["teachers"] = teachers
    context["classes"] = classes
    context["rooms"] = rooms
    return render(request, "chronos/all.html", context)
Hangzhi Yu's avatar
Hangzhi Yu committed
@pwa_cache
Hangzhi Yu's avatar
Hangzhi Yu committed
@permission_required("chronos.view_my_timetable_rule")
def my_timetable(
    request: HttpRequest,
    year: Optional[int] = None,
    month: Optional[int] = None,
    day: Optional[int] = None,
) -> HttpResponse:
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    """View personal timetable on a specified date."""
    context = {}

    if day:
        wanted_day = timezone.datetime(year=year, month=month, day=day).date()
        wanted_day = TimePeriod.get_next_relevant_day(wanted_day)
Tom Teichler's avatar
Tom Teichler committed
        wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
    wanted_week = CalendarWeek.from_date(wanted_day)

    if has_person(request.user):
        person = request.user.person
        # Build timetable
        timetable = build_timetable("person", person, wanted_day)
        week_timetable = build_timetable("person", person, wanted_week)
            # If no student or teacher, redirect to all timetables
            return redirect("all_timetables")

        context["week_timetable"] = week_timetable
Jonathan Weth's avatar
Jonathan Weth committed
        context["holiday"] = Holiday.on_day(wanted_day)
        context["super"] = {"type": type_, "el": super_el}
        context["type"] = type_
        context["day"] = wanted_day
        context["today"] = timezone.now().date()
        context["week"] = wanted_week
        context["periods"] = TimePeriod.get_times_dict()
        context["smart"] = True
Tom Teichler's avatar
Tom Teichler committed
        context["announcements"] = (
            Announcement.for_timetables().on_date(wanted_day).for_person(person)
        )
        context["week_announcements"] = (
            Announcement.for_timetables()
            .within_days(wanted_week[0], wanted_week[6])
            .for_person(person)
        )
        context["weekdays"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES, wanted_week)
        context["weekdays_short"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week)
        context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
            wanted_day, "my_timetable_by_date"
        )

        return render(request, "chronos/my_timetable.html", context)
    else:
        return redirect("all_timetables")
Hangzhi Yu's avatar
Hangzhi Yu committed
@pwa_cache
Hangzhi Yu's avatar
Hangzhi Yu committed
@permission_required("chronos.view_timetable_rule", fn=get_el_by_pk)
def timetable(
    request: HttpRequest,
    type_: str,
    pk: int,
    year: Optional[int] = None,
    week: Optional[int] = None,
    regular: Optional[str] = None,
Lloyd Meins's avatar
Lloyd Meins committed
    is_print: bool = False,
) -> HttpResponse:
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    """View a selected timetable for a person, group or room."""
Tom Teichler's avatar
Tom Teichler committed
    context = {}

Jonathan Weth's avatar
Jonathan Weth committed
    is_smart = regular != "regular"

Lloyd Meins's avatar
Lloyd Meins committed
    if is_print:
        is_smart = False

    el = get_el_by_pk(request, type_, pk, prefetch=True)
Jonathan Weth's avatar
Jonathan Weth committed
    if type(el) == HttpResponseNotFound:
        return HttpResponseNotFound()

    type_ = TimetableType.from_string(type_)

    if year and week:
        wanted_week = CalendarWeek(year=year, week=week)
        wanted_week = TimePeriod.get_relevant_week_from_datetime()
    timetable = build_timetable(type_, el, wanted_week, with_holidays=is_smart)
    context["timetable"] = timetable

    # Add time periods
    context["periods"] = TimePeriod.get_times_dict()

    # Build lists with weekdays and corresponding dates (long and short variant)
    context["weekdays"] = build_weekdays(
        TimePeriod.WEEKDAY_CHOICES, wanted_week, with_holidays=is_smart
    )
    context["weekdays_short"] = build_weekdays(
        TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week, with_holidays=is_smart
    )
    context["weeks"] = get_weeks_for_year(year=wanted_week.year)
    context["week"] = wanted_week
Jonathan Weth's avatar
Jonathan Weth committed
    context["type"] = type_
Jonathan Weth's avatar
Jonathan Weth committed
    context["smart"] = is_smart
    context["week_select"] = {
        "year": wanted_week.year,
        "dest": reverse(
            "timetable_by_week",
            args=[type_.value, pk, wanted_week.year, wanted_week.week],
        )[::-1]
        .replace(str(wanted_week.week)[::-1], "cw"[::-1], 1)
        .replace(str(wanted_week.year)[::-1], "year"[::-1], 1)[::-1],
    if is_smart:
        start = wanted_week[TimePeriod.weekday_min]
        stop = wanted_week[TimePeriod.weekday_max]
Tom Teichler's avatar
Tom Teichler committed
        context["announcements"] = (
            Announcement.for_timetables().relevant_for(el).within_days(start, stop)
        )
    week_prev = wanted_week - 1
    week_next = wanted_week + 1
    context["url_prev"] = reverse(
Jonathan Weth's avatar
Jonathan Weth committed
        "timetable_by_week", args=[type_.value, pk, week_prev.year, week_prev.week]
    )
    context["url_next"] = reverse(
Jonathan Weth's avatar
Jonathan Weth committed
        "timetable_by_week", args=[type_.value, pk, week_next.year, week_next.week]
    if apps.is_installed("aleksis.apps.alsijil"):
        context["is_alsijil_installed"] = True

Lloyd Meins's avatar
Lloyd Meins committed
    if is_print:
        context["back_url"] = reverse(
            "timetable_by_week",
            args=[type_.value, pk, wanted_week.year, wanted_week.week],
        return render_pdf(request, "chronos/timetable_print.html", context)
Lloyd Meins's avatar
Lloyd Meins committed
    else:
        return render(request, "chronos/timetable.html", context)
Hangzhi Yu's avatar
Hangzhi Yu committed
@pwa_cache
Hangzhi Yu's avatar
Hangzhi Yu committed
@permission_required("chronos.view_lessons_day_rule")
def lessons_day(
    request: HttpRequest,
    year: Optional[int] = None,
    month: Optional[int] = None,
    day: Optional[int] = None,
) -> HttpResponse:
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    """View all lessons taking place on a specified day."""
    if day:
        wanted_day = timezone.datetime(year=year, month=month, day=day).date()
        wanted_day = TimePeriod.get_next_relevant_day(wanted_day)
Tom Teichler's avatar
Tom Teichler committed
        wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
    # Get lessons
    lesson_periods = LessonPeriod.objects.on_day(wanted_day)
    # Get filter
    lesson_periods_filter = LessonPeriodFilter(
        request.GET,
        queryset=lesson_periods.annotate(
            current_substitution=FilteredRelation(
                "substitutions",
                condition=(
                    Q(substitutions__week=wanted_day.isocalendar()[1], substitutions__year=year)
                ),
            )
        ),
        weekday=wanted_day.weekday(),
    )
    context["lesson_periods_filter"] = lesson_periods_filter

    # Build table
    lessons_table = LessonsTable(lesson_periods_filter.qs)
Tom Teichler's avatar
Tom Teichler committed
    RequestConfig(request).configure(lessons_table)
    context["lessons_table"] = lessons_table
    context["lesson_periods"] = lesson_periods
    context["datepicker"] = {
        "date": date_unix(wanted_day),
Tom Teichler's avatar
Tom Teichler committed
        "dest": reverse("lessons_day"),
    context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
    return render(request, "chronos/lessons_day.html", context)
Hangzhi Yu's avatar
Hangzhi Yu committed
@never_cache
Hangzhi Yu's avatar
Hangzhi Yu committed
@permission_required("chronos.edit_substitution_rule", fn=get_substitution_by_id)
def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    """View a form to edit a substitution lessen."""
    context = {}

    lesson_period = get_object_or_404(LessonPeriod, pk=id_)
    wanted_week = lesson_period.lesson.get_calendar_week(week)
    context["lesson_period"] = lesson_period
    day = week_weekday_to_date(wanted_week, lesson_period.period.weekday)
    context["date"] = day
    lesson_substitution = get_substitution_by_id(request, id_, week)

        edit_substitution_form = LessonSubstitutionForm(
            request, request.POST or None, instance=lesson_substitution
        edit_substitution_form = LessonSubstitutionForm(
            request.POST or None,
        )
    context["substitution"] = lesson_substitution
    if request.method == "POST":
        if edit_substitution_form.is_valid():
            with reversion.create_revision(atomic=True):
                tracker = TimetableDataChangeTracker()

                lesson_substitution = edit_substitution_form.save(commit=False)
                if not lesson_substitution.pk:
                    lesson_substitution.lesson_period = lesson_period
                    lesson_substitution.week = wanted_week.week
                    lesson_substitution.year = wanted_week.year
                lesson_substitution.save()
                edit_substitution_form.save_m2m()

                messages.success(request, _("The substitution has been saved."))
            return redirect("lessons_day_by_date", year=day.year, month=day.month, day=day.day)
    context["edit_substitution_form"] = edit_substitution_form
    return render(request, "chronos/edit_substitution.html", context)
Hangzhi Yu's avatar
Hangzhi Yu committed
@permission_required("chronos.delete_substitution_rule", fn=get_substitution_by_id)
Nik | Klampfradler's avatar
Nik | Klampfradler committed
def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    """Delete a substitution lesson.

    Redirects back to substition list on success.
    """
    lesson_period = get_object_or_404(LessonPeriod, pk=id_)
    wanted_week = lesson_period.lesson.get_calendar_week(week)
    get_substitution_by_id(request, id_, week).delete()
    messages.success(request, _("The substitution has been deleted."))

    date = wanted_week[lesson_period.period.weekday]
Tom Teichler's avatar
Tom Teichler committed
    return redirect("lessons_day_by_date", year=date.year, month=date.month, day=date.day)
Hangzhi Yu's avatar
Hangzhi Yu committed
@pwa_cache
@permission_required("chronos.view_substitutions_rule")
def substitutions(
    request: HttpRequest,
    year: Optional[int] = None,
    month: Optional[int] = None,
    day: Optional[int] = None,
    is_print: bool = False,
) -> HttpResponse:
    """View all substitutions on a specified day."""
    context = get_substitutions_context_data(request, year, month, day, is_print)
    if not is_print:
        return render(request, "chronos/substitutions.html", context)
    else:
        return render_pdf(request, "chronos/substitutions_print.html", context)
Hangzhi Yu's avatar
Hangzhi Yu committed
@pwa_cache
@permission_required("chronos.view_supervisions_day_rule")
def supervisions_day(
    request: HttpRequest,
    year: Optional[int] = None,
    month: Optional[int] = None,
    day: Optional[int] = None,
) -> HttpResponse:
    """View all supervisions taking place on a specified day."""
    context = {}

    if day:
        wanted_day = timezone.datetime(year=year, month=month, day=day).date()
        wanted_day = TimePeriod.get_next_relevant_day(wanted_day)
    else:
        wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())

    # Get supervisions
magicfelix's avatar
magicfelix committed
    supervisions = (
        Supervision.objects.on_day(wanted_day)
        .filter_by_weekday(wanted_day.weekday())
        .order_by("break_item__before_period__period")
    )

    # Get filter
    supervisions_filter = SupervisionFilter(
        request.GET,
        queryset=supervisions.annotate(
            current_substitution=FilteredRelation(
                "substitutions",
                condition=(Q(substitutions__date=wanted_day)),
            )
        ),
    )
    context["supervisions_filter"] = supervisions_filter

    # Build table
    supervisions_table = SupervisionsTable(
        supervisions_filter.qs.annotate_week(week=CalendarWeek.from_date(wanted_day))
    )
    RequestConfig(request).configure(supervisions_table)

    context["supervisions_table"] = supervisions_table
    context["day"] = wanted_day
    context["supervisions"] = supervisions

    context["datepicker"] = {
        "date": date_unix(wanted_day),
        "dest": reverse("supervisions_day"),
    }

    context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
        wanted_day, "supervisions_day_by_date"
    )

    return render(request, "chronos/supervisions_day.html", context)


@never_cache
@permission_required("chronos.edit_supervision_substitution_rule")
def edit_supervision_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
    """View a form to edit a supervision substitution."""
    context = {}

    supervision = get_object_or_404(Supervision, pk=id_)
    wanted_week = supervision.get_calendar_week(week)
    context["week"] = week
    context["supervision"] = supervision
    date = week_weekday_to_date(wanted_week, supervision.break_item.weekday)
    context["date"] = date

    supervision_substitution = get_supervision_substitution_by_id(request, id_, date)

    if supervision_substitution:
        edit_supervision_substitution_form = SupervisionSubstitutionForm(
            request, request.POST or None, instance=supervision_substitution
        )
    else:
        edit_supervision_substitution_form = SupervisionSubstitutionForm(
            request.POST or None,
        )

    context["substitution"] = supervision_substitution

    if request.method == "POST":
        if edit_supervision_substitution_form.is_valid():
            with reversion.create_revision(atomic=True):
                tracker = TimetableDataChangeTracker()

                supervision_substitution = edit_supervision_substitution_form.save(commit=False)
                if not supervision_substitution.pk:
                    supervision_substitution.supervision = supervision
                    supervision_substitution.date = date
                supervision_substitution.save()
                edit_supervision_substitution_form.save_m2m()

                messages.success(request, _("The substitution has been saved."))

            return redirect(
                "supervisions_day_by_date", year=date.year, month=date.month, day=date.day
            )

    context["edit_supervision_substitution_form"] = edit_supervision_substitution_form

    return render(request, "chronos/edit_supervision_substitution.html", context)


@permission_required("chronos.delete_supervision_substitution_rule")
def delete_supervision_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
    """Delete a supervision substitution.

    Redirects back to supervision list on success.
    """
    supervision = get_object_or_404(Supervision, pk=id_)
    wanted_week = supervision.get_calendar_week(week)
    date = week_weekday_to_date(wanted_week, supervision.break_item.weekday)

    get_supervision_substitution_by_id(request, id_, date).delete()

    messages.success(request, _("The substitution has been deleted."))

    return redirect("supervisions_day_by_date", year=date.year, month=date.month, day=date.day)