Newer
Older
from collections import OrderedDict
from datetime import date, datetime, time

Jonathan Weth
committed
from django.apps import apps
from aleksis.apps.chronos.managers import TimetableType
from aleksis.core.models import Group, Person, Room

Jonathan Weth
committed
LessonPeriod = apps.get_model("chronos", "LessonPeriod")
LessonEvent = apps.get_model("chronos", "LessonEvent")

Jonathan Weth
committed
TimePeriod = apps.get_model("chronos", "TimePeriod")
Break = apps.get_model("chronos", "Break")
Supervision = apps.get_model("chronos", "Supervision")

Jonathan Weth
committed
LessonSubstitution = apps.get_model("chronos", "LessonSubstitution")
SupervisionSubstitution = apps.get_model("chronos", "SupervisionSubstitution")
Event = apps.get_model("chronos", "Event")
ExtraLesson = apps.get_model("chronos", "ExtraLesson")

Jonathan Weth
committed
def build_timetable(
obj: Union[Group, Room, Person],
with_holidays: bool = True,

Jonathan Weth
committed
):
needed_breaks = []

Jonathan Weth
committed
is_person = False
if type_ == "person":
is_person = True
type_ = obj.timetable_type
if isinstance(date_ref, CalendarWeek):
if type_ is None:
return None
holidays_per_weekday = Holiday.in_week(date_ref) if with_holidays else {}
holiday = Holiday.on_day(date_ref) if with_holidays else None
# Get matching lesson periods
lesson_periods = LessonPeriod.objects
lesson_periods = (
lesson_periods.select_related(None)
.select_related("lesson", "lesson__subject", "period", "room")
.only(
"lesson",
"period",
"room",
"lesson__subject",
"period__weekday",
"period__period",
"lesson__subject__short_name",
"lesson__subject__name",
"lesson__subject__colour_fg",
"lesson__subject__colour_bg",
"room__short_name",
"room__name",
)
)
if is_week:
lesson_periods = lesson_periods.in_week(date_ref)
else:
lesson_periods = lesson_periods.on_day(date_ref)

Jonathan Weth
committed
if is_person:
lesson_periods = lesson_periods.filter_from_person(obj)

