from datetime import date, datetime, timedelta from collections import OrderedDict from typing import Optional from django.contrib.auth.decorators import login_required from django.db.models import F, Max, Min, Q from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.views.decorators.cache import cache_page from django.urls import reverse from django.utils.translation import ugettext as _ from django_tables2 import RequestConfig from biscuit.core.decorators import admin_required from biscuit.core.util import messages from .forms import SelectForm, LessonSubstitutionForm from .models import LessonPeriod, TimePeriod, LessonSubstitution from .util import CalendarWeek, week_weekday_from_date from .tables import LessonsTable @login_required def timetable(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None) -> HttpResponse: context = {} if year and week: wanted_week = CalendarWeek(year=year, week=week) else: wanted_week = CalendarWeek() lesson_periods = LessonPeriod.objects.filter( lesson__date_start__lte=wanted_week[0] + timedelta(days=1) * F('lesson__weekday') - 1, lesson__date_end__gte=wanted_week[0] + timedelta(days=1) * F('lesson__weekday') - 1 ).select_related( 'lesson', 'lesson__subject', 'period', 'room' ).prefetch_related( 'lesson__groups', 'lesson__teachers', 'substitutions' ).extra( select={'_week': wanted_week.week} ) if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None): # Incrementally filter lesson periods by GET parameters if 'group' in request.GET and request.GET['group']: lesson_periods = lesson_periods.filter( Q(lesson__groups__pk=int(request.GET['group'])) | Q(lesson__groups__parent_groups__pk=int(request.GET['group']))) if 'teacher' in request.GET and request.GET['teacher']: lesson_periods = lesson_periods.filter( Q(substitutions__teachers__pk=int(request.GET['teacher']), substitutions__week=wanted_week.week) | Q(lesson__teachers__pk=int(request.GET['teacher']))) if 'room' in request.GET and request.GET['room']: lesson_periods = lesson_periods.filter( room__pk=int(request.GET['room'])) 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_day = {} for lesson_period in lesson_periods: per_day.setdefault(lesson_period.period.weekday, {})[lesson_period.period.period] = lesson_period # Determine overall first and last day and period min_max = TimePeriod.objects.aggregate( Min('period'), Max('period'), Min('weekday'), Max('weekday')) # Fill in empty lessons for weekday_num in range(min_max.get('weekday__min', 0), min_max.get('weekday__max', 6) + 1): # Fill in empty weekdays if weekday_num not in per_day.keys(): per_day[weekday_num] = {} # Fill in empty lessons on this workday for period_num in range(min_max.get('period__min', 1), min_max.get('period__max', 7) + 1): if period_num not in per_day[weekday_num].keys(): per_day[weekday_num][period_num] = None # Order this weekday by periods per_day[weekday_num] = OrderedDict( sorted(per_day[weekday_num].items())) # Add a form to filter the view select_form = SelectForm(request.GET or None) context['current_head'] = _('Timetable') context['lesson_periods'] = OrderedDict(sorted(per_day.items())) context['periods'] = TimePeriod.get_times_dict() context['weekdays'] = dict(TimePeriod.WEEKDAY_CHOICES) context['week'] = wanted_week context['select_form'] = select_form week_prev = wanted_week - 1 week_next = wanted_week + 1 context['url_prev'] = '%s?%s' % (reverse('timetable_by_week', args=[week_prev.year, week_prev.week]), request.GET.urlencode()) context['url_next'] = '%s?%s' % (reverse('timetable_by_week', args=[week_next.year, week_next.week]), request.GET.urlencode()) return render(request, 'chronos/tt_week.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() week, weekday = week_weekday_from_date(day) # Get lessons lesson_periods = LessonPeriod.objects.filter( lesson__date_start__lte=day, lesson__date_end__gte=day, period__weekday=weekday ) # Build table lessons_table = LessonsTable(lesson_periods.extra(select={'_week': week.week}).all()) RequestConfig(request).configure(lessons_table) context['current_head'] = _('Lessons') context['lessons_table'] = lessons_table context['day'] = day context['week'] = week context['lesson_periods'] = lesson_periods day_prev = day - timedalta(days=1) day_next = day + timedalta(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_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'))