from collections import OrderedDict from datetime import date, datetime, timedelta, time from typing import Optional, Union from django.contrib.auth.decorators import login_required from django.db.models import Count, Max, Min 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 ugettext as _ from django_tables2 import RequestConfig from aleksis.core.decorators import admin_required from aleksis.core.models import Person, Group from aleksis.core.util import messages from .forms import LessonSubstitutionForm from .models import LessonPeriod, LessonSubstitution, TimePeriod, Room from .tables import LessonsTable, SubstitutionsTable from .util import CalendarWeek, get_weeks_for_year # Determine overall first and last day and period min_max = TimePeriod.objects.aggregate( Min("period"), Max("period"), Min("weekday"), Max("weekday"), Min("time_start"), Max("time_end") ) period_min = min_max.get("period__min", 1) period_max = min_max.get("period__max", 7) time_min = min_max.get("time_start__min", None) time_max = min_max.get("time_end__max", None) weekday_min_ = min_max.get("weekday__min", 0) weekday_max = min_max.get("weekday__max", 6) def get_next_relevant_day(day: Union[date, None] = None, time: Union[time, None] = None): if day is None: day = timezone.now().date() if time is not None: if time > time_max: day += timedelta(days=1) cw = CalendarWeek.from_date(day) # Remap to Sunday first weekday = 0 if day.weekday() == 6 else day.weekday() + 1 if weekday > weekday_max or (weekday < weekday_min_): if weekday > weekday_max or weekday == 0: cw += 1 # Remap to Monday first weekday_min = weekday_min_ - 1 # TODO: Probably causes problems day = cw[weekday_min] return day @login_required def all(request: HttpRequest) -> HttpResponse: context = {} teachers = Person.objects.annotate(lessons_count=Count("lessons_as_teacher")).filter(lessons_count__gt=0) classes = Group.objects.annotate(lessons_count=Count("lessons")).filter(lessons_count__gt=0, parent_groups=None) rooms = Room.objects.annotate(lessons_count=Count("lesson_periods")).filter(lessons_count__gt=0) context['teachers'] = teachers context['classes'] = classes context['rooms'] = rooms return render(request, 'chronos/quicklaunch.html', context) @login_required def timetable( request: HttpRequest, _type: str, pk: int, year: Optional[int] = None, week: Optional[int] = None ) -> HttpResponse: context = {} if _type == "group": el = get_object_or_404(Group, pk=pk) elif _type == "teacher": el = get_object_or_404(Person, pk=pk) elif _type == "room": el = get_object_or_404(Room, pk=pk) else: return HttpResponseNotFound() if year and week: wanted_week = CalendarWeek(year=year, week=week) else: # TODO: On not used days show next week wanted_week = CalendarWeek() lesson_periods = LessonPeriod.objects.in_week(wanted_week) lesson_periods = lesson_periods.filter_from_type(_type, pk) # else: # # Redirect to a selected view if no filter provided # if request.user.person: # if request.user.person.primary_group: # return redirect( # reverse("timetable") + "?group=%d" % request.user.person.primary_group.pk # ) # elif lesson_periods.filter(lesson__teachers=request.user.person).exists(): # return redirect(reverse("timetable") + "?teacher=%d" % request.user.person.pk) # Regroup lesson periods per weekday per_period = {} for lesson_period in lesson_periods: print(lesson_period.period) added = False if lesson_period.period.period in per_period : if lesson_period.period.weekday in per_period[lesson_period.period.period]: print("HEY HEY") print(per_period[lesson_period.period.period][lesson_period.period.weekday]) per_period[lesson_period.period.period][lesson_period.period.weekday].append(lesson_period) added =True if not added: per_period.setdefault(lesson_period.period.period, {})[ lesson_period.period.weekday ] = [lesson_period] print(per_period) # Fill in empty lessons for period_num in range(period_min, period_max + 1): print(period_num) # Fill in empty weekdays if period_num not in per_period.keys(): per_period[period_num] = {} # Fill in empty lessons on this workday for weekday_num in range(weekday_min_, weekday_max + 1): if weekday_num not in per_period[period_num].keys(): per_period[period_num][weekday_num] = [] # Order this weekday by periods per_period[period_num] = OrderedDict(sorted(per_period[period_num].items())) print(lesson_periods) context["lesson_periods"] = OrderedDict(sorted(per_period.items())) context["periods"] = TimePeriod.get_times_dict() context["weekdays"] = dict(TimePeriod.WEEKDAY_CHOICES[weekday_min_:weekday_max + 1]) context["weekdays_short"] = dict(TimePeriod.WEEKDAY_CHOICES_SHORT[weekday_min_:weekday_max + 1]) 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"] = True week_prev = wanted_week - 1 week_next = wanted_week + 1 context["url_prev"] = reverse("timetable_by_week", args=[_type, pk, week_prev.year, week_prev.week]) context["url_next"] = reverse("timetable_by_week", args=[_type, pk, week_next.year, week_next.week]) return render(request, "chronos/plan.html", context) @login_required def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpResponse: context = {} if when: day = datetime.strptime(when, "%Y-%m-%d").date() else: day = date.today() # Get lessons lesson_periods = LessonPeriod.objects.on_day(day) # Build table lessons_table = LessonsTable(lesson_periods.all()) RequestConfig(request).configure(lessons_table) context["lessons_table"] = lessons_table context["day"] = day context["lesson_periods"] = lesson_periods day_prev = day - timedelta(days=1) day_next = day + timedelta(days=1) context["url_prev"] = reverse("lessons_day_by_date", args=[day_prev.strftime("%Y-%m-%d")]) context["url_next"] = reverse("lessons_day_by_date", args=[day_next.strftime("%Y-%m-%d")]) return render(request, "chronos/lessons_day.html", context) @admin_required def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse: context = {} lesson_period = get_object_or_404(LessonPeriod, pk=id_) wanted_week = lesson_period.lesson.get_calendar_week(week) lesson_substitution = LessonSubstitution.objects.filter( week=wanted_week.week, lesson_period=lesson_period ).first() if lesson_substitution: edit_substitution_form = LessonSubstitutionForm( request.POST or None, instance=lesson_substitution ) else: edit_substitution_form = LessonSubstitutionForm( request.POST or None, initial={"week": wanted_week.week, "lesson_period": lesson_period}, ) context["substitution"] = lesson_substitution if request.method == "POST": if edit_substitution_form.is_valid(): edit_substitution_form.save(commit=True) messages.success(request, _("The substitution has been saved.")) return redirect( "lessons_day_by_date", when=wanted_week[lesson_period.period.weekday - 1].strftime("%Y-%m-%d"), ) context["edit_substitution_form"] = edit_substitution_form return render(request, "chronos/edit_substitution.html", context) @admin_required def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse: context = {} lesson_period = get_object_or_404(LessonPeriod, pk=id_) wanted_week = lesson_period.lesson.get_calendar_week(week) LessonSubstitution.objects.filter(week=wanted_week.week, lesson_period=lesson_period).delete() messages.success(request, _("The substitution has been deleted.")) return redirect( "lessons_day_by_date", when=wanted_week[lesson_period.period.weekday - 1].strftime("%Y-%m-%d"), ) def substitutions( request: HttpRequest, year: Optional[int] = None, month: Optional[int] = None, day: Optional[int] = None ) -> HttpResponse: context = {} if day: wanted_day = timezone.datetime(year=year, month=month, day=day).date() wanted_day = get_next_relevant_day(wanted_day) else: wanted_day = get_next_relevant_day(timezone.now().date(), datetime.now().time()) substitutions = LessonSubstitution.objects.on_day(wanted_day) # Prepare table substitutions_table = SubstitutionsTable(substitutions) RequestConfig(request).configure(substitutions_table) context["current_head"] = str(wanted_day) context["substitutions_table"] = substitutions_table context["substitutions"] = substitutions context["day"] = wanted_day day_prev = wanted_day - timedelta(days=1) day_next = wanted_day + timedelta(days=1) context["url_prev"] = "%s" % ( reverse("substitutions_by_day", args=[day_prev.year, day_prev.month, day_prev.day]) ) context["url_next"] = "%s" % ( reverse("substitutions_by_day", args=[day_next.year, day_next.month, day_next.day]) ) return render(request, "chronos/substitution.html", context)