Newer
Older

Jonathan Weth
committed
from datetime import date
from operator import itemgetter

Jonathan Weth
committed
from typing import Any, Dict, Iterable, List, Optional, Sequence, Union
from django.db.models.expressions import Exists, OuterRef
from django.db.models.query import Prefetch, QuerySet
from django.db.models.query_utils import Q
from django.utils.formats import date_format
from django.utils.translation import gettext as _
from aleksis.apps.alsijil.forms import FilterRegisterObjectForm
from aleksis.apps.alsijil.models import LessonDocumentation

Jonathan Weth
committed
from aleksis.apps.chronos.models import Event, ExtraLesson, Holiday, LessonPeriod
from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
def get_register_object_by_pk(
model: Optional[str] = None,
year: Optional[int] = None,
week: Optional[int] = None,
id_: Optional[int] = None,
) -> Optional[Union[LessonPeriod, Event, ExtraLesson]]:
"""Get register object either by given object_id or by time and current person."""
wanted_week = CalendarWeek(year=year, week=week)
if id_ and model == "lesson":
register_object = LessonPeriod.objects.annotate_week(wanted_week).get(pk=id_)
elif id_ and model == "event":
register_object = Event.objects.get(pk=id_)
elif id_ and model == "extra_lesson":
register_object = ExtraLesson.objects.get(pk=id_)
elif hasattr(request, "user") and hasattr(request.user, "person"):
if request.user.person.lessons_as_teacher.exists():
LessonPeriod.objects.at_time().filter_teacher(request.user.person).first()
LessonPeriod.objects.at_time().filter_participant(request.user.person).first()
register_object = None
return register_object
def get_timetable_instance_by_pk(
request: HttpRequest,
year: Optional[int] = None,
week: Optional[int] = None,
type_: Optional[str] = None,
id_: Optional[int] = None,
):
"""Get timetable object (teacher, room or group) by given type and id or the current person."""
elif hasattr(request, "user") and hasattr(request.user, "person"):
def annotate_documentations(
klass: Union[Event, LessonPeriod, ExtraLesson], wanted_week: CalendarWeek, pks: List[int]
) -> QuerySet:
"""Return an annotated queryset of all provided register objects."""
if isinstance(klass, LessonPeriod):
prefetch = Prefetch(
"documentations",
queryset=LessonDocumentation.objects.filter(
week=wanted_week.week, year=wanted_week.year
),
)
else:
prefetch = Prefetch("documentations")
instances = klass.objects.prefetch_related(prefetch).filter(pk__in=pks)
if klass == LessonPeriod:
instances = instances.annotate_week(wanted_week)
elif klass in (LessonPeriod, ExtraLesson):
instances = instances.order_by("period__weekday", "period__period")
else:
instances = instances.order_by("period_from__weekday", "period_from__period")
instances = instances.annotate(
has_documentation=Exists(
LessonDocumentation.objects.filter(
~Q(topic__exact=""), week=wanted_week.week, year=wanted_week.year,
).filter(**{klass.label_: OuterRef("pk")})
)
)
return instances
def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLesson]) -> int:
"""Sort key for sorted/sort for sorting a list of class register objects.
This will sort the objects by the start period.
"""
if hasattr(register_object, "period"):
return register_object.period.period
elif isinstance(register_object, Event):
return register_object.period_from_on_day
else:
return 0

Jonathan Weth
committed
def _filter_register_objects_by_dict(
filter_dict: Dict[str, Any],
register_objects: QuerySet[Union[LessonPeriod, Event, ExtraLesson]],
label_: str,
) -> QuerySet[Union[LessonPeriod, Event, ExtraLesson]]:
"""Filter register objects by a dictionary generated through ``FilterRegisterObjectForm``."""

