From 47cbec5a56323e8f2decf1ddbab477d95691c069 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Thu, 25 Mar 2021 20:59:22 +0100 Subject: [PATCH] Make next_lesson work with multiple validity ranges and year changes --- aleksis/apps/chronos/managers.py | 90 ++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/aleksis/apps/chronos/managers.py b/aleksis/apps/chronos/managers.py index 5c104ba5..ca92c3eb 100644 --- a/aleksis/apps/chronos/managers.py +++ b/aleksis/apps/chronos/managers.py @@ -1,6 +1,6 @@ from datetime import date, datetime, timedelta from enum import Enum -from typing import Iterable, List, Optional, Union +from typing import Dict, Iterable, List, Optional, Union from django.contrib.sites.managers import CurrentSiteManager as _CurrentSiteManager from django.db import models @@ -10,7 +10,7 @@ from django.db.models.functions import Concat from calendarweek import CalendarWeek -from aleksis.apps.chronos.util.date import week_weekday_from_date +from aleksis.apps.chronos.util.date import week_weekday_from_date, week_weekday_to_date from aleksis.core.managers import DateRangeQuerySetMixin, SchoolTermRelatedQuerySet from aleksis.core.models import Group, Person from aleksis.core.util.core_helpers import get_site_preferences @@ -375,28 +375,100 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin): return lesson_periods - def next_lesson(self, reference: "LessonPeriod", offset: Optional[int] = 1) -> "LessonPeriod": + def group_by_validity(self) -> Dict["ValidityRange", List["LessonPeriod"]]: + """Group lesson periods by validity range as dictionary.""" + lesson_periods_by_validity = {} + for lesson_period in self: + lesson_periods_by_validity.setdefault(lesson_period.lesson.validity, []) + lesson_periods_by_validity[lesson_period.lesson.validity].append(lesson_period) + return lesson_periods_by_validity + + def next_lesson( + self, reference: "LessonPeriod", offset: Optional[int] = 1 + ) -> Optional["LessonPeriod"]: """Get another lesson in an ordered set of lessons. By default, it returns the next lesson in the set. By passing the offset argument, the n-th next lesson can be selected. By passing a negative number, the n-th previous lesson can be selected. + + This function will handle week, year and validity range changes automatically + if the queryset contains enough lesson data. """ - index = list(self.values_list("id", flat=True)).index(reference.id) + # Group lesson periods by validity to handle validity range changes correctly + lesson_periods_by_validity = self.group_by_validity() + validity_ranges = list(lesson_periods_by_validity.keys()) + + # List with lesson periods in the validity range of the reference lesson period + current_lesson_periods = lesson_periods_by_validity[reference.lesson.validity] + pks = [lesson_period.pk for lesson_period in current_lesson_periods] + + # Position of the reference lesson period + index = pks.index(reference.id) next_index = index + offset - if next_index > self.count() - 1: - next_index %= self.count() + if next_index > len(pks) - 1: + next_index %= len(pks) week = reference._week + 1 elif next_index < 0: - next_index = self.count() + next_index + next_index = len(pks) + next_index week = reference._week - 1 else: week = reference._week - week = CalendarWeek(week=week, year=reference.lesson.get_year(week)) + # Check if selected week makes a year change necessary + year = reference._year + if week < 1: + year -= 1 + week = CalendarWeek.get_last_week_of_year(year) + elif week > CalendarWeek.get_last_week_of_year(year): + year += 1 + week = 1 + + # Get the next lesson period in this validity range and it's date + # to check whether the validity range has to be changed + week = CalendarWeek(week=week, year=year) + next_lesson_period = current_lesson_periods[next_index] + next_lesson_period_date = week_weekday_to_date(week, next_lesson_period.period.period) + + validity_index = validity_ranges.index(next_lesson_period.lesson.validity) + + # If date of next lesson period is out of validity range (smaller) ... + if next_lesson_period_date < next_lesson_period.lesson.validity.date_start: + # ... we have to get the lesson period from the previous validity range + if validity_index == 0: + # There are no validity ranges (and thus no lessons) + # in the school term before this lesson period + return None + + # Get new validity range and last lesson period of this validity range + new_validity = validity_ranges[validity_index - 1] + next_lesson_period = lesson_periods_by_validity[new_validity][-1] + + # Build new week with the date from the new validity range/lesson period + week = CalendarWeek( + week=new_validity.date_end.isocalendar()[1], year=new_validity.date_end.year + ) + + # If date of next lesson period is out of validity range (larger) ... + elif next_lesson_period_date > next_lesson_period.lesson.validity.date_end: + # ... we have to get the lesson period from the next validity range + if validity_index >= len(validity_ranges): + # There are no validity ranges (and thus no lessons) + # in the school term after this lesson period + return None + + # Get new validity range and first lesson period of this validity range + new_validity = validity_ranges[validity_index + 1] + next_lesson_period = lesson_periods_by_validity[new_validity][0] + + # Build new week with the date from the new validity range/lesson period + week = CalendarWeek( + week=new_validity.date_start.isocalendar()[1], year=new_validity.date_start.year + ) - return self.annotate_week(week).all()[next_index] + # Do a new query here to be able to annotate the new week + return self.annotate_week(week).get(pk=next_lesson_period.pk) class LessonPeriodQuerySet(LessonDataQuerySet, GroupByPeriodsMixin): -- GitLab