aboutsummaryrefslogtreecommitdiff
path: root/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'views.py')
-rw-r--r--views.py244
1 files changed, 217 insertions, 27 deletions
diff --git a/views.py b/views.py
index c647fac..e45ece3 100644
--- a/views.py
+++ b/views.py
@@ -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}