Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Chronos
  • sunweaver/AlekSIS-App-Chronos
  • sggua/AlekSIS-App-Chronos
  • tincmeKdenka/AlekSIS-App-Chronos
  • ligquamacti/AlekSIS-App-Chronos
  • 1crotatilhe/AlekSIS-App-Chronos
  • 1compluningi/AlekSIS-App-Chronos
  • starwardcarfi/AlekSIS-App-Chronos
  • ceohecholeg/AlekSIS-App-Chronos
  • 7quecontranchi/AlekSIS-App-Chronos
  • 8evsubcesza/AlekSIS-App-Chronos
  • unscinKibdzu/AlekSIS-App-Chronos
  • delucPchondmu/AlekSIS-App-Chronos
13 results
Show changes
Showing
with 116 additions and 541 deletions
<strong>
<span data-position="bottom" class="tooltipped" data-tooltip="{{ subject.name }}">{{ subject.short_name }}</span>
</strong>
{{ subject.short_name|default:subject.name }}
{% if subject.colour_fg %}
color: {{ subject.colour_fg }};
{% endif %}
{% if subject.colour_bg and subject.colour_bg != subject.colour_fg %}
background-color: {{ subject.colour_bg }};
{% endif %}
{% load i18n %}
{% if sub.cancelled %}
{% if el.cancelled %}
<span class="badge new green">{% trans "Cancelled" %}</span>
{% elif item.el.cancelled_for_teachers %}
<span class="badge new green">{% trans "Cancelled for teachers" %}</span>
{% endif %}
{% if item.type == "substitution" %}
{% if item.el.cancelled or item.el.cancelled_for_teachers %}
green-text
{% else %}
black-text
{% endif %}
{% elif item.type == "supervision_substitution" %}
blue-text
{% elif item.type == "event" %}
purple-text
{% if el.cancelled %}
green-text
{% else %}
black-text
{% endif %}
{% if type == "substitution" %}
{% include "chronos/partials/groups.html" with groups=el.lesson_period.lesson.groups.all %}
{% elif type == "extra_lesson" or type == "event" %}
{% if el.cancelled and el.amends.groups.all %}
{% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
{% elif el.groups.all and el.amends.groups.all %}
<s>{% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}</s>
&nbsp;&nbsp;
<strong>{% include "chronos/partials/groups.html" with groups=el.groups.all %}</strong>
{% elif el.groups.all and not el.amends.groups.all %}
{% include "chronos/partials/groups.html" with groups=el.groups.all %}
{% elif el.amends.groups.all %}
{% include "chronos/partials/groups.html" with groups=el.amends.groups.all %}
{% endif %}
{% load i18n %}
<strong>
{% if type == "substitution" and item.start_period == item.end_period %}
{{ el.lesson_period.period.period }}.
{% elif type == "substitution" %}
{{ item.start_period }}.–{{ item.end_period }}.
{% elif type == "extra_lesson" %}
{{ el.period.period }}.
{% elif type == "event" %}
{% if el.period_from_on_day == el.period_to_on_day %}
{{ el.period_from_on_day }}.
{% if el.REFERENCE_OBJECT.slot_number_start and el.REFERENCE_OBJECT.slot_number_end %}
{% if el.REFERENCE_OBJECT.slot_number_start == el.REFERENCE_OBJECT.slot_number_end %}
{{ el.REFERENCE_OBJECT.slot_number_start }}.
{% else %}
{{ el.period_from_on_day }}.–{{ el.period_to_on_day }}.
{{ el.REFERENCE_OBJECT.slot_number_start }}.–{{ el.REFERENCE_OBJECT.slot_number_end }}.
{% endif %}
{% elif type == "supervision_substitution" %}
{% with break=el.supervision.break_item %}
{{ break.after_period_number }}./{{ break.before_period_number }}.
{% endwith %}
{% elif el.DTSTART.dt.time %}
{{ el.DTSTART.dt.time }}–{{ el.DTEND.dt.time }}
{% else %}
{% trans "all day" %}
{% endif %}
</strong>
{% if type == "substitution" %}
{% if el.cancelled or el.cancelled_for_teachers %}
{# Cancelled lesson: no room #}
{% elif el.room and el.lesson_period.room %}
{# New and old room available #}
<span class="tooltipped" data-position="bottom"
data-tooltip="{{ el.lesson_period.room.name }} → {{ el.room.name }}"
title="{{ el.lesson_period.room.name }} → {{ el.room.name }}">
<a href="{% url "timetable" "room" el.lesson_period.room.pk %}">
<s>{{ el.lesson_period.room.short_name }}</s>
</a>
<a href="{% url "timetable" "room" el.room.pk %}">
<strong>{{ el.room.short_name }}</strong>
</a>
</span>
{% elif el.room and not el.lesson_period.room %}
{# Only new room available #}
{% include "chronos/partials/room.html" with room=el.room %}
{% elif not el.room and not el.lesson_period.room %}
{# Nothing to view #}
{% else %}
{# Only old room available #}
{% include "chronos/partials/room.html" with room=el.lesson_period.room %}
{% endif %}
{% elif type == "supervision_substitution" %}
{% with supervision=el.supervision %}
<span data-position="bottom" class="tooltipped"
data-tooltip="{{ supervision.area.name }}" title="{{ supervision.area.name }}">
{{ supervision.area.short_name }}
</span>
{% endwith %}
{% elif type == "extra_lesson" %}
{% include "chronos/partials/room.html" with room=el.room %}
{% elif type == "event" %}
{% for room in el.rooms.all %}
{% include "chronos/partials/room.html" with room=room %}{% if not forloop.last %},{% endif %}
{% endfor %}
{% endif %}
{% if el.cancelled and el.amends.rooms.all %}
{% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
{% elif el.rooms.all and el.amends.rooms.all %}
<s>{% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}</s>
&nbsp;&nbsp;
<strong>{% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}</strong>
{% elif el.rooms.all and not el.amends.rooms.all %}
{% include "chronos/partials/rooms.html" with rooms=el.rooms.all %}
{% elif el.amends.rooms.all %}
{% include "chronos/partials/rooms.html" with rooms=el.amends.rooms.all %}
{% endif %}
{% load i18n %}
{% if type == "substitution" %}
{% if not el.lesson_period.lesson.subject and not el.subject %}
{% elif el.cancelled or el.cancelled_for_teachers %}
<span data-position="bottom" class="tooltipped" data-tooltip="{{ el.lesson_period.lesson.subject.name }}">
<s>{{ el.lesson_period.lesson.subject.short_name }}</s>
</span>
{% elif el.subject and el.lesson_period.lesson.subject %}
<span data-position="bottom" class="tooltipped" data-tooltip="{{ el.lesson_period.lesson.subject.name }}">
<s>{{ el.lesson_period.lesson.subject.short_name }}</s>
</span>
<span data-position="bottom" class="tooltipped" data-tooltip="{{ el.subject.name }}">
<strong>{{ el.subject.short_name }}</strong>
</span>
{% elif el.subject and not el.lesson_period.lesson.subject %}
{% include "chronos/partials/subject.html" with subject=el.subject %}
{% else %}
{% include "chronos/partials/subject.html" with subject=el.lesson_period.lesson.subject %}
{% endif %}
{% elif type == "supervision_substitution" %}
{% if el.name == "supervision" %}
{% trans "Supervision" %}
{% elif type == "extra_lesson" %}
{% elif not el.amends.subject and not el.subject %}
{% if el.amends.title %}
<s>{{ el.amends.title }}</s>
{% endif %}
{% if el.title %}
<s>{{ el.title }}</s>
{% endif %}
{% elif el.cancelled %}
<s>{% include "chronos/partials/subject.html" with subject=el.amends.subject %}</s>
{% elif el.subject and el.amends.subject %}
<s>{% include "chronos/partials/subject.html" with subject=el.amends.subject %}</s>
&nbsp;&nbsp;
<strong>{% include "chronos/partials/subject.html" with subject=el.subject %}</strong>
{% elif el.subject and not el.amends.subject %}
{% include "chronos/partials/subject.html" with subject=el.subject %}
{% elif type == "event" %}
{% trans "Event" %}
{% else %}
{% include "chronos/partials/subject.html" with subject=el.amends.subject %}
{% endif %}
{% if type == "substitution" %}
{% if el.cancelled and el.lesson_period.lesson.teachers.all %}
{% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
{% elif el.teachers.all and el.lesson_period.lesson.teachers.all %}
<s>
{% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
</s>
<strong>
{% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
</strong>
{% elif el.teachers.all and not el.lesson_period.lesson.teachers.all %}
{% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
{% elif el.lesson_period.lesson.teachers.all %}
{% include "chronos/partials/teachers.html" with teachers=el.lesson_period.lesson.teachers.all %}
{% endif %}
{% elif type == "supervision_substitution" %}
{% if el.cancelled and el.amends.teachers.all %}
{% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
{% elif el.teachers.all and el.amends.teachers.all %}
<s>
{% include "chronos/partials/teachers.html" with teachers=el.supervision.teachers %}
{% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
</s>
<strong>
{% include "chronos/partials/teachers.html" with teachers=el.teachers %}
{% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
</strong>
{% elif type == "extra_lesson" or type == "event" %}
{% elif el.teachers.all and not el.amends.teachers.all %}
{% include "chronos/partials/teachers.html" with teachers=el.teachers.all %}
{% elif el.amends.teachers.all %}
{% include "chronos/partials/teachers.html" with teachers=el.amends.teachers.all %}
{% endif %}
{% load i18n %}
<div class="card lesson-card supervision-card {% if active_day and week_day == active_day %} z-depth-5 active {% endif %}">
<div class="card-content">
{% if supervision %}
<div style="
{% if supervision.area.colour_fg %}
color: {{ supervision.area.colour_fg }};
{% endif %}
{% if supervision.area.colour_bg and supervision.area.colour_bg != supervision.area.colour_fg %}
background-color: {{ supervision.area.colour_bg }};
{% endif %}
" class="{% if supervision.get_substitution and smart %}lesson-with-sub{% endif %}">
<p>
<strong>{% trans "Supervision" %}</strong>
<span data-position="bottom" class="tooltipped"
data-tooltip="{{ supervision.area.name }}" title="{{ supervision.area.name }}">
{{ supervision.area.short_name }}
</span>
{% if supervision.get_substitution and smart %}
{% include "chronos/partials/subs/teachers.html" with type="supervision_substitution" el=supervision.get_substitution %}
{% elif type == "supervision_area" %}
{% include "chronos/partials/teachers.html" with teachers=supervision.teachers %}
{% endif %}
</p>
</div>
{% endif %}
</div>
</div>
{% for teacher in teachers %}
<span data-position="bottom" class="tooltipped"
data-tooltip="{{ teacher }}">
<a href="{% url "timetable" "teacher" teacher.pk %}">
{{ teacher.short_name }}{% if not forloop.last %},{% endif %}
</a>
</span>
{{ teacher.short_name|default:teacher.full_name }}{% if not forloop.last %},{% endif %}
{% endfor %}
{% load i18n %}
<span class="badge new orange center-align holiday-badge">{% trans "Today" %}</span>
{% load i18n %}
<div class="col s12 m6 right">
<div class="col s2 no-print">
<a class="waves-effect waves-teal btn-flat btn-flat-medium right" href="{{ url_prev }}">
<i class="material-icons iconify center" data-icon="mdi:chevron-left"></i>
</a>
</div>
<div class="input-field col s8 no-margin hide-on-med-and-up">
<select id="calendar-week-1">
{% for week in weeks %}
<option value="{{ week.week }}" {% if week == wanted_week %}
selected {% endif %}>{% trans "CW" %} {{ week.week }}
({{ week.0|date:"SHORT_DATE_FORMAT" }}–{{ week.6|date:"SHORT_DATE_FORMAT" }})
</option>
{% endfor %}
</select>
</div>
<div class="input-field col s8 no-margin hide-on-med-and-down">
<select id="calendar-week-2">
{% for week in weeks %}
<option value="{{ week.week }}" {% if week == wanted_week %}
selected {% endif %}>{% trans "CW" %} {{ week.week }}
({{ week.0|date:"j.n." }}–{{ week.6|date:"SHORT_DATE_FORMAT" }})
</option>
{% endfor %}
</select>
</div>
<div class="input-field col s8 no-margin hide-on-small-and-down hide-on-large-only">
<select id="calendar-week-3">
{% for week in weeks %}
<option value="{{ week.week }}" {% if week == wanted_week %}
selected {% endif %}>{% trans "CW" %} {{ week.week }} ({{ week.0|date:"j.n." }}–{{ week.6|date:"j.n." }})
</option>
{% endfor %}
</select>
</div>
<div class="col s2 no-print">
<a class="waves-effect waves-teal btn-flat btn-flat-medium left" href="{{ url_next }}">
<i class="material-icons iconify center" data-icon="mdi:chevron-right"></i>
</a>
</div>
</div>
{# Week days #}
<div class="row">
<div class="col {% if active_day %}s1{% else %}s2{% endif %}">
</div>
{# Show short weekdays on tablets #}
{% for weekday in weekdays_short %}
<div class="col s2 hide-on-large-only">
<div class="card timetable-title-card {% if active_day and weekday.date == active_day %} z-depth-5 {% endif %}">
<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 %}
{% if weekday.date == today %}
<br/> {% include "chronos/partials/today.html" %}
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
{# Show long weekdays elsewere #}
{% for weekday in weekdays %}
<div class="col {% if weekday.date == active_day %} s3 {% else %} s2 {% endif %} hide-on-med-only">
<div class="card timetable-title-card {% if active_day and weekday.date == active_day %} z-depth-5 {% endif %}">
<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 %}
{% if weekday.date == today %}
<br/> {% include "chronos/partials/today.html" %}
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{# Lessons #}
{% for row in timetable %}
<div class="row">
<div class="col {% if active_day %}s1{% else %}s2{% endif %}">
{% if row.type == "period" %}
{% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
{% endif %}
</div>
{% for col in row.cols %}
{# A lesson #}
<div class="col {% if forloop.counter0 == active_day.weekday %} s3 {% else %} s2 {% endif %}">
{% if row.type == "period" %}
{% include "chronos/partials/elements.html" with elements=col week_day=forloop.counter0 active_day=active_day.weekday %}
{% else %}
{% include "chronos/partials/supervision.html" with supervision=col %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
\ No newline at end of file
......@@ -18,13 +18,13 @@
{% include "core/partials/announcements.html" with announcements=c.announcements show_recipients=1 %}
{% include "chronos/partials/headerbox.html" with affected_teachers=c.affected_teachers affected_groups=c.affected_groups absent_teachers=c.absent_teachers absent_groups=c.absent_groups print=1 %}
{% include "chronos/partials/headerbox.html" with absent_teachers=c.absent_teachers absent_groups=c.absent_groups affected_teachers=c.affected_teachers affected_groups=c.affected_groups %}
<table class="substitutions">
<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>{% blocktrans %}Groups{% endblocktrans %}</th>
<th>{% blocktrans %}Time{% endblocktrans %}</th>
<th>{% blocktrans %}Teachers{% endblocktrans %}</th>
<th>{% blocktrans %}Subject{% endblocktrans %}</th>
<th>{% blocktrans %}Room{% endblocktrans %}</th>
......@@ -47,30 +47,30 @@
<tbody>
{% for item in c.substitutions %}
{% ifchanged item.el.lesson_period.lesson.groups_to_show_names %}
{% ifchanged item.el.REFERENCE_OBJECT.group_names %}
</tbody>
<tbody class="{% cycle "striped" "not-striped" %}">
{% endifchanged %}
<tr class="{% include "chronos/partials/subs/colour.html" with item=item %}">
<tr class="{% include "chronos/partials/subs/colour.html" with el=item.el.REFERENCE_OBJECT %}">
<td>
{% include "chronos/partials/subs/groups.html" with type=item.type el=item.el %}
{% include "chronos/partials/subs/groups.html" with el=item.el.REFERENCE_OBJECT %}
</td>
<td>
{% include "chronos/partials/subs/period.html" with type=item.type el=item.el item=item %}
{% include "chronos/partials/subs/period.html" with el=item.el %}
</td>
<td>
{% include "chronos/partials/subs/teachers.html" with type=item.type el=item.el %}
{% include "chronos/partials/subs/teachers.html" with el=item.el.REFERENCE_OBJECT %}
</td>
<td>
{% include "chronos/partials/subs/subject.html" with type=item.type el=item.el %}
{% include "chronos/partials/subs/subject.html" with el=item.el.REFERENCE_OBJECT %}
</td>
<td>
{% include "chronos/partials/subs/room.html" with type=item.type el=item.el %}
{% include "chronos/partials/subs/rooms.html" with el=item.el.REFERENCE_OBJECT %}
</td>
<td>
{% include "chronos/partials/subs/badge.html" with sub=item.el %}
{% include "chronos/partials/subs/comment.html" with el=item.el %}
{% include "chronos/partials/subs/badge.html" with el=item.el.REFERENCE_OBJECT %}
{% include "chronos/partials/subs/comment.html" with el=item.el.REFERENCE_OBJECT %}
</td>
</tr>
{% endfor %}
......
{% extends 'core/base_print.html' %}
{% load data_helpers static i18n %}
{% block extra_head %}
<link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
<link rel="stylesheet" href="{% static 'css/chronos/timetable_print.css' %}">
{% endblock %}
{% block page_title %}
{% trans "Timetable" %} <i>{{ el.short_name }}</i>
{% endblock %}
{% block content %}
<div class="timetable-plan">
{# Week days #}
<div class="row">
<div class="col s2">
</div>
{% for weekday in weekdays_short %}
<div class="col s2">
<div class="card timetable-title-card">
<div class="card-content">
<span class="card-title">
{{ weekday.name }}
</span>
</div>
</div>
</div>
{% endfor %}
</div>
{% for row in timetable %}
<div class="row">
<div class="col s2">
{% if row.type == "period" %}
{% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
{% endif %}
</div>
{% for col in row.cols %}
{# A lesson #}
<div class="col s2">
{% if row.type == "period" %}
{% include "chronos/partials/elements.html" with elements=col %}
{% else %}
{% include "chronos/partials/supervision.html" with supervision=col %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}
......@@ -8,4 +8,9 @@ urlpatterns = [
views.substitutions_print,
name="substitutions_print",
),
path(
"substitutions/print/<str:day>/",
views.substitutions_print,
name="substitutions_print",
),
]
from collections import OrderedDict
from datetime import date
from datetime import date, datetime, time
from typing import Union
from django.apps import apps
......@@ -7,9 +7,11 @@ from django.apps import apps
from calendarweek import CalendarWeek
from aleksis.apps.chronos.managers import TimetableType
from aleksis.apps.chronos.models import SupervisionEvent
from aleksis.core.models import Group, Person, Room
LessonPeriod = apps.get_model("chronos", "LessonPeriod")
LessonEvent = apps.get_model("chronos", "LessonEvent")
TimePeriod = apps.get_model("chronos", "TimePeriod")
Break = apps.get_model("chronos", "Break")
Supervision = apps.get_model("chronos", "Supervision")
......@@ -380,96 +382,57 @@ def build_timetable(
return rows
def build_substitutions_list(wanted_day: date) -> list[dict]:
def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person], set[Group]]:
rows = []
subs = LessonSubstitution.objects.on_day(wanted_day).order_by(
"lesson_period__lesson__groups", "lesson_period__period"
affected_teachers = set()
affected_groups = set()
lesson_events = LessonEvent.get_single_events(
datetime.combine(wanted_day, time.min),
datetime.combine(wanted_day, time.max),
params={"amending": True},
with_reference_object=True,
)
start_period = None
for i, sub in enumerate(subs):
if not sub.cancelled_for_teachers:
sort_a = sub.lesson_period.lesson.groups_to_show_names
else:
sort_a = f"Z.{sub.lesson_period.lesson.teacher_names}"
# Get next substitution
next_sub = subs[i + 1] if i + 1 < len(subs) else None
# Check if next substitution is equal with this substitution
if (
next_sub
and sub.comment == next_sub.comment
and sub.cancelled == next_sub.cancelled
and sub.subject == next_sub.subject
and sub.room == next_sub.room
and sub.lesson_period.lesson == next_sub.lesson_period.lesson
and set(sub.teachers.all()) == set(next_sub.teachers.all())
):
if not start_period:
start_period = sub.lesson_period.period.period
continue
for lesson_event in lesson_events:
affected_teachers.update(lesson_event["REFERENCE_OBJECT"].teachers.all())
affected_teachers.update(lesson_event["REFERENCE_OBJECT"].amends.teachers.all())
affected_groups.update(lesson_event["REFERENCE_OBJECT"].groups.all())
affected_groups.update(lesson_event["REFERENCE_OBJECT"].amends.groups.all())
row = {
"type": "substitution",
"sort_a": sort_a,
"sort_b": str(sub.lesson_period.period.period),
"el": sub,
"start_period": start_period if start_period else sub.lesson_period.period.period,
"end_period": sub.lesson_period.period.period,
"sort_a": lesson_event["REFERENCE_OBJECT"].group_names,
"sort_b": str(lesson_event["DTSTART"]),
"el": lesson_event,
}
if start_period:
start_period = None
rows.append(row)
# Get supervision substitutions
super_subs = SupervisionSubstitution.objects.filter(date=wanted_day)
supervision_events = SupervisionEvent.get_single_events(
datetime.combine(wanted_day, time.min),
datetime.combine(wanted_day, time.max),
params={"amending": True},
with_reference_object=True,
)
print(supervision_events)
for supervision_event in supervision_events:
affected_teachers.update(supervision_event["REFERENCE_OBJECT"].teachers.all())
affected_teachers.update(supervision_event["REFERENCE_OBJECT"].amends.teachers.all())
for super_sub in super_subs:
row = {
"type": "supervision_substitution",
"sort_a": f"Z.{super_sub.teacher}",
"sort_b": str(super_sub.supervision.break_item.after_period_number),
"el": super_sub,
"sort_a": "Z",
"sort_b": str(supervision_event["DTSTART"]),
"el": supervision_event,
}
rows.append(row)
# Get extra lessons
extra_lessons = ExtraLesson.objects.on_day(wanted_day)
for extra_lesson in extra_lessons:
row = {
"type": "extra_lesson",
"sort_a": str(extra_lesson.group_names),
"sort_b": str(extra_lesson.period.period),
"el": extra_lesson,
}
rows.append(row)
# Get events
events = Event.objects.on_day(wanted_day).annotate_day(wanted_day)
rows.sort(key=lambda row: row["sort_a"] + row["sort_b"])
for event in events:
sort_a = event.group_names if event.groups.all() else f"Z.{event.teacher_names}"
row = {
"type": "event",
"sort_a": sort_a,
"sort_b": str(event.period_from_on_day),
"el": event,
}
rows.append(row)
# Sort all items
def sorter(row: dict):
return row["sort_a"] + row["sort_b"]
rows.sort(key=sorter)
return rows
return rows, affected_teachers, affected_groups
def build_weekdays(
......
from typing import Any, Optional
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import Model
from django.db.models.signals import m2m_changed, post_save, pre_delete
from django.dispatch import Signal, receiver
from celery import shared_task
......@@ -11,126 +6,9 @@ from reversion.models import Revision
def _get_substitution_models():
from aleksis.apps.chronos.models import (
Event,
ExtraLesson,
LessonSubstitution,
SupervisionSubstitution,
)
return [LessonSubstitution, Event, ExtraLesson, SupervisionSubstitution]
class TimetableChange:
"""A change to timetable models."""
def __init__(
self,
instance: Model,
changed_fields: Optional[dict[str, Any]] = None,
created: bool = False,
deleted: bool = False,
):
self.instance = instance
self.changed_fields = changed_fields or {}
self.created = created
self.deleted = deleted
class TimetableDataChangeTracker:
"""Helper class for tracking changes in timetable models by using signals."""
@classmethod
def get_models(cls) -> list[type[Model]]:
"""Return all models that should be tracked."""
from aleksis.apps.chronos.models import (
Event,
ExtraLesson,
LessonSubstitution,
SupervisionSubstitution,
)
return [LessonSubstitution, Event, ExtraLesson, SupervisionSubstitution]
def __init__(self):
self.changes = {}
self.m2m_fields = {}
if transaction.get_autocommit():
raise RuntimeError("Cannot use change tracker outside of transaction")
for model in self.get_models():
post_save.connect(self._handle_save, sender=model, weak=False)
pre_delete.connect(self._handle_delete, sender=model, weak=False)
# Register signals for all relevant m2m fields
m2m_fields = {
getattr(model, f.name).through: f
for f in model._meta.get_fields()
if f.many_to_many
}
self.m2m_fields.update(m2m_fields)
for through_model, _field in m2m_fields.items():
m2m_changed.connect(self._handle_m2m_changed, sender=through_model, weak=False)
transaction.on_commit(self.close)
def get_instance_key(self, instance: Model) -> str:
"""Get unique string key for an instance."""
return f"{instance._meta.model_name}_{instance.id}"
def _add_change(self, change: TimetableChange):
"""Add a change to the list of changes and update, if necessary."""
key = self.get_instance_key(change.instance)
if key not in self.changes or change.deleted or change.created:
self.changes[key] = change
else:
self.changes[key].changed_fields.update(change.changed_fields)
def _handle_save(self, sender: type[Model], instance: Model, created: bool, **kwargs):
"""Handle the save signal."""
change = TimetableChange(instance, created=created)
if not created:
change.changed_fields = instance.tracker.changed()
self._add_change(change)
def _handle_delete(self, sender: type[Model], instance: Model, **kwargs):
"""Handle the delete signal."""
change = TimetableChange(instance, deleted=True)
self._add_change(change)
def _handle_m2m_changed(
self,
sender: type[Model],
instance: Model,
action: str,
model: type[Model],
pk_set: set,
**kwargs,
):
"""Handle the m2m_changed signal."""
if action in ["pre_add", "pre_remove", "pre_clear"]:
field_name = self.m2m_fields[sender].name
current_value = list(getattr(instance, field_name).all())
if self.get_instance_key(instance) in self.changes:
change = self.changes[self.get_instance_key(instance)]
if field_name in change.changed_fields:
current_value = None
if current_value is not None:
change = TimetableChange(instance, changed_fields={field_name: current_value})
self._add_change(change)
def close(self):
"""Disconnect signals and send change signal."""
for model in self.get_models():
post_save.disconnect(self._handle_save, sender=model)
pre_delete.disconnect(self._handle_delete, sender=model)
for through_model, _field in self.m2m_fields.items():
m2m_changed.disconnect(self._handle_m2m_changed, sender=through_model)
from aleksis.apps.chronos.models import LessonEvent, SupervisionEvent
timetable_data_changed.send(sender=self, changes=self.changes)
return [LessonEvent, SupervisionEvent]
chronos_revision_created = Signal()
......