Jonathan Weth
committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
if label_ == LessonPeriod.label_:
register_objects = register_objects.filter(
lesson__validity__school_term=filter_dict.get("school_term")
)
else:
register_objects = register_objects.filter(school_term=filter_dict.get("school_term"))
register_objects = register_objects.distinct()
if (
filter_dict.get("date_start")
and filter_dict.get("date_end")
and label_ != LessonPeriod.label_
):
register_objects = register_objects.within_dates(
filter_dict.get("date_start"), filter_dict.get("date_end")
)
if filter_dict.get("person"):
if label_ == LessonPeriod.label_:
register_objects = register_objects.filter(
Q(lesson__teachers=filter_dict.get("person"))
| Q(substitutions__teachers=filter_dict.get("person"))
)
else:
register_objects = register_objects.filter_teacher(filter_dict.get("person"))
if filter_dict.get("group"):
register_objects = register_objects.filter_group(filter_dict.get("group"))
if filter_dict.get("groups"):
register_objects = register_objects.filter_groups(filter_dict.get("groups"))
if filter_dict.get("subject"):
if label_ == LessonPeriod.label_:
register_objects = register_objects.filter(
Q(lesson__subject=filter_dict.get("subject"))
| Q(substitutions__subject=filter_dict.get("subject"))
)
elif label_ == Event.label_:
# As events have no subject, we exclude them at all
register_objects = register_objects.none()
else:
register_objects = register_objects.filter(subject=filter_dict.get("subject"))
return register_objects
def _generate_dicts_for_lesson_periods(
filter_dict: Dict[str, Any],
lesson_periods: QuerySet[LessonPeriod],
documentations: Optional[Iterable[LessonDocumentation]] = None,
holiday_days: Optional[Sequence[date]] = None,
) -> List[Dict[str, Any]]:
"""Generate a list of dicts for use with ``RegisterObjectTable``."""
if not holiday_days:
holiday_days = []
lesson_periods = list(lesson_periods)
date_start = lesson_periods[0].lesson.validity.date_start
date_end = lesson_periods[-1].lesson.validity.date_end

Jonathan Weth
committed
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
if (
filter_dict["filter_date"]
and filter_dict.get("date_start") > date_start
and filter_dict.get("date_start") < date_end
):
date_start = filter_dict.get("date_start")
if (
filter_dict["filter_date"]
and filter_dict.get("date_end") < date_end
and filter_dict.get("date_end") > date_start
):
date_end = filter_dict.get("date_end")
weeks = CalendarWeek.weeks_within(date_start, date_end)
register_objects = []
for lesson_period in lesson_periods:
for week in weeks:
day = week[lesson_period.period.weekday]
# Skip all lesson periods in holidays
if day in holiday_days:
continue
# Ensure that the lesson period is in filter range and validity range
if (
lesson_period.lesson.validity.date_start
<= day
<= lesson_period.lesson.validity.date_end
) and (
not filter_dict.get("filter_date")
or (filter_dict.get("date_start") <= day <= filter_dict.get("date_end"))
):
sub = lesson_period.get_substitution()
# Skip lesson period if the person isn't a teacher
# or substitution teacher of this lesson period
if filter_dict.get("person") and (
filter_dict.get("person") not in lesson_period.lesson.teachers.all() and not sub
):
continue
teachers = lesson_period.teacher_names

Jonathan Weth
committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
if (
filter_dict.get("subject")
and filter_dict.get("subject") != lesson_period.get_subject()
):
continue
# Filter matching documentations and annotate if they exist
filtered_documentations = list(
filter(
lambda d: d.week == week.week
and d.year == week.year
and d.lesson_period_id == lesson_period.pk,
documentations
if documentations is not None
else lesson_period.documentations.all(),
)
)
has_documentation = bool(filtered_documentations)
if filter_dict.get(
"has_documentation"
) is not None and has_documentation != filter_dict.get("has_documentation"):
continue
# Build table entry
entry = {
"pk": f"lesson_period_{lesson_period.pk}_{week.year}_{week.week}",
"week": week,
"has_documentation": has_documentation,
"substitution": sub,
"register_object": lesson_period,
"date": date_format(day),
"date_sort": day,
"period": f"{lesson_period.period.period}.",
"period_sort": lesson_period.period.period,
"groups": lesson_period.lesson.group_names,
"teachers": teachers,
"subject": lesson_period.get_subject().name,
}
if has_documentation:
doc = filtered_documentations[0]
entry["topic"] = doc.topic
entry["homework"] = doc.homework
entry["group_note"] = doc.group_note
register_objects.append(entry)
return register_objects
def _generate_dicts_for_events_and_extra_lessons(
filter_dict: Dict[str, Any],
register_objects_start: Sequence[Union[Event, ExtraLesson]],
documentations: Optional[Iterable[LessonDocumentation]] = None,
) -> List[Dict[str, Any]]:
"""Generate a list of dicts for use with ``RegisterObjectTable``."""
register_objects = []
for register_object in register_objects_start:
filtered_documentations = list(
filter(
lambda d: getattr(d, f"{register_object.label_}_id") == register_object.pk,
documentations
if documentations is not None
else register_object.documentations.all(),
)
)
has_documentation = bool(filtered_documentations)
if filter_dict.get(
"has_documentation"
) is not None and has_documentation != filter_dict.get("has_documentation"):
continue
if isinstance(register_object, ExtraLesson):
day = date_format(register_object.day)
day_sort = register_object.day
period = f"{register_object.period.period}."
period_sort = register_object.period.period
else:
register_object.annotate_day(register_object.date_end)

