diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py index f75e9f611e0eb73f456c780bed9f961b67d260f6..9da4770d092663c4baa2c5a9e770677305ea0121 100644 --- a/aleksis/apps/chronos/admin.py +++ b/aleksis/apps/chronos/admin.py @@ -23,7 +23,6 @@ from .models import ( SupervisionArea, SupervisionSubstitution, TimePeriod, - TimetableWidget, ValidityRange, ) from .util.format import format_date_period, format_m2m @@ -209,13 +208,6 @@ class TimePeriodAdmin(admin.ModelAdmin): admin.site.register(TimePeriod, TimePeriodAdmin) -class TimetableWidgetAdmin(admin.ModelAdmin): - list_display = ("title", "active") - - -admin.site.register(TimetableWidget, TimetableWidgetAdmin) - - class ValidityRangeAdmin(admin.ModelAdmin): list_display = ("__str__", "date_start", "date_end") list_display_links = ("__str__", "date_start", "date_end") diff --git a/aleksis/apps/chronos/filters.py b/aleksis/apps/chronos/filters.py deleted file mode 100644 index 5e231ef98ed73c3102288a09d8acabb7a39d0972..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/filters.py +++ /dev/null @@ -1,178 +0,0 @@ -from collections.abc import Sequence - -from django.db.models import Count, Q -from django.forms import RadioSelect -from django.utils.translation import gettext as _ - -from django_filters import BooleanFilter, FilterSet, ModelMultipleChoiceFilter -from django_select2.forms import ModelSelect2MultipleWidget -from material import Layout, Row - -from aleksis.core.models import Group, Person, Room, SchoolTerm - -from .models import Break, Subject, SupervisionArea, TimePeriod - - -class MultipleModelMultipleChoiceFilter(ModelMultipleChoiceFilter): - """Filter for filtering multiple fields with one input. - - >>> multiple_filter = MultipleModelMultipleChoiceFilter(["room", "substitution_room"]) - """ - - def filter(self, qs, value): # noqa - if not value: - return qs - - if self.is_noop(qs, value): - return qs - - q = Q() - for v in set(value): - if v == self.null_value: - v = None - for field in self.lookup_fields: - q = q | Q(**{field: v}) - - qs = self.get_method(qs)(q) - - return qs.distinct() if self.distinct else qs - - def __init__(self, lookup_fields: Sequence[str], *args, **kwargs): - self.lookup_fields = lookup_fields - super().__init__(self, *args, **kwargs) - - -class LessonPeriodFilter(FilterSet): - period = ModelMultipleChoiceFilter(queryset=TimePeriod.objects.all()) - lesson__groups = ModelMultipleChoiceFilter( - queryset=Group.objects.all(), - widget=ModelSelect2MultipleWidget( - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - search_fields=[ - "name__icontains", - "short_name__icontains", - ], - ), - ) - room = MultipleModelMultipleChoiceFilter( - ["room", "current_substitution__room"], - queryset=Room.objects.all(), - label=_("Room"), - widget=ModelSelect2MultipleWidget( - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - search_fields=[ - "name__icontains", - "short_name__icontains", - ], - ), - ) - lesson__teachers = MultipleModelMultipleChoiceFilter( - ["lesson__teachers", "current_substitution__teachers"], - queryset=Person.objects.none(), - label=_("Teachers"), - widget=ModelSelect2MultipleWidget( - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - search_fields=[ - "first_name__icontains", - "last_name__icontains", - "short_name__icontains", - ], - ), - ) - lesson__subject = MultipleModelMultipleChoiceFilter( - ["lesson__subject", "current_substitution__subject"], - queryset=Subject.objects.all(), - label=_("Subject"), - widget=ModelSelect2MultipleWidget( - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - search_fields=[ - "name__icontains", - "short_name__icontains", - ], - ), - ) - substituted = BooleanFilter( - field_name="current_substitution", - label=_("Substitution status"), - lookup_expr="isnull", - exclude=True, - widget=RadioSelect( - choices=[ - ("", _("All lessons")), - (True, _("Substituted")), - (False, _("Not substituted")), - ] - ), - ) - - def __init__(self, *args, **kwargs): - weekday = kwargs.pop("weekday") - super().__init__(*args, **kwargs) - self.filters["period"].queryset = TimePeriod.objects.filter(weekday=weekday) - self.filters["lesson__teachers"].queryset = ( - Person.objects.annotate( - lessons_count=Count( - "lessons_as_teacher", - filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current) - if SchoolTerm.current - else Q(), - ) - ) - .filter(lessons_count__gt=0) - .order_by("short_name", "last_name") - ) - self.form.layout = Layout( - Row("period", "lesson__groups", "room"), - Row("lesson__teachers", "lesson__subject", "substituted"), - ) - - -class SupervisionFilter(FilterSet): - break_item = ModelMultipleChoiceFilter(queryset=Break.objects.all()) - area = ModelMultipleChoiceFilter(queryset=SupervisionArea.objects.all()) - teacher = MultipleModelMultipleChoiceFilter( - ["teacher", "current_substitution__teacher"], - queryset=Person.objects.none(), - label=_("Teacher"), - widget=ModelSelect2MultipleWidget( - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - search_fields=[ - "first_name__icontains", - "last_name__icontains", - "short_name__icontains", - ], - ), - ) - substituted = BooleanFilter( - field_name="current_substitution", - label=_("Substitution status"), - lookup_expr="isnull", - exclude=True, - widget=RadioSelect( - choices=[ - ("", _("All supervisions")), - (True, _("Substituted")), - (False, _("Not substituted")), - ] - ), - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.filters["break_item"].queryset = Break.objects.filter(supervisions__in=self.queryset) - self.filters["teacher"].queryset = ( - Person.objects.annotate( - lessons_count=Count( - "lessons_as_teacher", - filter=Q(lessons_as_teacher__validity__school_term=SchoolTerm.current) - if SchoolTerm.current - else Q(), - ) - ) - .filter(lessons_count__gt=0) - .order_by("short_name", "last_name") - ) - self.form.layout = Layout( - Row("break_item", "area"), - Row("teacher", "substituted"), - ) diff --git a/aleksis/apps/chronos/forms.py b/aleksis/apps/chronos/forms.py deleted file mode 100644 index 58664a45ecae7792e20b086450388aeecc0b0e24..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/forms.py +++ /dev/null @@ -1,61 +0,0 @@ -from django import forms - -from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget -from material import Layout - -from .models import AutomaticPlan, LessonSubstitution, SupervisionSubstitution -from .util.chronos_helpers import get_teachers - - -class LessonSubstitutionForm(forms.ModelForm): - """Form to manage substitutions.""" - - class Meta: - model = LessonSubstitution - fields = ["subject", "teachers", "room", "cancelled", "comment"] - widgets = { - "teachers": ModelSelect2MultipleWidget( - search_fields=[ - "first_name__icontains", - "last_name__icontains", - "short_name__icontains", - ], - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - ), - } - - def __init__(self, request, *args, **kwargs): - super().__init__(*args, **kwargs) - self.request = request - self.fields["teachers"].queryset = get_teachers(request.user) - - -class SupervisionSubstitutionForm(forms.ModelForm): - """Form to manage supervisions substitutions.""" - - class Meta: - model = SupervisionSubstitution - fields = ["teacher"] - widgets = { - "teacher": ModelSelect2Widget( - search_fields=[ - "first_name__icontains", - "last_name__icontains", - "short_name__icontains", - ], - attrs={"data-minimum-input-length": 0, "class": "browser-default"}, - ), - } - - def __init__(self, request, *args, **kwargs): - super().__init__(*args, **kwargs) - self.request = request - self.fields["teacher"].queryset = get_teachers(request.user) - - -class AutomaticPlanForm(forms.ModelForm): - layout = Layout("slug", "name", "number_of_days", "show_header_box") - - class Meta: - model = AutomaticPlan - fields = ["slug", "name", "number_of_days", "show_header_box"] diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py index 7f01d7f67c9bff1458804e93a538daa340e014b3..c83c6db038c96e4c7d7221897076ce6c6b162313 100644 --- a/aleksis/apps/chronos/models.py +++ b/aleksis/apps/chronos/models.py @@ -14,7 +14,6 @@ from django.db import models from django.db.models import Max, Min, Q, QuerySet from django.db.models.functions import Coalesce from django.dispatch import receiver -from django.forms import Media from django.http import HttpRequest from django.template.loader import render_to_string from django.urls import reverse @@ -66,7 +65,7 @@ from aleksis.core.mixins import ( GlobalPermissionModel, SchoolTermRelatedExtensibleModel, ) -from aleksis.core.models import CalendarEvent, DashboardWidget, Group, Person, Room, SchoolTerm +from aleksis.core.models import CalendarEvent, Group, Person, Room, SchoolTerm from aleksis.core.util.core_helpers import get_site_preferences, has_person @@ -618,45 +617,6 @@ class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel) verbose_name_plural = _("Lesson periods") -class TimetableWidget(DashboardWidget): - template = "chronos/widget.html" - - def get_context(self, request): - from aleksis.apps.chronos.util.build import build_timetable # noqa - - context = {"has_plan": True} - wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time()) - - if has_person(request.user): - person = request.user.person - type_ = person.timetable_type - - # Build timetable - timetable = build_timetable("person", person, wanted_day) - - if type_ is None: - # If no student or teacher, redirect to all timetables - context["has_plan"] = False - else: - context["timetable"] = timetable - context["holiday"] = Holiday.on_day(wanted_day) - context["type"] = type_ - context["day"] = wanted_day - context["periods"] = TimePeriod.get_times_dict() - context["smart"] = True - else: - context["has_plan"] = False - - return context - - media = Media(css={"all": ("css/chronos/timetable.css",)}) - - class Meta: - proxy = True - verbose_name = _("Timetable widget") - verbose_name_plural = _("Timetable widgets") - - class AbsenceReason(ExtensibleModel): short_name = models.CharField(verbose_name=_("Short name"), max_length=255, unique=True) name = models.CharField(verbose_name=_("Name"), blank=True, max_length=255) diff --git a/aleksis/apps/chronos/tables.py b/aleksis/apps/chronos/tables.py deleted file mode 100644 index 7a60b1f6254cfef5bfaf2a415c5bfcff58f6156c..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/tables.py +++ /dev/null @@ -1,122 +0,0 @@ -from __future__ import annotations - -from django.utils.html import format_html -from django.utils.translation import gettext_lazy as _ - -import django_tables2 as tables -from django_tables2.utils import A, Accessor - -from .models import LessonPeriod, Supervision - - -def _title_attr_from_lesson_or_supervision_state( - record: LessonPeriod | Supervision | None = None, - table: LessonsTable | SupervisionsTable | None = None, -) -> str: - """Return HTML title depending on lesson or supervision state.""" - if record.get_substitution(): - if hasattr(record.get_substitution(), "cancelled") and record.get_substitution().cancelled: - return _("Lesson cancelled") - else: - return _("Substituted") - else: - return "" - - -class SubstitutionColumn(tables.Column): - def render(self, value, record: LessonPeriod | Supervision | None = None): - if record.get_substitution(): - return ( - format_html( - "<s>{}</s> → {}", - value, - self.substitution_accessor.resolve(record.get_substitution()), - ) - if self.substitution_accessor.resolve(record.get_substitution()) - else format_html( - "<s>{}</s>", - value, - ) - ) - return value - - def __init__(self, *args, **kwargs): - self.substitution_accessor = Accessor(kwargs.pop("substitution_accessor")) - super().__init__(*args, **kwargs) - - -class LessonStatusColumn(tables.Column): - def render(self, record: LessonPeriod | Supervision | None = None): - if record.get_substitution(): - return ( - format_html( - '<span class="new badge green">{}</span>', - _("cancelled"), - ) - if hasattr(record.get_substitution(), "cancelled") - and record.get_substitution().cancelled - else format_html( - '<span class="new badge orange">{}</span>', - _("substituted"), - ) - ) - return "" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - -class LessonsTable(tables.Table): - """Table for daily lessons and management of substitutions.""" - - class Meta: - attrs = {"class": "highlight, striped"} - row_attrs = { - "title": _title_attr_from_lesson_or_supervision_state, - } - - period__period = tables.Column(accessor="period__period") - lesson__groups = tables.Column(accessor="lesson__group_names", verbose_name=_("Groups")) - status = LessonStatusColumn(verbose_name=_("Status"), empty_values=()) - lesson__teachers = SubstitutionColumn( - accessor="lesson__teacher_names", - substitution_accessor="teacher_names", - verbose_name=_("Teachers"), - ) - lesson__subject = SubstitutionColumn( - accessor="lesson__subject", substitution_accessor="subject" - ) - room = SubstitutionColumn(accessor="room", substitution_accessor="room") - edit_substitution = tables.LinkColumn( - "edit_substitution", - args=[A("id"), A("_week")], - text=_("Substitution"), - attrs={"a": {"class": "btn-flat waves-effect waves-orange"}}, - verbose_name=_("Manage substitution"), - ) - - -class SupervisionsTable(tables.Table): - """Table for daily supervisions and management of substitutions.""" - - class Meta: - attrs = {"class": "highlight, striped"} - row_attrs = { - "title": _title_attr_from_lesson_or_supervision_state, - } - - break_item = tables.Column(accessor="break_item") - status = LessonStatusColumn(verbose_name=_("Status"), empty_values=()) - area = tables.Column(accessor="area") - teacher = SubstitutionColumn( - accessor="teacher", - substitution_accessor="teacher", - verbose_name=_("Teachers"), - ) - edit_substitution = tables.LinkColumn( - "edit_supervision_substitution", - args=[A("id"), A("_week")], - text=_("Substitution"), - attrs={"a": {"class": "btn-flat waves-effect waves-orange"}}, - verbose_name=_("Manage substitution"), - ) diff --git a/aleksis/apps/chronos/templates/chronos/all.html b/aleksis/apps/chronos/templates/chronos/all.html deleted file mode 100644 index 01b9770567f0458913db85cabc12bd31976518e5..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/all.html +++ /dev/null @@ -1,59 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends 'core/base.html' %} - -{% load i18n static %} - -{% block extra_head %} - <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}"> -{% endblock %} - -{% block browser_title %}{% blocktrans %}All timetables{% endblocktrans %}{% endblock %} -{% block page_title %}{% trans "All timetables" %}{% endblock %} - -{% block content %} - <div class="row"> - <div class="col s12 m4"> - <h2>{% trans "Teachers" %}</h2> - - {% for teacher in teachers %} - <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" - href="{% url 'timetable' 'teacher' teacher.pk %}"> - {{ teacher.short_name }} - </a> - {% empty %} - {% trans 'No teachers timetables available.' as message %} - {% include 'components/msgbox.html' with status='info' icon='mdi:alert-outline' msg=message %} - {% endfor %} - </div> - - <div class="col s12 m4"> - <h2>{% trans "Groups" %}</h2> - - {% for class in classes %} - <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" - href="{% url 'timetable' 'group' class.pk %}"> - {{ class.short_name }} - </a> - {% empty %} - {% trans 'No group timetables available.' as message %} - {% include 'components/msgbox.html' with status='info' icon='mdi:alert-outline' msg=message %} - {% endfor %} - </div> - - <div class="col s12 m4"> - <h2>{% trans "Rooms" %}</h2> - - {% for room in rooms %} - <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" - href="{% url 'timetable' 'room' room.pk %}"> - {{ room.short_name }} - </a> - {% empty %} - {% trans 'No room timetables available.' as message %} - {% include 'components/msgbox.html' with status='info' icon='mdi:alert-outline' msg=message %} - {% endfor %} - </div> - </div> - -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/edit_substitution.html b/aleksis/apps/chronos/templates/chronos/edit_substitution.html deleted file mode 100644 index 297d70139684f71d17b43f3bb4b4c875c985b9a7..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/edit_substitution.html +++ /dev/null @@ -1,31 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends "core/base.html" %} -{% load material_form i18n any_js %} - -{% block extra_head %} - {{ edit_substitution_form.media.css }} - {% include_css "select2-materialize" %} -{% endblock %} - -{% block browser_title %}{% blocktrans %}Edit substitution.{% endblocktrans %}{% endblock %} -{% block page_title %}{% blocktrans %}Edit substitution{% endblocktrans %}{% endblock %} - -{% block content %} - <p class="flow-text">{{ date }}: {{ lesson_period }}</p> - <form method="post"> - {% csrf_token %} - - {% form form=edit_substitution_form %}{% endform %} - - {% include "core/partials/save_button.html" %} - {% if substitution %} - <a href="{% url 'delete_substitution' substitution.lesson_period.id substitution.week %}" - class="btn red waves-effect waves-light"> - <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i> {% trans "Delete" %} - </a> - {% endif %} - </form> - {% include_js "select2-materialize" %} - {{ edit_substitution_form.media.js }} -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/edit_supervision_substitution.html b/aleksis/apps/chronos/templates/chronos/edit_supervision_substitution.html deleted file mode 100644 index 2ee637e87502a7e9bb45e7894e7bdbf70f056a5e..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/edit_supervision_substitution.html +++ /dev/null @@ -1,31 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends "core/base.html" %} -{% load material_form i18n any_js %} - -{% block extra_head %} - {{ edit_supervision_substitution_form.media.css }} - {% include_css "select2-materialize" %} -{% endblock %} - -{% block browser_title %}{% blocktrans %}Edit substitution.{% endblocktrans %}{% endblock %} -{% block page_title %}{% blocktrans %}Edit substitution{% endblocktrans %}{% endblock %} - -{% block content %} - <p class="flow-text">{{ date }}: {{ supervision }}</p> - <form method="post"> - {% csrf_token %} - - {% form form=edit_supervision_substitution_form %}{% endform %} - - {% include "core/partials/save_button.html" %} - {% if substitution %} - <a href="{% url 'delete_supervision_substitution' substitution.supervision.id week %}" - class="btn red waves-effect waves-light"> - <i class="material-icons iconify left" data-icon="mdi:delete-outline"></i> {% trans "Delete" %} - </a> - {% endif %} - </form> - {% include_js "select2-materialize" %} - {{ edit_supervision_substitution_form.media.js }} -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/lessons_day.html b/aleksis/apps/chronos/templates/chronos/lessons_day.html deleted file mode 100644 index e8d0662fd9e16cff185bbd2fdca78cfdd5504fbf..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/lessons_day.html +++ /dev/null @@ -1,42 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends "core/base.html" %} -{% load i18n material_form any_js %} - - -{% load render_table from django_tables2 %} - -{% block extra_head %} - {{ lesson_periods_filter.form.media.css }} - {% include_css "select2-materialize" %} -{% endblock %} - -{% block browser_title %}{% blocktrans %}Lessons{% endblocktrans %}{% endblock %} -{% block no_page_title %}{% endblock %} - -{% block content %} - <script type="text/javascript"> - var dest = Urls.lessonsDay(); - </script> - - <h2>{% trans "Filter lessons" %}</h2> - <form method="get"> - {% form form=lesson_periods_filter.form %}{% endform %} - {% trans "Search" as caption %} - {% include "core/partials/save_button.html" with caption=caption icon="mdi:search" %} - </form> - - <div class="row no-margin"> - <div class="col s12 m6 l8 no-padding"> - <h1>{% blocktrans %}Lessons{% endblocktrans %} {{ day|date:"l" }}, {{ day }}</h1> - </div> - <div class="col s12 m6 l4 no-padding"> - {% include "chronos/partials/datepicker.html" %} - </div> - </div> - - {% render_table lessons_table %} - - {% include_js "select2-materialize" %} - {{ lesson_periods_filter.form.media.js }} -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/my_timetable.html b/aleksis/apps/chronos/templates/chronos/my_timetable.html deleted file mode 100644 index 1408ec233161f4e14c2022a34447526f7cb3dbb2..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/my_timetable.html +++ /dev/null @@ -1,66 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends 'core/base.html' %} - -{% load i18n static %} - -{% block extra_head %} - <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}"> -{% endblock %} - -{% block browser_title %}{% blocktrans %}My timetable{% endblocktrans %}{% endblock %} -{% block no_page_title %}{% endblock %} - -{% block content %} - <div class="row no-margin"> - <div class="col s12"> - <h1> - {% trans "My timetable" %} <i>{{ el }}</i> - <span class="badge new primary-color ">{% trans "SMART PLAN" %}</span> - </h1> - <a class="btn-flat waves-effect waves-light" href="{% url "timetable" super.type.value super.el.pk %}"> - {% trans "Show week timetable for" %} {{ super.el.short_name }} - </a> - </div> - </div> - - <div class="row nomargin hide-on-large-only"> - <div class="col m12 s12 l6 xl4"> - {% include "core/partials/announcements.html" with announcements=announcements %} - </div> - </div> - - <div class="row nomargin hide-on-large-med-and-down"> - <div class="col m12 s12 l6 xl4"> - {% include "core/partials/announcements.html" with announcements=week_announcements %} - </div> - </div> - - <div class="row"> - <div class="col s12"> - <div class="card timetable-title-card"> - <div class="card-content"> - <span class="card-title"> - {% include "chronos/partials/datepicker.html" with display_date_only=1 %} - <span class="show-on-medium-and-down hide-on-large-only"> - {% if weekday.date == today %} - <br/> {% include "chronos/partials/today.html" %} - {% endif %} - </span> - </span> - </div> - </div> - - </div> - </div> - - <div class="row hide-on-large-only"> - <div class="timetable-plan col s12 m12 xl4"> - {# Lessons #} - {% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %} - </div> - </div> - <div class="row timetable-plan hide-on-med-and-down"> - {% include "chronos/partials/week_timetable.html" with timetable=week_timetable active_day=day today=today %} - </div> -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/substitutions.html b/aleksis/apps/chronos/templates/chronos/substitutions.html deleted file mode 100644 index 42a19e63cc3d1620343734fa6880ac2c410061f0..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/substitutions.html +++ /dev/null @@ -1,94 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends 'core/base.html' %} - -{% load i18n static %} - -{% block extra_head %} - <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}"> -{% endblock %} - -{% block browser_title %}{% blocktrans %}Substitutions{% endblocktrans %}{% endblock %} -{% block no_page_title %}{% endblock %} - -{% block content %} - <div class="row no-margin"> - <div class="col s10 m6 no-padding"> - <h1>{% trans "Substitutions" %}</h1> - </div> - <div class="col s2 m6 right align-right print-icon"> - <a class="waves-effect waves-teal btn-flat btn-flat-medium right" - href="{% url "substitutions_print_by_date" day.year day.month day.day %}" target="_blank"> - <i class="material-icons iconify center" data-icon="mdi:printer-outline"></i> - </a> - </div> - </div> - - <div class="row no-print"> - <div class="col s12 m6 l8"> - {% include "chronos/partials/headerbox.html" %} - - {% include "core/partials/announcements.html" with announcements=announcements show_recipients=1 %} - </div> - <div class="col s12 m6 l4 no-padding"> - {% include "chronos/partials/datepicker.html" %} - </div> - </div> - - <h2 class="hide-on-small-and-down">{{ day|date:"l" }}, {{ day }}</h2> - - <div class="table-container"> - <table class="substitutions striped"> - <thead> - <tr> - <th><i class="material-icons iconify center" data-icon="mdi:account-multiple-outline"></i></th> - <th><i class="material-icons iconify center" data-icon="mdi:clock-outline"></i></th> - <th>{% trans "Teacher" %}</th> - <th>{% trans "Subject" %}</th> - <th>{% trans "Room" %}</th> - <th>{% trans "Notes" %}</th> - <th></th> - </tr> - </thead> - <tbody> - {% if not substitutions %} - <td colspan="7"> - <p class="flow-text center"> - {% blocktrans %}No substitutions available.{% endblocktrans %} - </p> - </td> - {% endif %} - {% for item in substitutions %} - <tr class="{% include "chronos/partials/subs/colour.html" with item=item %}"> - {# TODO: Extend support for purple (events) #} - <td> - {% include "chronos/partials/subs/groups.html" with type=item.type el=item.el %} - </td> - <td> - {% include "chronos/partials/subs/period.html" with type=item.type el=item.el item=item %} - </td> - <td> - {% include "chronos/partials/subs/teachers.html" with type=item.type el=item.el %} - </td> - <td> - {% include "chronos/partials/subs/subject.html" with type=item.type el=item.el %} - </td> - <td> - {% include "chronos/partials/subs/room.html" with type=item.type el=item.el %} - </td> - <td> - <span class="hide-on-med-and-up"> - {% include "chronos/partials/subs/badge.html" with sub=item.el %} - </span> - {% include "chronos/partials/subs/comment.html" with el=item.el %} - </td> - <td class="hide-on-small-and-down"> - {% include "chronos/partials/subs/badge.html" with sub=item.el %} - </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> - -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/supervisions_day.html b/aleksis/apps/chronos/templates/chronos/supervisions_day.html deleted file mode 100644 index 8c28324ea74f5931e3eaff9603f77846c91cd723..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/supervisions_day.html +++ /dev/null @@ -1,38 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends "core/base.html" %} -{% load i18n material_form any_js %} - - -{% load render_table from django_tables2 %} - -{% block extra_head %} - {{ supervisions_filter.form.media.css }} - {% include_css "select2-materialize" %} -{% endblock %} - -{% block browser_title %}{% blocktrans %}Lessons{% endblocktrans %}{% endblock %} -{% block no_page_title %}{% endblock %} - -{% block content %} - <h2>{% trans "Filter supervisions" %}</h2> - <form method="get"> - {% form form=supervisions_filter.form %}{% endform %} - {% trans "Search" as caption %} - {% include "core/partials/save_button.html" with caption=caption icon="mdi:search" %} - </form> - - <div class="row no-margin"> - <div class="col s12 m6 l8 no-padding"> - <h1>{% blocktrans %}Supervisions{% endblocktrans %} {{ day|date:"l" }}, {{ day }}</h1> - </div> - <div class="col s12 m6 l4 no-padding"> - {% include "chronos/partials/datepicker.html" %} - </div> - </div> - - {% render_table supervisions_table %} - - {% include_js "select2-materialize" %} - {{ supervisions_filter.form.media.js }} -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/timetable.html b/aleksis/apps/chronos/templates/chronos/timetable.html deleted file mode 100644 index 3e253eba013e9d5873c65f38867d7830394fdb38..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/timetable.html +++ /dev/null @@ -1,135 +0,0 @@ -{# -*- engine:django -*- #} - -{% extends 'core/base.html' %} - -{% load data_helpers rules static i18n %} - -{% block extra_head %} - <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}"> -{% endblock %} - -{% block browser_title %}{% blocktrans %}Timetable{% endblocktrans %}{% endblock %} -{% block no_page_title %}{% endblock %} -{% block content %} - - {% if smart %} - <script type="text/javascript" src="{% static "js/helper.js" %}"></script> - {{ week_select|json_script:"week_select" }} - <script type="text/javascript" src="{% static "js/chronos/week_select.js" %}"></script> - {% endif %} - - <div class="row no-margin"> - <div class="col s8 m6 l8 xl9"> - <h1> - {% trans "Timetable" %} <i>{{ el }}</i> - </h1> - - {# Show class teacher and deputy class teacher #} - {% if type.value == "group" and el.owners.all %} - <h2>{% trans "Group teachers:" %} - {% for teacher in el.owners.all %} - <span data-position="bottom" class="tooltipped" - data-tooltip="{{ teacher }}"> - <a href="{% url "timetable" "teacher" teacher.pk %}"> - {{ teacher.short_name }}</a></span>{% if not forloop.last %},{% endif %} - {% endfor %} - </h2> - {% endif %} - </div> - <div class="col s4 m6 l4 xl3 right align-right no-print"> - <a class="waves-effect waves-teal btn-flat btn-flat-medium right hide-on-small-and-down" href="{% url "timetable_print" type.value pk %}"> - <i class="material-icons iconify center" data-icon="mdi:printer-outline"></i> - </a> - </div> - </div> - <div class="row"> - {% if smart %} - {# Show if smart #} - {# Toggle button to regular and smart plan badge #} - <div class="row s12 m6 left"> - <span class="badge new primary-color left smart-plan-badge">{% trans "SMART PLAN" %}</span> - - <a class="waves-effect waves-light btn-flat no-print" - href="{% url "timetable_regular" type.value pk "regular" %}"> - <i class="material-icons iconify left" data-icon="mdi:play-box-outline"></i> - {% trans "Show regular timetable" %} - </a> - - {% has_perm "alsijil.view_week_rule" user as can_view_week_view %} - {% if is_alsijil_installed and can_view_week_view %} - <a class="waves-effect waves-light btn-flat no-print" - href="{% url "week_view_by_week" year=week.year week=week.week %}"> - <i class="material-icons iconify left" data-icon="mdi:book-open"></i> - {% trans "View class register of this week" %} - </a> - {% endif %} - </div> - - {# Week select #} - {% include "chronos/partials/week_select.html" with wanted_week=week %} - - {% else %} - {# Show if regular #} - <a class="waves-effect waves-light btn-flat no-print" - href="{% url "timetable" type.value pk %}"> - <i class="material-icons iconify left" data-icon="mdi:play-box-outline"></i> - {% trans "Show SMART PLAN" %} - </a> - - {% has_perm "alsijil.view_week_rule" user as can_view_week_view %} - {% if is_alsijil_installed and can_view_week_view %} - <a class="waves-effect waves-light btn-flat no-print" - href="{% url "week_view_by_week" year=week.year week=week.week %}"> - <i class="material-icons iconify left" data-icon="mdi:book-open"></i> - {% trans "View class register of this week" %} - </a> - {% endif %} - {% endif %} - </div> - - {% include "core/partials/announcements.html" with announcements=announcements show_interval=1 %} - - {# show full timetable on tablets, laptops and pcs #} - <div class="timetable-plan hide-on-small-and-down"> - {% include "chronos/partials/week_timetable.html" %} - </div> - - {# show 5 seperate ones on mobiles #} - <div class="timetable-plan hide-on-med-and-up"> - {% for weekday in weekdays %} - <div class="card timetable-mobile-title-card"> - <div class="card-content"> - <span class="card-title"> - {{ weekday.name }} - </span> - {% if smart %} - {{ weekday.date }} - {% if weekday.holiday %} - <br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %} - {% endif %} - {% endif %} - </div> - </div> - {% for row in timetable %} - <div class="row"> - <div class="col s4"> - {% include "chronos/partials/period_time.html" with period=row.period periods=periods %} - </div> - - {% for col in row.cols %} - {% if forloop.counter0 == weekday.key %} - <div class="col s8"> - {# A lesson #} - {% if row.type == "period" %} - {% include "chronos/partials/elements.html" with elements=col %} - {% else %} - {% include "chronos/partials/supervision.html" with supervision=col %} - {% endif %} - </div> - {% endif %} - {% endfor %} - </div> - {% endfor %} - {% endfor %} - </div> -{% endblock %} diff --git a/aleksis/apps/chronos/templates/chronos/widget.html b/aleksis/apps/chronos/templates/chronos/widget.html deleted file mode 100644 index 10d3c19aa14d1ec68a25d2f327d51ab3ef0a341a..0000000000000000000000000000000000000000 --- a/aleksis/apps/chronos/templates/chronos/widget.html +++ /dev/null @@ -1,30 +0,0 @@ -{# -*- engine:django -*- #} - -{% load i18n static humanize %} - -<div class="card"> - <div class="card-content"> - <span class="card-title"> - {% blocktrans with day=day|naturalday:"l" %} - My timetable for {{ day }} - {% endblocktrans %} - </span> - <div class="timetable-plan"> - {% if has_plan %} - {% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %} - {% else %} - <figure class="alert warning"> - <i class="material-icons iconify left" data-icon="mdi:alert-outline"></i> - {% blocktrans %} - There is no timetable linked to your person. - {% endblocktrans %} - </figure> - {% endif %} - </div> - </div> - {% if has_plan %} - <div class="card-action"> - <a href="{% url "my_timetable" %}">{% trans "Go to smart plan" %}</a> - </div> - {% endif %} -</div> diff --git a/aleksis/apps/chronos/urls.py b/aleksis/apps/chronos/urls.py index e4cf26197a1e45fefac0bc16021fc05196ddc109..7d55323042b9a23438975c589bd23967c3cbb396 100644 --- a/aleksis/apps/chronos/urls.py +++ b/aleksis/apps/chronos/urls.py @@ -3,78 +3,9 @@ from django.urls import path from . import views urlpatterns = [ - path("", views.all_timetables, name="all_timetables"), - path("timetable/my/", views.my_timetable, name="my_timetable"), - path( - "timetable/my/<int:year>/<int:month>/<int:day>/", - views.my_timetable, - name="my_timetable_by_date", - ), - path("timetable/<str:type_>/<int:pk>/", views.timetable, name="timetable"), - path( - "timetable/<str:type_>/<int:pk>/<int:year>/<int:week>/", - views.timetable, - name="timetable_by_week", - ), - path( - "timetable/<str:type_>/<int:pk>/print/", - views.timetable, - {"is_print": True}, - name="timetable_print", - ), - path( - "timetable/<str:type_>/<int:pk>/<str:regular>/", - views.timetable, - name="timetable_regular", - ), - path("lessons/", views.lessons_day, name="lessons_day"), - path( - "lessons/<int:year>/<int:month>/<int:day>/", - views.lessons_day, - name="lessons_day_by_date", - ), - path( - "lessons/<int:id_>/<int:week>/substitution/", - views.edit_substitution, - name="edit_substitution", - ), - path( - "lessons/<int:id_>/<int:week>/substitution/delete/", - views.delete_substitution, - name="delete_substitution", - ), - path("substitutions/", views.substitutions, name="substitutions"), path( "substitutions/print/", - views.substitutions, - {"is_print": True}, + views.substitutions_print, name="substitutions_print", ), - path( - "substitutions/<int:year>/<int:month>/<int:day>/", - views.substitutions, - name="substitutions_by_date", - ), - path( - "substitutions/<int:year>/<int:month>/<int:day>/print/", - views.substitutions, - {"is_print": True}, - name="substitutions_print_by_date", - ), - path("supervisions/", views.supervisions_day, name="supervisions_day"), - path( - "supervisions/<int:year>/<int:month>/<int:day>/", - views.supervisions_day, - name="supervisions_day_by_date", - ), - path( - "supervisions/<int:id_>/<int:week>/substitution/", - views.edit_supervision_substitution, - name="edit_supervision_substitution", - ), - path( - "supervisions/<int:id_>/<int:week>/substitution/delete/", - views.delete_supervision_substitution, - name="delete_supervision_substitution", - ), ] diff --git a/aleksis/apps/chronos/views.py b/aleksis/apps/chronos/views.py index 8bd3307977dbe5a625df0223edf7a85044ca5f78..a45997ca580db9f8fa442be52d88054055e2661d 100644 --- a/aleksis/apps/chronos/views.py +++ b/aleksis/apps/chronos/views.py @@ -1,468 +1,25 @@ -from datetime import datetime from typing import Optional -from django.apps import apps -from django.db.models import FilteredRelation, Q -from django.http import HttpRequest, HttpResponse, HttpResponseNotFound -from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse -from django.utils import timezone -from django.utils.translation import gettext as _ -from django.views.decorators.cache import never_cache +from django.http import HttpRequest, HttpResponse -import reversion -from django_tables2 import RequestConfig from rules.contrib.views import permission_required 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, - get_supervision_substitution_by_id, - get_teachers, ) -from .util.date import CalendarWeek, get_weeks_for_year, week_weekday_to_date -from .util.js import date_unix - - -@pwa_cache -@permission_required("chronos.view_timetable_overview_rule") -def all_timetables(request: HttpRequest) -> HttpResponse: - """View all timetables for persons, groups and rooms.""" - context = {} - - 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) - - -@pwa_cache -@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: - """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) - else: - 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 - type_ = person.timetable_type - - # Build timetable - timetable = build_timetable("person", person, wanted_day) - week_timetable = build_timetable("person", person, wanted_week) - - if type_ is None: - # If no student or teacher, redirect to all timetables - return redirect("all_timetables") - - super_el = person.timetable_object - - context["timetable"] = timetable - context["week_timetable"] = week_timetable - 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 - 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") - - -@pwa_cache -@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, - is_print: bool = False, -) -> HttpResponse: - """View a selected timetable for a person, group or room.""" - context = {} - - is_smart = regular != "regular" - - if is_print: - is_smart = False - - el = get_el_by_pk(request, type_, pk, prefetch=True) - - if isinstance(el, HttpResponseNotFound): - return HttpResponseNotFound() - - type_ = TimetableType.from_string(type_) - - if year and week: - wanted_week = CalendarWeek(year=year, week=week) - else: - wanted_week = TimePeriod.get_relevant_week_from_datetime() - - # Build timetable - 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 - context["type"] = type_ - context["pk"] = pk - context["el"] = el - 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] - 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( - "timetable_by_week", args=[type_.value, pk, week_prev.year, week_prev.week] - ) - context["url_next"] = reverse( - "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 - - 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) - else: - return render(request, "chronos/timetable.html", context) - - -@pwa_cache -@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: - """View all lessons 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 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) - RequestConfig(request).configure(lessons_table) - - context["lessons_table"] = lessons_table - context["day"] = wanted_day - context["lesson_periods"] = lesson_periods - - context["datepicker"] = { - "date": date_unix(wanted_day), - "dest": reverse("lessons_day"), - } - - context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day( - wanted_day, "lessons_day_by_date" - ) - - return render(request, "chronos/lessons_day.html", context) - - -@never_cache -@permission_required("chronos.edit_substitution_rule", fn=get_substitution_by_id) -def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse: - """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) - - if lesson_substitution: - edit_substitution_form = LessonSubstitutionForm( - request, request.POST or None, instance=lesson_substitution - ) - else: - edit_substitution_form = LessonSubstitutionForm( - request, - request.POST or None, - ) - - context["substitution"] = lesson_substitution - - if request.method == "POST" and edit_substitution_form.is_valid(): - with reversion.create_revision(atomic=True): - 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) - - -@permission_required("chronos.delete_substitution_rule", fn=get_substitution_by_id) -def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse: - """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] - return redirect("lessons_day_by_date", year=date.year, month=date.month, day=date.day) @pwa_cache @permission_required("chronos.view_substitutions_rule") -def substitutions( +def substitutions_print( 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) - - -@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 - 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, - request.POST or None, - ) - - context["substitution"] = supervision_substitution - - if request.method == "POST" and edit_supervision_substitution_form.is_valid(): - with reversion.create_revision(atomic=True): - 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) + context = get_substitutions_context_data(request, year, month, day, is_print=True) + return render_pdf(request, "chronos/substitutions_print.html", context)