Newer
Older
# celcatsanitizer is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# celcatsanitizer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with celcatsanitizer. If not, see <http://www.gnu.org/licenses/>.
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.views.decorators.csrf import csrf_exempt
from .models import Course, Group, Room, Timetable, Year
from .utils import get_current_week, get_current_or_next_week, get_week, \
group_courses
if connection.vendor == "postgresql":
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models.expressions import RawSQL
years = Year.objects.order_by("name")
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)
return render(request, "mention_list.html",
def group_list(request, year_slug, timetable_slug):
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 = 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)
Alban Gruin
committed
Alban Gruin
committed
for group_week in groups_weeks:
if group.corresponds_to(group_week["groups__mention"],
group_week["groups__semester"],
group_week["groups__subgroup"]):
Alban Gruin
committed
if not hasattr(group, "weeks"):
group.weeks = []
date, _ = get_week(group_week["year"], group_week["week"])
if date not in group.weeks:
group.weeks.append(date)
if hasattr(group, "weeks"):
group.weeks.sort()
return render(request, "group_list.html",
{"timetable": timetable, "groups": groups})
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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]
# 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
if year is None or week is None:
year, week = current_year, current_week
provided_week = False
elif (int(year), int(week)) < (current_year, current_week):
is_old_timetable = True
try:
start, end = get_week(year, week)
except ValueError:
raise Http404
courses = Course.objects.get_courses(obj, begin__gte=start, begin__lt=end)
if not courses.exists() and provided_week:
raise Http404
Alban Gruin
committed
# 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)
Alban Gruin
committed
last_update = courses.aggregate(Max("last_update"))["last_update__max"]
Alban Gruin
committed
grouped_courses = group_courses(courses)
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):
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})
# 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, "group_list.html", {"groups": 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, "group_list.html", {"groups": 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)
{"elements": rooms, "form": form})
# Si le formulaire est invalide, on ré-affiche le formulaire
# avec les erreurs
# Si le formulaire n’a pas été soumis, on en instancie un
# nouveau
return render(request, "qsjps_form.html", {"form": form})
def ctx_processor(request):
return {"celcatsanitizer_version": edt.VERSION}