Jonathan Weth
committed
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
day = (
f"{date_format(register_object.date_start)}"
f"–{date_format(register_object.date_end)}"
)
day_sort = register_object.date_start
period = f"{register_object.period_from.period}.–{register_object.period_to.period}."
period_sort = register_object.period_from.period
# Build table entry
entry = {
"pk": f"{register_object.label_}_{register_object.pk}",
"has_documentation": has_documentation,
"register_object": register_object,
"date": day,
"date_sort": day_sort,
"period": period,
"period_sort": period_sort,
"groups": register_object.group_names,
"teachers": register_object.teacher_names,
"subject": register_object.subject.name
if isinstance(register_object, ExtraLesson)
else _("Event"),
}
if has_documentation:
doc = filtered_documentations[0]
entry["topic"] = doc.topic
entry["homework"] = doc.homework
entry["group_note"] = doc.group_note
register_objects.append(entry)
return register_objects
def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate a list of all register objects.
This list can be filtered using ``filter_dict``. The following keys are supported:
- ``school_term`` (defaults to the current school term)
- ``date_start`` and ``date_end`` (defaults to the last thirty days)
- ``groups`` and/or ``groups``
- ``person``
- ``subject``
"""
# Always force a value for school term, start and end date so that queries won't get too big
initial_filter_data = FilterRegisterObjectForm.get_initial()

Jonathan Weth
committed
filter_dict["school_term"] = filter_dict.get("school_term", initial_filter_data["school_term"])

Jonathan Weth
committed
# If there is not school year at all, there are definitely no data.
if not filter_dict["school_term"]:
return []

Jonathan Weth
committed
filter_dict["date_start"] = filter_dict.get("date_start", initial_filter_data["date_start"])
filter_dict["date_end"] = filter_dict.get("date_end", initial_filter_data["date_end"])
filter_dict["filter_date"] = bool(filter_dict.get("date_start")) and bool(
filter_dict.get("date_end")
)
# Get all holidays in the selected school term to sort all data in holidays out
holidays = Holiday.objects.within_dates(

Jonathan Weth
committed
filter_dict["school_term"].date_start, filter_dict["school_term"].date_end

Jonathan Weth
committed
holiday_days = holidays.get_all_days()
lesson_periods = _filter_register_objects_by_dict(
filter_dict,
LessonPeriod.objects.order_by("lesson__validity__date_start"),
LessonPeriod.label_,

Jonathan Weth
committed
events = _filter_register_objects_by_dict(
filter_dict, Event.objects.exclude_holidays(holidays), Event.label_
)
extra_lessons = _filter_register_objects_by_dict(
filter_dict, ExtraLesson.objects.exclude_holidays(holidays), ExtraLesson.label_
)
# Prefetch documentations for all register objects and substitutions for all lesson periods
# in order to prevent extra queries
documentations = LessonDocumentation.objects.not_empty().filter(
Q(event__in=events)
| Q(extra_lesson__in=extra_lessons)
| Q(lesson_period__in=lesson_periods)
)
if lesson_periods:

Jonathan Weth
committed
register_objects = _generate_dicts_for_lesson_periods(
filter_dict, lesson_periods, documentations, holiday_days
)
register_objects += _generate_dicts_for_events_and_extra_lessons(
filter_dict, list(events) + list(extra_lessons), documentations
)
# Sort table entries by date and period and configure table
register_objects = sorted(register_objects, key=itemgetter("date_sort", "period_sort"))
return register_objects
return []