diff options
Diffstat (limited to 'views.py')
-rw-r--r-- | views.py | 244 |
1 files changed, 217 insertions, 27 deletions
@@ -1,4 +1,4 @@ -# Copyright (C) 2017 Alban Gruin +# Copyright (C) 2017-2018 Alban Gruin # # celcatsanitizer is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published @@ -15,34 +15,50 @@ import datetime -from django.db.models import Max -from django.db.models.functions import Length +from django.db import connection +from django.db.models import Count, Max +from django.db.models.functions import ExtractWeek, ExtractYear, Length from django.http import Http404 from django.shortcuts import get_object_or_404, render +from django.utils import timezone +from django.views.decorators.csrf import csrf_exempt -from .models import Timetable, Group, Course, Year -from .utils import get_current_week, get_current_or_next_week, get_week, group_courses +from .forms import QSJPSForm +from .models import Course, Group, Room, Timetable, Year +from .utils import get_current_week, get_current_or_next_week, get_week, \ + group_courses import edt +if connection.vendor == "postgresql": + from django.contrib.postgres.aggregates import ArrayAgg + from django.db.models.expressions import RawSQL + + def index(request): years = Year.objects.order_by("name") - return render(request, "index.html", {"elements": years}) + return render(request, "year_list.html", {"elements": years}) + def mention_list(request, year_slug): year = get_object_or_404(Year, slug=year_slug) - timetables = Timetable.objects.order_by("name").filter(year=year).select_related("year") + timetables = Timetable.objects.order_by("name").filter(year=year) + + return render(request, "mention_list.html", + {"year": year, "elements": timetables}) - return render(request, "index.html", {"year": year, "elements": timetables}) def group_list(request, year_slug, timetable_slug): - timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) - groups = Group.objects.filter(timetable=timetable, hidden=False).order_by("name") + timetable = get_object_or_404(Timetable, year__slug=year_slug, + slug=timetable_slug) start, _ = get_week(*get_current_week()) end = start + datetime.timedelta(weeks=4) - groups_weeks = Course.objects.get_weeks(begin__gte=start, begin__lt=end, groups__in=groups) + groups = Group.objects.get_relevant_groups(start, source=timetable.source, + hidden=False) + groups_weeks = Course.objects.get_weeks(begin__gte=start, begin__lt=end, + groups__in=groups) for group in groups: for group_week in groups_weeks: @@ -59,9 +75,48 @@ def group_list(request, year_slug, timetable_slug): if hasattr(group, "weeks"): group.weeks.sort() - return render(request, "group_list.html", {"timetable": timetable, "groups": groups}) + return render(request, "group_list.html", + {"timetable": timetable, "elements": groups}) + + +def groups_all(request, year_slug, timetable_slug): + # Récupération de l’emploi du temps et du groupe + timetable = get_object_or_404(Timetable, year__slug=year_slug, + slug=timetable_slug) + groups = Group.objects.filter(source=timetable.source).order_by("name") + + # Rendu de la page + return render(request, "groups_all_list.html", + {"timetable": timetable, "elements": groups}) + + +def group_weeks(request, year_slug, timetable_slug, group_slug): + # Récupération de l’emploi du temps et des groupes + timetable = get_object_or_404(Timetable, year__slug=year_slug, + slug=timetable_slug) + group = get_object_or_404(Group, slug=group_slug, source=timetable.source) + + # Groupes parents + groups = Group.objects.get_parents(group) + + # Récupération de toutes les semaines avec des cours, sans doublons + courses = Course.objects.filter(groups__in=groups) \ + .order_by("year", "week") \ + .annotate(year=ExtractYear("begin"), + week=ExtractWeek("begin")) \ + .values("year", "week") \ + .annotate(c=Count("*")) + + # Conversion des semaines de cours en dates + weeks = [get_week(course["year"], course["week"])[0] for course in courses] -def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): + # Rendu + return render(request, "group_weeks_list.html", + {"timetable": timetable, "group": group, + "elements": weeks}) + + +def timetable_common(request, obj, year=None, week=None, timetable=None): current_year, current_week = get_current_or_next_week() is_old_timetable, provided_week = False, True @@ -71,30 +126,165 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No elif (int(year), int(week)) < (current_year, current_week): is_old_timetable = True - start, end = get_week(year, week) - - timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) - group = get_object_or_404(Group, slug=group_slug, timetable=timetable) + try: + start, end = get_week(year, week) + except ValueError: + raise Http404 - courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) + courses = Course.objects.get_courses(obj, begin__gte=start, begin__lt=end) if not courses.exists() and provided_week: raise Http404 + # Récupération des semaines suivantes et précédentes pour les + # afficher proprement dans l’emploi du temps + last_course = Course.objects.get_courses(obj, begin__lt=start).last() + last_week = getattr(last_course, "begin", None) + + next_course = Course.objects.get_courses(obj, begin__gte=end).first() + next_week = getattr(next_course, "begin", None) + last_update = courses.aggregate(Max("last_update"))["last_update__max"] grouped_courses = group_courses(courses) - return render(request, "timetable.html", {"group": group, "courses": grouped_courses, - "last_update": last_update, - "year": year, "week": int(week), - "is_old_timetable": is_old_timetable}) + return render(request, "timetable.html", + {"group": obj, "courses": grouped_courses, + "last_update": last_update, + "year": year, "week": int(week), + "last_week": last_week, + "next_week": next_week, + "is_old_timetable": is_old_timetable, + "group_mode": isinstance(obj, Group), + "timetable": timetable}) + + +def timetable(request, year_slug, timetable_slug, group_slug, + year=None, week=None): + timetable = get_object_or_404(Timetable, year__slug=year_slug, + slug=timetable_slug) + group = get_object_or_404(Group, slug=group_slug, source=timetable.source) + + return timetable_common(request, group, year, week, timetable) + def calendars(request, year_slug, timetable_slug, group_slug): - group = get_object_or_404(Group, timetable__year__slug=year_slug, - timetable__slug=timetable_slug, slug=group_slug) - groups = Group.objects.get_parents(group).annotate(length=Length("subgroup")) \ - .order_by("length") + timetable = get_object_or_404(Timetable, year__slug=year_slug, + slug=timetable_slug) + group = get_object_or_404(Group, source=timetable.source, slug=group_slug) + groups = Group.objects.get_parents(group) \ + .annotate(length=Length("subgroup")) \ + .order_by("length") + + return render(request, "calendars.html", + {"timetable": timetable, "group": group, "groups": groups}) + + +def rooms(request): + # On récupère les dates allant de cette semaine à dans un mois + start, _ = get_week(*get_current_week()) + end = start + datetime.timedelta(weeks=4) + + if connection.vendor == "postgresql": + # Si le SGBD est PostgreSQL, on utilise une requête à base de + # ArrayAgg. Elle présente l’avantage d’être plus rapide que la + # requête « généraliste » et de ne pas nécessiter de + # traitement après. On récupère chaque salle ayant un cours + # dans le mois à venir. Pour chacun de ses cours, on ne + # récupère que le premier jour de la semaine, et si jamais ce + # jour n’est pas déjà dans la liste des semaines de cours + # (« weeks »), on l’y rajoute. + rooms = Room.objects.filter(course__begin__gte=start, + course__begin__lt=end) \ + .order_by("name") \ + .annotate(weeks=ArrayAgg( + RawSQL("date_trunc('week', edt_course.begin)", + []), distinct=True)) + + return render(request, "room_list.html", {"elements": rooms}) + + # Récupération des salles et de toutes les semaines où elles sont + # concernées. + # Cette requête associe chaque salle à toutes les semaines où un + # cours s’y déroule. Le résultat est trié par le nom de la salle + # et par semaine. + # TODO optimiser cette requête, elle me semble un peu lente + rooms = Room.objects.filter(course__begin__gte=start, + course__begin__lt=end) \ + .order_by("name") \ + .annotate(year=ExtractYear("course__begin"), + week=ExtractWeek("course__begin"), + c=Count("*")) + + # Regroupement des semaines dans une liste de chaque objet salle + rooms_weeks = [] + for room in rooms: + # Si on a pas traité de salle ou que la salle courante + # dans le résultat de la requête est différente de la dernière + # dans la liste des salles traitées + if len(rooms_weeks) == 0 or rooms_weeks[-1].id != room.id: + # On lui affecte un tableau et on l’ajoute dans + # la liste des salles à traiter + room.weeks = [] + rooms_weeks.append(room) + + # On récupère le premier jour de la semaine + date, _ = get_week(room.year, room.week) + # Et on le rajoute dans la liste des semaines de la salle. + rooms_weeks[-1].weeks.append(date) + + # Rendu de la page. + return render(request, "room_list.html", {"elements": rooms_weeks}) + + +def room_weeks(request, room_slug): + room = get_object_or_404(Room, slug=room_slug) + + # Récupération des semaines de cours + courses = Course.objects.filter(rooms=room) \ + .order_by("year", "week") \ + .annotate(year=ExtractYear("begin"), + week=ExtractWeek("begin")) \ + .values("year", "week") \ + .annotate(c=Count("*")) + + weeks = [get_week(course["year"], course["week"])[0] for course in courses] + + return render(request, "room_weeks_list.html", + {"room": room, "elements": weeks}) + + +def room_timetable(request, room_slug, year=None, week=None): + room = get_object_or_404(Room, slug=room_slug) + return timetable_common(request, room, year, week) + + +@csrf_exempt +def qsjps(request): + if request.method == "POST": + # Si on traite un formulaire, on le valide + form = QSJPSForm(request.POST) + if form.is_valid(): + # Formulaire validé + day = form.cleaned_data["day"] + begin_hour = form.cleaned_data["begin"] + end_hour = form.cleaned_data["end"] + + begin = timezone.make_aware(datetime.datetime.combine(day, + begin_hour)) + end = timezone.make_aware(datetime.datetime.combine(day, end_hour)) + + rooms = Room.objects.qsjps(begin, end) + return render(request, "qsjps.html", + {"elements": rooms, "form": form}) + + # Si le formulaire est invalide, on ré-affiche le formulaire + # avec les erreurs + else: + # Si le formulaire n’a pas été soumis, on en instancie un + # nouveau + form = QSJPSForm() + + return render(request, "qsjps_form.html", {"form": form}) - return render(request, "calendars.html", {"group": group, "groups": groups}) def ctx_processor(request): return {"celcatsanitizer_version": edt.VERSION} |