Jonathan Weth
committed
else:
lesson_periods = lesson_periods.filter_from_type(type_, obj, is_smart=with_holidays)
# Sort lesson periods in a dict
lesson_periods_per_period = lesson_periods.group_by_periods(is_week=is_week)
extra_lessons = ExtraLesson.objects
if is_week:
extra_lessons = extra_lessons.filter(week=date_ref.week, year=date_ref.year)
else:
extra_lessons = extra_lessons.on_day(date_ref)
extra_lessons = extra_lessons.filter_from_person(obj)
extra_lessons = extra_lessons.filter_from_type(type_, obj)
extra_lessons = extra_lessons.only(
"week",
"year",
"period",
"subject",
"room",
"comment",
"period__weekday",
"period__period",
"subject__short_name",
"subject__name",
"subject__colour_fg",
"subject__colour_bg",
"room__short_name",
"room__name",
)
extra_lessons_per_period = extra_lessons.group_by_periods(is_week=is_week)
events = events.in_week(date_ref) if is_week else events.on_day(date_ref)
events = events.only(
"id",
"title",
"date_start",
"date_end",
"period_from",
"period_to",
"period_from__weekday",
"period_from__period",
"period_to__weekday",
"period_to__period",
)
if is_person:
events_to_display = events.filter_from_person(obj)
else:
events_to_display = events.filter_from_type(type_, obj)
# Sort events in a dict
events_per_period = {}
events_for_replacement_per_period = {}
if is_week and event.date_start < date_ref[TimePeriod.weekday_min]:
# If start date not in current week, set weekday and period to min
weekday_from = TimePeriod.weekday_min
period_from_first_weekday = TimePeriod.period_min
else:
weekday_from = event.date_start.weekday()
period_from_first_weekday = event.period_from.period
if is_week and event.date_end > date_ref[TimePeriod.weekday_max]:
# If end date not in current week, set weekday and period to max
weekday_to = TimePeriod.weekday_max
period_to_last_weekday = TimePeriod.period_max
else:
weekday_to = event.date_end.weekday()
period_to_last_weekday = event.period_to.period
for weekday in range(weekday_from, weekday_to + 1):
if not is_week and weekday != date_ref.weekday():
# If daily timetable for person, skip other weekdays
continue
# If start day, use start period else use min period
period_from = (
period_from_first_weekday if weekday == weekday_from else TimePeriod.period_min
)
# If end day, use end period else use max period
period_to = period_to_last_weekday if weekday == weekday_to else TimePeriod.periox_max
# The following events are possibly replacing some lesson periods
if period not in events_for_replacement_per_period:
events_for_replacement_per_period[period] = {} if is_week else []
if is_week and weekday not in events_for_replacement_per_period[period]:
events_for_replacement_per_period[period][weekday] = []
events_for_replacement_per_period[period].append(event)
events_for_replacement_per_period[period][weekday].append(event)
# and the following will be displayed in the timetable
if event in events_to_display:
if period not in events_per_period:
events_per_period[period] = {} if is_week else []
if is_week and weekday not in events_per_period[period]:
events_per_period[period][weekday] = []
if not is_week:
events_per_period[period].append(event)
else:
events_per_period[period][weekday].append(event)
week = CalendarWeek.from_date(date_ref) if not is_week else date_ref
Supervision.objects.in_week(week)
.all()
.annotate_week(week)
.filter_by_teacher(obj)
.only(
"area",
"break_item",
"teacher",
"area",
"area__short_name",
"area__name",
"area__colour_fg",
"area__colour_bg",
"break_item__short_name",
"break_item__name",
"break_item__after_period__period",
"break_item__after_period__weekday",
"break_item__before_period__period",
"break_item__before_period__weekday",
"teacher__short_name",
"teacher__first_name",
"teacher__last_name",
)

Jonathan Weth
committed
supervisions = supervisions.filter_by_weekday(date_ref.weekday())
supervisions_per_period_after = {}
for supervision in supervisions:
weekday = supervision.break_item.weekday
period_after_break = supervision.break_item.before_period_number
if period_after_break not in needed_breaks:
needed_breaks.append(period_after_break)
if is_week and period_after_break not in supervisions_per_period_after:
supervisions_per_period_after[period_after_break] = {}

Jonathan Weth
committed
supervisions_per_period_after[period_after_break] = supervision
else:
supervisions_per_period_after[period_after_break][weekday] = supervision
# Get ordered breaks
breaks = OrderedDict(sorted(Break.get_breaks_dict().items()))
rows = []
for period, break_ in breaks.items(): # period is period after break
# Break
if type_ == TimetableType.TEACHER and period in needed_breaks:
row = {
"type": "break",
"after_period": break_.after_period_number,
"before_period": break_.before_period_number,
"time_start": break_.time_start,
"time_end": break_.time_end,
}

Jonathan Weth
committed
cols = []
for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):

Jonathan Weth
committed
col = None
if (
period in supervisions_per_period_after
and weekday not in holidays_per_weekday
) and weekday in supervisions_per_period_after[period]:
col = supervisions_per_period_after[period][weekday]

Jonathan Weth
committed
cols.append(col)

Jonathan Weth
committed
row["cols"] = cols
else:
if period in supervisions_per_period_after and not holiday:

Jonathan Weth
committed
col = supervisions_per_period_after[period]
row["col"] = col
rows.append(row)
if period <= TimePeriod.period_max:
row = {
"type": "period",
"period": period,
"time_start": break_.before_period.time_start,
"time_end": break_.before_period.time_end,
}

Jonathan Weth
committed

Jonathan Weth
committed
cols = []
for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):

Jonathan Weth
committed
# Skip this period if there are holidays
if weekday in holidays_per_weekday:
cols.append([])
continue

Jonathan Weth
committed
col = []

Jonathan Weth
committed
events_for_this_period = (
events_per_period[period].get(weekday, [])
if period in events_per_period
else []
)
events_for_replacement_for_this_period = (
events_for_replacement_per_period[period].get(weekday, [])
if period in events_for_replacement_per_period
else []
)

Jonathan Weth
committed
lesson_periods_for_this_period = (
lesson_periods_per_period[period].get(weekday, [])
if period in lesson_periods_per_period
else []
)

Jonathan Weth
committed
# Add lesson periods

Jonathan Weth
committed
if lesson_periods_for_this_period:
if events_for_replacement_for_this_period:

Jonathan Weth
committed
# If there is a event in this period,
# we have to check whether the actual lesson is taking place.
for lesson_period in lesson_periods_for_this_period:
replaced_by_event = lesson_period.is_replaced_by_event(
events_for_replacement_for_this_period,

Jonathan Weth
committed
[obj] if type_ == TimetableType.GROUP else None,
)
lesson_period.replaced_by_event = replaced_by_event
if not replaced_by_event or (

Jonathan Weth
committed
):
col.append(lesson_period)

Jonathan Weth
committed
else:
col += lesson_periods_for_this_period

Jonathan Weth
committed

Jonathan Weth
committed
if period in extra_lessons_per_period:
col += extra_lessons_per_period[period].get(weekday, [])

Jonathan Weth
committed
col += events_for_this_period

Jonathan Weth
committed
cols.append(col)
row["cols"] = cols
else:
col = []

Jonathan Weth
committed
# Skip this period if there are holidays
if holiday:
continue

Jonathan Weth
committed
events_for_this_period = events_per_period.get(period, [])
events_for_replacement_for_this_period = events_for_replacement_per_period.get(
period, []
)

Jonathan Weth
committed
lesson_periods_for_this_period = lesson_periods_per_period.get(period, [])

Jonathan Weth
committed
# Add lesson periods
if lesson_periods_for_this_period:
if events_for_replacement_for_this_period:

Jonathan Weth
committed
# If there is a event in this period,
# we have to check whether the actual lesson is taking place.
lesson_periods_to_keep = []
for lesson_period in lesson_periods_for_this_period:
if not lesson_period.is_replaced_by_event(
events_for_replacement_for_this_period
):

Jonathan Weth
committed
lesson_periods_to_keep.append(lesson_period)
col += lesson_periods_to_keep
else:
col += lesson_periods_for_this_period
# Add events and extra lessons
col += extra_lessons_per_period.get(period, [])
col += events_for_this_period

Jonathan Weth
committed
row["col"] = col
rows.append(row)
return rows

Jonathan Weth
committed
def build_substitutions_list(wanted_day: date) -> tuple[list[dict], set[Person], set[Group]]:

Jonathan Weth
committed
rows = []
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,

Jonathan Weth
committed
)
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())

Jonathan Weth
committed

Jonathan Weth
committed
row = {
"type": "substitution",
"sort_a": lesson_event["REFERENCE_OBJECT"].group_names,
"sort_b": str(lesson_event["DTSTART"]),
"el": lesson_event,
rows.sort(key=lambda row: row["sort_a"] + row["sort_b"])
print(rows)
return rows, affected_teachers, affected_groups
def build_weekdays(
base: list[tuple[int, str]], wanted_week: CalendarWeek, with_holidays: bool = True
) -> list[dict]:
if with_holidays:
holidays_per_weekday = Holiday.in_week(wanted_week)
weekdays = []
for key, name in base[TimePeriod.weekday_min : TimePeriod.weekday_max + 1]:
weekday = {
"key": key,
"name": name,
"date": wanted_week[key],
}
if with_holidays:
weekday["holiday"] = holidays_per_weekday[key] if key in holidays_per_weekday else None