From 0b5d2ede0a7027c26f117bf85636fd0c002f772c Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 26 Nov 2017 23:20:07 +0100 Subject: Suppression des imports inutiles --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models.py b/models.py index 25ddd1b..035bd6d 100644 --- a/models.py +++ b/models.py @@ -16,7 +16,7 @@ from functools import reduce from django.db import models -from django.db.models import Count, Manager, Q +from django.db.models import Manager, Q from django.db.models.functions import ExtractWeek, ExtractYear from django.utils import timezone from django.utils.text import slugify -- cgit v1.2.1 From 3991efeb9af75ad55ec373422da3c11ea2d36e6e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 12:08:08 +0100 Subject: Les champs semestre et sous-groupe ne sont plus modifiables dans l’interface d’administration --- admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin.py b/admin.py index 0235fb9..7739a95 100644 --- a/admin.py +++ b/admin.py @@ -49,7 +49,7 @@ class GroupAdmin(admin.ModelAdmin): list_editable = ("hidden",) list_filter = ("timetable",) ordering = ("timetable",) - readonly_fields = ("celcat_name", "mention",) + readonly_fields = ("celcat_name", "mention", "semester", "subgroup",) actions = (make_hidden, make_visible,) -- cgit v1.2.1 From b219f1248fa84eb2b015a5a04f80905dad8904ae Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 17 Nov 2017 16:11:55 +0100 Subject: Page principale de l’emploi du temps des salles --- urls.py | 1 + views.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/urls.py b/urls.py index 487994e..4de8103 100644 --- a/urls.py +++ b/urls.py @@ -19,6 +19,7 @@ from . import feeds, views urlpatterns = [ url(r"^$", views.index, name="index"), url(r"^pages/", include("django.contrib.flatpages.urls")), + url(r"^salles/", views.rooms, name="rooms"), url(r"^(?P[-\w]+)/$", views.mention_list, name="mentions"), url(r"^(?P[-\w]+)/(?P[-\w]+)/$", views.group_list, name="groups"), url(r"^(?P[-\w]+)/(?P[-\w]+)/(?P[-\w]+)/$", views.timetable, name="timetable"), diff --git a/views.py b/views.py index c647fac..c008052 100644 --- a/views.py +++ b/views.py @@ -96,5 +96,8 @@ def calendars(request, year_slug, timetable_slug, group_slug): return render(request, "calendars.html", {"group": group, "groups": groups}) +def rooms(request): + return render(request, "index.html") + def ctx_processor(request): return {"celcatsanitizer_version": edt.VERSION} -- cgit v1.2.1 From 1b0fbf29a484b16de31ac5df1b3fded39be95e97 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 17 Nov 2017 21:20:37 +0100 Subject: Ajout d’un champ slug au modèle des salles --- admin.py | 2 +- models.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/admin.py b/admin.py index 7739a95..011ad73 100644 --- a/admin.py +++ b/admin.py @@ -55,7 +55,7 @@ class GroupAdmin(admin.ModelAdmin): @admin.register(Room) class RoomAdmin(admin.ModelAdmin): - pass + prepopulated_fields = {"slug": ("name",)} @admin.register(Course) diff --git a/models.py b/models.py index 035bd6d..547c0e1 100644 --- a/models.py +++ b/models.py @@ -142,10 +142,14 @@ class Group(models.Model): class Room(models.Model): name = models.CharField(max_length=255, unique=True, verbose_name="nom") + slug = models.SlugField(max_length=64, default="", unique=True) def __str__(self): return self.name + def save(self, *args, **kwargs): + self.slug = slugify(self.name) + super(Room, self).save() class Meta: verbose_name = "salle" -- cgit v1.2.1 From 959afb011f5e98e56f88a455228eab32579c9433 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 13:35:37 +0100 Subject: Contenu de la page des emplois du temps --- urls.py | 3 ++- views.py | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/urls.py b/urls.py index 4de8103..b0ca9f4 100644 --- a/urls.py +++ b/urls.py @@ -19,7 +19,8 @@ from . import feeds, views urlpatterns = [ url(r"^$", views.index, name="index"), url(r"^pages/", include("django.contrib.flatpages.urls")), - url(r"^salles/", views.rooms, name="rooms"), + url(r"^salles/$", views.rooms, name="rooms"), + url(r"^salles/(?P[-\w]+)$", views.room_timetable), url(r"^(?P[-\w]+)/$", views.mention_list, name="mentions"), url(r"^(?P[-\w]+)/(?P[-\w]+)/$", views.group_list, name="groups"), url(r"^(?P[-\w]+)/(?P[-\w]+)/(?P[-\w]+)/$", views.timetable, name="timetable"), diff --git a/views.py b/views.py index c008052..9047329 100644 --- a/views.py +++ b/views.py @@ -15,12 +15,12 @@ import datetime -from django.db.models import Max -from django.db.models.functions import Length +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 .models import Timetable, Group, Course, Year +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 @@ -97,7 +97,36 @@ def calendars(request, year_slug, timetable_slug, group_slug): return render(request, "calendars.html", {"group": group, "groups": groups}) def rooms(request): - return render(request, "index.html") + # 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) + + # Récupération des salles et de toutes les semaines où elles sont + # concernées + # Cette requête est un peu lente sur sqlite… j’espère que ce sera mieux + # sur la base de prod. + rooms = Room.objects.filter(course__begin__gte=start, course__begin__lt=end) \ + .annotate(year=ExtractYear("course__begin"), + week=ExtractWeek("course__begin")) \ + .order_by("name").annotate(c=Count("*")) + + rooms_weeks = [] + for room in rooms: + if len(rooms_weeks) == 0 or rooms_weeks[-1].id != room.id: + room.weeks = [] + rooms_weeks.append(room) + + date, _ = get_week(room.year, room.week) + rooms_weeks[-1].weeks.append(date) + + return render(request, "group_list.html", {"groups": rooms_weeks}) + +def room_timetable(request, room_slug): + room = get_object_or_404(Room, slug=room_slug) + courses = Course.objects.filter(rooms__in=(room,)).order_by("begin") + + return render(request, "timetable.html", {"group": room, "courser": courses, + }) def ctx_processor(request): return {"celcatsanitizer_version": edt.VERSION} -- cgit v1.2.1 From ac9489cf4faa063c9270a3900a52809306355ea2 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 15:56:44 +0100 Subject: Adaptation de la template pour pouvoir afficher la liste des emplois du temps des salles --- templates/group_list.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/group_list.html b/templates/group_list.html index f21b2b1..88cc7a4 100644 --- a/templates/group_list.html +++ b/templates/group_list.html @@ -1,13 +1,13 @@ {% extends "index.html" %} {% load dt_week %} -{% block title %}{{ timetable }} – {% endblock %} +{% block title %}{% if timetable %}{{ timetable }}{% else %}Emploi du temps des salles{% endif %} – {% endblock %} {% block body %} -

{{ timetable }}

+

{% if timetable %}{{ timetable }}{% else %}Emploi du temps des salles{% endif %}

    {% for group in groups %} -
  • {{ group }} — {% for week in group.weeks %}{{ week|dt_prettyprint }} {% if not forloop.last %}– {% endif %}{% empty %}aucun cours dans le mois à venir{% endfor %}
  • +
  • {{ group }} — {% for week in group.weeks %}{{ week|dt_prettyprint }} {% if not forloop.last %}– {% endif %}{% empty %}aucun cours dans le mois à venir{% endfor %}
  • {% endfor %}
{% endblock %} -- cgit v1.2.1 From 599ca85167ad7d53f6434b105cb74c761d994ecf Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 16:04:18 +0100 Subject: Liens et URLs pour les emplois du temps des salles --- templates/group_list.html | 2 +- urls.py | 3 ++- views.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/templates/group_list.html b/templates/group_list.html index 88cc7a4..7fe1fe8 100644 --- a/templates/group_list.html +++ b/templates/group_list.html @@ -7,7 +7,7 @@

{% if timetable %}{{ timetable }}{% else %}Emploi du temps des salles{% endif %}

    {% for group in groups %} -
  • {{ group }} — {% for week in group.weeks %}{{ week|dt_prettyprint }} {% if not forloop.last %}– {% endif %}{% empty %}aucun cours dans le mois à venir{% endfor %}
  • +
  • {{ group }} — {% for week in group.weeks %}{{ week|dt_prettyprint }} {% if not forloop.last %}– {% endif %}{% empty %}aucun cours dans le mois à venir{% endfor %}
  • {% endfor %}
{% endblock %} diff --git a/urls.py b/urls.py index b0ca9f4..4c4abe3 100644 --- a/urls.py +++ b/urls.py @@ -20,7 +20,8 @@ urlpatterns = [ url(r"^$", views.index, name="index"), url(r"^pages/", include("django.contrib.flatpages.urls")), url(r"^salles/$", views.rooms, name="rooms"), - url(r"^salles/(?P[-\w]+)$", views.room_timetable), + url(r"^salles/(?P[-\w]+)$", views.room_timetable, name="room-timetable"), + url(r"^salles/(?P[-\w]+)/(?P[0-9]{4})/(?P[0-4]?[0-9]|5[0-3])/$", views.room_timetable, name="room-timetable"), url(r"^(?P[-\w]+)/$", views.mention_list, name="mentions"), url(r"^(?P[-\w]+)/(?P[-\w]+)/$", views.group_list, name="groups"), url(r"^(?P[-\w]+)/(?P[-\w]+)/(?P[-\w]+)/$", views.timetable, name="timetable"), diff --git a/views.py b/views.py index 9047329..e35796b 100644 --- a/views.py +++ b/views.py @@ -121,7 +121,7 @@ def rooms(request): return render(request, "group_list.html", {"groups": rooms_weeks}) -def room_timetable(request, room_slug): +def room_timetable(request, room_slug, year=None, week=None): room = get_object_or_404(Room, slug=room_slug) courses = Course.objects.filter(rooms__in=(room,)).order_by("begin") -- cgit v1.2.1 From 1046a9ea68d9a29dc929eb88581e50a0704a5142 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 16:04:28 +0100 Subject: Optimisation (?) de la requête des emplois du temps des salles --- views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/views.py b/views.py index e35796b..3916fac 100644 --- a/views.py +++ b/views.py @@ -103,12 +103,13 @@ def rooms(request): # Récupération des salles et de toutes les semaines où elles sont # concernées - # Cette requête est un peu lente sur sqlite… j’espère que ce sera mieux - # sur la base de prod. + # Cette requête est un peu lente sur sqlite… + # Par contre c’est beaucoup plus rapide sur PostgreSQL rooms = Room.objects.filter(course__begin__gte=start, course__begin__lt=end) \ + .order_by("name") \ .annotate(year=ExtractYear("course__begin"), - week=ExtractWeek("course__begin")) \ - .order_by("name").annotate(c=Count("*")) + week=ExtractWeek("course__begin"), + c=Count("*")) rooms_weeks = [] for room in rooms: -- cgit v1.2.1 From 3148aaf7c43866bd672d54ac54a0e70bc71f1020 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 17:14:26 +0100 Subject: Mise en commun du traitement des données avant rendu de l’emploi du temps --- models.py | 13 +++++++++---- views.py | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/models.py b/models.py index 547c0e1..369a4a6 100644 --- a/models.py +++ b/models.py @@ -157,10 +157,15 @@ class Room(models.Model): class CourseManager(Manager): - def get_courses_for_group(self, group, **criteria): - return self.get_queryset() \ - .filter(groups__in=Group.objects.get_parents(group), **criteria) \ - .order_by("begin").prefetch_related("rooms") + def get_courses(self, obj, **criteria): + qs = self.get_queryset() + if isinstance(obj, Group): + qs = qs.filter(groups__in=Group.objects.get_parents(obj), **criteria) \ + .prefetch_related("rooms") + elif isinstance(obj, Room): + qs = qs.filter(rooms__in=(obj,), **criteria) + + return qs.order_by("begin") def get_weeks(self, **criteria): return self.get_queryset() \ diff --git a/views.py b/views.py index 3916fac..428711f 100644 --- a/views.py +++ b/views.py @@ -61,7 +61,7 @@ def group_list(request, year_slug, timetable_slug): return render(request, "group_list.html", {"timetable": timetable, "groups": groups}) -def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): +def timetable_common(request, obj, year=None, week=None): current_year, current_week = get_current_or_next_week() is_old_timetable, provided_week = False, True @@ -73,21 +73,30 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No 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) - - 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 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, + return render(request, "timetable.html", {"group": obj, "courses": grouped_courses, "last_update": last_update, "year": year, "week": int(week), "is_old_timetable": is_old_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, timetable=timetable) + + if Group.objects.filter(timetable=timetable, mention=group.mention, + subgroup__startswith=group.subgroup).count() > 1: + subgroups = Group.objects.get_relevant_groups(timetable, mention=group.mention, + subgroup__startswith=group.subgroup) + return group_list_common(request, timetable, subgroups) + + return timetable_common(request, group, year, week) + 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) @@ -102,32 +111,40 @@ def rooms(request): end = start + datetime.timedelta(weeks=4) # Récupération des salles et de toutes les semaines où elles sont - # concernées - # Cette requête est un peu lente sur sqlite… - # Par contre c’est beaucoup plus rapide sur PostgreSQL + # 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_timetable(request, room_slug, year=None, week=None): room = get_object_or_404(Room, slug=room_slug) - courses = Course.objects.filter(rooms__in=(room,)).order_by("begin") - - return render(request, "timetable.html", {"group": room, "courser": courses, - }) + return timetable_common(request, room, year, week) def ctx_processor(request): return {"celcatsanitizer_version": edt.VERSION} -- cgit v1.2.1 From bb049a8d5954bbcf3ae5207a0ab4445591555d6e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 18:21:52 +0100 Subject: Tentative de mise en commun de la template des edts --- templates/timetable.html | 12 ++++++------ views.py | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/templates/timetable.html b/templates/timetable.html index fc2065f..4c921fd 100644 --- a/templates/timetable.html +++ b/templates/timetable.html @@ -1,21 +1,21 @@ {% extends "index.html" %} -{% block head %} +{% block head %}{% if group_mode %} -{% endblock %} +{% endif %}{% endblock %} -{% block title %}{{ group.timetable }} – {{ group }} – Semaine {{ week }} – {% endblock %} +{% block title %}{% if group_mode %}{{ group.timetable }} –{% else %}Salle{% endif %} {{ group }} – Semaine {{ week }} – {% endblock %} {% block body %} -

{{ group.timetable }} – {{ group }} – Semaine {{ week }}

+

{% if group_mode %}{{ group.timetable }} –{% else %}Salle{% endif %} {{ group }} – Semaine {{ week }}

{% if is_old_timetable %} - Accéder à l’emploi du temps de cette semaine.
+ Accéder à l’emploi du temps de cette semaine.
{% endif %} {% if last_update %}Dernière mise à jour le {{ last_update|date:"l j F o" }} à {{ last_update|date:"H:i" }}{% endif %}

{% include "timetable_common.html" %} - {% endblock %} + {% if group_mode %}{% endif %}{% endblock %} diff --git a/views.py b/views.py index 428711f..cb6978b 100644 --- a/views.py +++ b/views.py @@ -83,7 +83,8 @@ def timetable_common(request, obj, year=None, week=None): return render(request, "timetable.html", {"group": obj, "courses": grouped_courses, "last_update": last_update, "year": year, "week": int(week), - "is_old_timetable": is_old_timetable}) + "is_old_timetable": is_old_timetable, + "group_mode": isinstance(obj, Group)}) 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) -- cgit v1.2.1 From 679010e9cb526709963ebcf49450c1c7c1ef105e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 19 Nov 2017 00:05:24 +0100 Subject: Liste des groupes à la place de la liste des salles dans l’edt des salles --- templates/timetable_common.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/timetable_common.html b/templates/timetable_common.html index 62b1d71..d9e4ec7 100644 --- a/templates/timetable_common.html +++ b/templates/timetable_common.html @@ -5,7 +5,7 @@
    {% for course in day %}
  • {{ course }}{% if course.type %} ({{ course.type }}){% endif %}, de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}
    - {{ course.rooms.all|format_rooms }}{% endif %}{% if course.notes %}
    + {% if group_mode %}{{ course.rooms.all|format_rooms }}{% else %}{{ course.groups.all|join:", " }}{% endif %}{% endif %}{% if course.notes %}
    Remarques : {{ course.notes }}{% endif %}
  • {% endfor %}
-- cgit v1.2.1 From 700d4218b84c45bb37a150f0ec1c9bf6866b3bf3 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 19 Nov 2017 00:19:21 +0100 Subject: À QUOI ÇA SERT DE FAIRE DES SUPERS MODÈLES ABSTRAITS SI ON S’EN SERT PAS APRÈS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA --- models.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/models.py b/models.py index 369a4a6..66d5ae5 100644 --- a/models.py +++ b/models.py @@ -84,7 +84,7 @@ class GroupManager(Manager): timetable=group.timetable) -class Group(models.Model): +class Group(SlugModel): objects = GroupManager() name = models.CharField(max_length=255, verbose_name="nom") @@ -121,7 +121,6 @@ class Group(models.Model): def save(self, *args, **kwargs): if self.name == "": self.name = self.celcat_name - self.slug = slugify(self.name) self.mention, self.semester, self.subgroup = parse_group(self.name) if self.subgroup is None: @@ -140,17 +139,13 @@ class Group(models.Model): verbose_name_plural = "groupes" -class Room(models.Model): +class Room(SlugModel): name = models.CharField(max_length=255, unique=True, verbose_name="nom") slug = models.SlugField(max_length=64, default="", unique=True) def __str__(self): return self.name - def save(self, *args, **kwargs): - self.slug = slugify(self.name) - super(Room, self).save() - class Meta: verbose_name = "salle" verbose_name_plural = "salles" -- cgit v1.2.1 From 582b1d2be865cc0fba9aa4726404f4370d0b80c5 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 19 Nov 2017 01:10:29 +0100 Subject: Séparation en deux modèles des emplois du temps : un pour l’affichage, l’autre pour les sources --- admin.py | 9 +++++++-- models.py | 22 +++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/admin.py b/admin.py index 011ad73..717a386 100644 --- a/admin.py +++ b/admin.py @@ -14,7 +14,7 @@ # along with celcatsanitizer. If not, see . from django.contrib import admin -from .models import Timetable, Group, Room, Course, Year +from .models import Course, Group, Room, Timetable, TimetableFront, Year def make_hidden(modeladmin, request, queryset): queryset.update(hidden=True) @@ -34,8 +34,13 @@ class YearAdmin(admin.ModelAdmin): @admin.register(Timetable) class TimetableAdmin(admin.ModelAdmin): + list_display = ("url", "last_update_date",) + + +@admin.register(TimetableFront) +class TimetableFrontAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} - list_display = ("name", "year", "url",) + list_display = ("name", "year", "source",) list_filter = ("year__name",) ordering = ("year", "name",) diff --git a/models.py b/models.py index 66d5ae5..dc9732b 100644 --- a/models.py +++ b/models.py @@ -49,15 +49,27 @@ class Year(SlugModel): verbose_name_plural = "années" -class Timetable(SlugModel): +class Timetable(models.Model): + url = models.URLField(max_length=255, verbose_name="URL", unique=True) + last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", + null=True, blank=True) + + def __str__(self): + return self.url + + + class Meta: + verbose_name = "source d’emploi du temps" + verbose_name_plural = "sources d’emploi du temps" + + +class TimetableFront(SlugModel): year = models.ForeignKey(Year, on_delete=models.CASCADE, verbose_name="année") name = models.CharField(max_length=64, verbose_name="nom") - url = models.URLField(max_length=255, verbose_name="URL") slug = models.SlugField(max_length=64, default="") - - last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", - null=True, blank=True) + source = models.ForeignKey(Timetable, on_delete=models.CASCADE, + verbose_name="source") def __str__(self): return self.year.name + " " + self.name -- cgit v1.2.1 From f729db8ed67f4540eb265e848bcd71add3ff074e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 15:25:42 +0100 Subject: Renommage de Timetable en Source et de TimetableFront en Timetable. C’est à partir de ce commit que la migration fournie sur la ML est utilisable. --- admin.py | 10 +++++----- models.py | 11 +++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/admin.py b/admin.py index 717a386..9d8a34f 100644 --- a/admin.py +++ b/admin.py @@ -14,7 +14,7 @@ # along with celcatsanitizer. If not, see . from django.contrib import admin -from .models import Course, Group, Room, Timetable, TimetableFront, Year +from .models import Course, Group, Room, Source, Timetable, Year def make_hidden(modeladmin, request, queryset): queryset.update(hidden=True) @@ -32,13 +32,13 @@ class YearAdmin(admin.ModelAdmin): ordering = ("name",) -@admin.register(Timetable) -class TimetableAdmin(admin.ModelAdmin): +@admin.register(Source) +class SourceAdmin(admin.ModelAdmin): list_display = ("url", "last_update_date",) -@admin.register(TimetableFront) -class TimetableFrontAdmin(admin.ModelAdmin): +@admin.register(Timetable) +class TimetableAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} list_display = ("name", "year", "source",) list_filter = ("year__name",) diff --git a/models.py b/models.py index dc9732b..c55bbae 100644 --- a/models.py +++ b/models.py @@ -31,7 +31,6 @@ class SlugModel(models.Model): super(SlugModel, self).save() - class Meta: abstract = True @@ -49,7 +48,7 @@ class Year(SlugModel): verbose_name_plural = "années" -class Timetable(models.Model): +class Source(models.Model): url = models.URLField(max_length=255, verbose_name="URL", unique=True) last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", null=True, blank=True) @@ -63,12 +62,12 @@ class Timetable(models.Model): verbose_name_plural = "sources d’emploi du temps" -class TimetableFront(SlugModel): +class Timetable(SlugModel): year = models.ForeignKey(Year, on_delete=models.CASCADE, verbose_name="année") name = models.CharField(max_length=64, verbose_name="nom") slug = models.SlugField(max_length=64, default="") - source = models.ForeignKey(Timetable, on_delete=models.CASCADE, + source = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="source") def __str__(self): @@ -102,7 +101,7 @@ class Group(SlugModel): name = models.CharField(max_length=255, verbose_name="nom") celcat_name = models.CharField(max_length=255, verbose_name="nom dans Celcat") - timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, + timetable = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="emploi du temps") mention = models.CharField(max_length=128) @@ -190,7 +189,7 @@ class Course(models.Model): name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom") type_ = models.CharField(name="type", max_length=255, verbose_name="type de cours", null=True) - timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, + timetable = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="emploi du temps") notes = models.TextField(verbose_name="remarques", blank=True, null=True) -- cgit v1.2.1 From 703cec5da11e7b859ef0172f5cc656bd8040a846 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 15:42:33 +0100 Subject: Relation source inverse --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models.py b/models.py index c55bbae..eba8f1c 100644 --- a/models.py +++ b/models.py @@ -68,7 +68,7 @@ class Timetable(SlugModel): name = models.CharField(max_length=64, verbose_name="nom") slug = models.SlugField(max_length=64, default="") source = models.ForeignKey(Source, on_delete=models.CASCADE, - verbose_name="source") + verbose_name="source", related_name="timetables") def __str__(self): return self.year.name + " " + self.name -- cgit v1.2.1 From e1c4eea3c706bb45cd73416e2e8626bd50a97cfb Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:04:27 +0100 Subject: Correction des interfaces utilisateur --- feeds.py | 53 ++++++++++++++++++++++-------------------------- templates/calendars.html | 4 ++-- templates/timetable.html | 14 ++++++------- views.py | 25 ++++++++++------------- 4 files changed, 44 insertions(+), 52 deletions(-) diff --git a/feeds.py b/feeds.py index aced5e6..95ecaf8 100644 --- a/feeds.py +++ b/feeds.py @@ -24,7 +24,7 @@ from django.utils.feedgenerator import Atom1Feed, SyndicationFeed from icalendar import Calendar, Event -from .models import Course, Group +from .models import Course, Group, Timetable from .templatetags.rooms import format_rooms from .utils import get_current_or_next_week, get_week, group_courses @@ -58,8 +58,9 @@ class IcalFeed(Feed): def get_object(self, request, year_slug, timetable_slug, group_slug): try: - group = Group.objects.get(timetable__year__slug=year_slug, - timetable__slug=timetable_slug, + timetable = Timetable.objects.get(year__slug=year_slug, + slug=timetable_slug) + group = Group.objects.get(timetable=timetable.source, slug=group_slug) except: raise ObjectDoesNotExist @@ -81,7 +82,7 @@ class IcalFeed(Feed): return item.name def items(self, obj): - return Course.objects.get_courses_for_group(obj) + return Course.objects.get_courses(obj) def item_extra_kwargs(self, item): return {"uid": "{0}@celcatsanitizer".format(item.id), @@ -103,39 +104,37 @@ class RSSFeed(Feed): _, end = get_week(year, week) try: - group = Group.objects.get(timetable__year__slug=year_slug, - timetable__slug=timetable_slug, - slug=group_slug) + self.timetable = Timetable.objects.get(year__slug=year_slug, + slug=timetable_slug) + self.group = Group.objects.get(timetable=self.timetable.source, + slug=group_slug) except: raise ObjectDoesNotExist else: - updates = Course.objects.get_courses_for_group(group, - begin__lt=end) \ + updates = Course.objects.get_courses(self.group, begin__lt=end) \ .annotate(year=ExtractYear("begin"), week=ExtractWeek("begin")) \ .values("year", "week") \ .annotate(Count("year", distinct=True), Max("last_update")) \ .order_by("-year", "-week")[:5] - return group, updates + return updates def link(self, obj): - group = obj[0] link = reverse("timetable", - kwargs={"year_slug": group.timetable.year.slug, - "timetable_slug": group.timetable.slug, - "group_slug": group.slug}) + kwargs={"year_slug": self.timetable.year.slug, + "timetable_slug": self.timetable.slug, + "group_slug": self.group.slug}) return link def title(self, obj): - return "Emploi du temps du groupe {0}".format(obj[0]) + return "Emploi du temps du groupe {0}".format(self.group) def item_link(self, item): - group = item["group"] return reverse("timetable", - kwargs={"year_slug": group.timetable.year.slug, - "timetable_slug": group.timetable.slug, - "group_slug": group.slug, + kwargs={"year_slug": self.timetable.year.slug, + "timetable_slug": self.timetable.slug, + "group_slug": self.group.slug, "year": item["year"], "week": item["week"]}) @@ -143,7 +142,7 @@ class RSSFeed(Feed): return item["description"] def item_title(self, item): - return "{0}, semaine {1} de {2}".format(item["group"], + return "{0}, semaine {1} de {2}".format(self.group, item["week"], item["year"]) @@ -152,23 +151,19 @@ class RSSFeed(Feed): def items(self, obj): template = loader.get_template("timetable_common.html") - group = obj[0] - for update in obj[1]: + for update in obj: start, end = get_week(update["year"], update["week"]) - courses = Course.objects.get_courses_for_group(group, - begin__gte=start, - begin__lt=end) - context = {"group": group, - "courses": group_courses(courses), + courses = Course.objects.get_courses(self.group, begin__gte=start, + begin__lt=end) + context = {"courses": group_courses(courses), "last_update": update["last_update__max"], "year": update["year"], "week": update["week"]} - update["group"] = group update["description"] = template.render(context) - return obj[1] + return obj class AtomFeed(RSSFeed): diff --git a/templates/calendars.html b/templates/calendars.html index d97ea78..52c3a11 100644 --- a/templates/calendars.html +++ b/templates/calendars.html @@ -5,9 +5,9 @@ {% block body %}

ICS disponibles pour le groupe {{ group }}

{% endblock %} diff --git a/templates/timetable.html b/templates/timetable.html index 4c921fd..8fafeed 100644 --- a/templates/timetable.html +++ b/templates/timetable.html @@ -2,20 +2,20 @@ {% block head %}{% if group_mode %} - - - + + + {% endif %}{% endblock %} -{% block title %}{% if group_mode %}{{ group.timetable }} –{% else %}Salle{% endif %} {{ group }} – Semaine {{ week }} – {% endblock %} +{% block title %}{% if group_mode %}{{ timetable }} –{% else %}Salle{% endif %} {{ group }} – Semaine {{ week }} – {% endblock %} {% block body %} -

{% if group_mode %}{{ group.timetable }} –{% else %}Salle{% endif %} {{ group }} – Semaine {{ week }}

+

{% if group_mode %}{{ timetable }} –{% else %}Salle{% endif %} {{ group }} – Semaine {{ week }}

{% if is_old_timetable %} - Accéder à l’emploi du temps de cette semaine.
+ Accéder à l’emploi du temps de cette semaine.
{% endif %} {% if last_update %}Dernière mise à jour le {{ last_update|date:"l j F o" }} à {{ last_update|date:"H:i" }}{% endif %}

{% include "timetable_common.html" %} - {% if group_mode %}{% endif %}{% endblock %} + {% if group_mode %}{% endif %}{% endblock %} diff --git a/views.py b/views.py index cb6978b..3179ec5 100644 --- a/views.py +++ b/views.py @@ -37,7 +37,7 @@ def mention_list(request, year_slug): 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") + groups = Group.objects.filter(timetable=timetable.source, hidden=False).order_by("name") start, _ = get_week(*get_current_week()) end = start + datetime.timedelta(weeks=4) @@ -61,7 +61,7 @@ def group_list(request, year_slug, timetable_slug): return render(request, "group_list.html", {"timetable": timetable, "groups": groups}) -def timetable_common(request, obj, year=None, week=None): +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 @@ -84,27 +84,24 @@ def timetable_common(request, obj, year=None, week=None): "last_update": last_update, "year": year, "week": int(week), "is_old_timetable": is_old_timetable, - "group_mode": isinstance(obj, Group)}) + "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, timetable=timetable) + group = get_object_or_404(Group, slug=group_slug, timetable=timetable.source) - if Group.objects.filter(timetable=timetable, mention=group.mention, - subgroup__startswith=group.subgroup).count() > 1: - subgroups = Group.objects.get_relevant_groups(timetable, mention=group.mention, - subgroup__startswith=group.subgroup) - return group_list_common(request, timetable, subgroups) - - return timetable_common(request, group, year, week) + 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) + timetable = get_object_or_404(Timetable, year__slug=year_slug, + slug=timetable_slug) + group = get_object_or_404(Group, timetable=timetable.source, slug=group_slug) groups = Group.objects.get_parents(group).annotate(length=Length("subgroup")) \ .order_by("length") - return render(request, "calendars.html", {"group": group, "groups": groups}) + 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 -- cgit v1.2.1 From 3efa5da22447fee662d81ebafb21b5e9b9f796fc Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:06:31 +0100 Subject: Affichage des salles à la place des groupes dans les flux RSS et Atom --- feeds.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feeds.py b/feeds.py index 95ecaf8..8341f06 100644 --- a/feeds.py +++ b/feeds.py @@ -159,7 +159,8 @@ class RSSFeed(Feed): context = {"courses": group_courses(courses), "last_update": update["last_update__max"], "year": update["year"], - "week": update["week"]} + "week": update["week"], + "group_mode": True} update["description"] = template.render(context) -- cgit v1.2.1 From d0c69d3095d14f5509190d6b637cc0c018e53a19 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:25:25 +0100 Subject: Changement des champs timetable en source pour plus de clareté --- admin.py | 14 +++++++------- models.py | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/admin.py b/admin.py index 9d8a34f..fc0ada1 100644 --- a/admin.py +++ b/admin.py @@ -48,12 +48,12 @@ class TimetableAdmin(admin.ModelAdmin): @admin.register(Group) class GroupAdmin(admin.ModelAdmin): fieldsets = ( - (None, {"fields": ("name", "celcat_name", "timetable", "hidden",)}), + (None, {"fields": ("name", "celcat_name", "source", "hidden",)}), ("Groupes", {"fields": ("mention", "semester", "subgroup",)}),) - list_display = ("name", "timetable", "hidden",) + list_display = ("name", "source", "hidden",) list_editable = ("hidden",) - list_filter = ("timetable",) - ordering = ("timetable",) + list_filter = ("source__timetables",) + ordering = ("name", "source",) readonly_fields = ("celcat_name", "mention", "semester", "subgroup",) actions = (make_hidden, make_visible,) @@ -66,9 +66,9 @@ class RoomAdmin(admin.ModelAdmin): @admin.register(Course) class CourseAdmin(admin.ModelAdmin): fieldsets = ( - (None, {"fields": ("name", "type", "timetable", "groups", "rooms", "last_update",)}), + (None, {"fields": ("name", "type", "source", "groups", "rooms", "last_update",)}), ("Horaires", {"fields": ("begin", "end",)}), ("Remarques", {"fields": ("notes",)}),) - list_display = ("name", "type", "timetable", "begin", "end",) - list_filter = ("type", "timetable", "groups",) + list_display = ("name", "type", "source", "begin", "end",) + list_filter = ("type", "source__timetables", "groups",) ordering = ("begin",) diff --git a/models.py b/models.py index eba8f1c..3c9fa27 100644 --- a/models.py +++ b/models.py @@ -101,8 +101,8 @@ class Group(SlugModel): name = models.CharField(max_length=255, verbose_name="nom") celcat_name = models.CharField(max_length=255, verbose_name="nom dans Celcat") - timetable = models.ForeignKey(Source, on_delete=models.CASCADE, - verbose_name="emploi du temps") + source = models.ForeignKey(Source, on_delete=models.CASCADE, + verbose_name="source d’emploi du temps") mention = models.CharField(max_length=128) semester = models.IntegerField(verbose_name="semestre", null=True) @@ -142,9 +142,9 @@ class Group(SlugModel): class Meta: index_together = ("mention", "semester", "subgroup",) - unique_together = (("name", "timetable",), - ("celcat_name", "timetable",), - ("slug", "timetable",),) + unique_together = (("name", "source",), + ("celcat_name", "source",), + ("slug", "source",),) verbose_name = "groupe" verbose_name_plural = "groupes" @@ -189,8 +189,8 @@ class Course(models.Model): name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom") type_ = models.CharField(name="type", max_length=255, verbose_name="type de cours", null=True) - timetable = models.ForeignKey(Source, on_delete=models.CASCADE, - verbose_name="emploi du temps") + source = models.ForeignKey(Source, on_delete=models.CASCADE, + verbose_name="emploi du temps") notes = models.TextField(verbose_name="remarques", blank=True, null=True) groups = models.ManyToManyField(Group, verbose_name="groupes") -- cgit v1.2.1 From 5d4d7530e1aba199c2604b311b17bef253f0a008 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:27:50 +0100 Subject: Remplacement des références aux champs timetable vers source --- feeds.py | 4 ++-- models.py | 2 +- views.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/feeds.py b/feeds.py index 8341f06..dde051b 100644 --- a/feeds.py +++ b/feeds.py @@ -60,7 +60,7 @@ class IcalFeed(Feed): try: timetable = Timetable.objects.get(year__slug=year_slug, slug=timetable_slug) - group = Group.objects.get(timetable=timetable.source, + group = Group.objects.get(source=timetable.source, slug=group_slug) except: raise ObjectDoesNotExist @@ -106,7 +106,7 @@ class RSSFeed(Feed): try: self.timetable = Timetable.objects.get(year__slug=year_slug, slug=timetable_slug) - self.group = Group.objects.get(timetable=self.timetable.source, + self.group = Group.objects.get(source=self.timetable.source, slug=group_slug) except: raise ObjectDoesNotExist diff --git a/models.py b/models.py index 3c9fa27..6124b59 100644 --- a/models.py +++ b/models.py @@ -92,7 +92,7 @@ class GroupManager(Manager): return self.get_queryset().filter(groups_criteria, Q(semester=None) | Q(semester=group.semester), mention=group.mention, - timetable=group.timetable) + source=group.source) class Group(SlugModel): diff --git a/views.py b/views.py index 3179ec5..2bf596b 100644 --- a/views.py +++ b/views.py @@ -37,7 +37,7 @@ def mention_list(request, year_slug): 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.source, hidden=False).order_by("name") + groups = Group.objects.filter(source=timetable.source, hidden=False).order_by("name") start, _ = get_week(*get_current_week()) end = start + datetime.timedelta(weeks=4) @@ -89,14 +89,14 @@ def timetable_common(request, obj, year=None, week=None, timetable=None): 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, timetable=timetable.source) + 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, timetable=timetable.source, slug=group_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") -- cgit v1.2.1 From 3b9eab819c603b1d3b2b59d79258ce6a05aa24b9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:34:27 +0100 Subject: Inversion des colonnes à trier pour le groupe dans l’interface d’admin --- admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin.py b/admin.py index fc0ada1..5af6cb1 100644 --- a/admin.py +++ b/admin.py @@ -53,7 +53,7 @@ class GroupAdmin(admin.ModelAdmin): list_display = ("name", "source", "hidden",) list_editable = ("hidden",) list_filter = ("source__timetables",) - ordering = ("name", "source",) + ordering = ("source", "name",) readonly_fields = ("celcat_name", "mention", "semester", "subgroup",) actions = (make_hidden, make_visible,) -- cgit v1.2.1 From fb6ea65e7d32dcf3d697e6c63985a018ada4d6b1 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:37:31 +0100 Subject: Ajout d’un gestionnaire pour Timetable récupérant automatiquement les années Réduit considérablement le nombre d’appels effectués dans l’interface d’administration --- models.py | 7 +++++++ views.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index 6124b59..b8c67d6 100644 --- a/models.py +++ b/models.py @@ -62,7 +62,14 @@ class Source(models.Model): verbose_name_plural = "sources d’emploi du temps" +class TimetableManager(Manager): + def get_queryset(self): + return super(Manager, self).get_queryset().select_related("year") + + class Timetable(SlugModel): + objects = TimetableManager() + year = models.ForeignKey(Year, on_delete=models.CASCADE, verbose_name="année") name = models.CharField(max_length=64, verbose_name="nom") diff --git a/views.py b/views.py index 2bf596b..97a1d28 100644 --- a/views.py +++ b/views.py @@ -31,7 +31,7 @@ def index(request): 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, "index.html", {"year": year, "elements": timetables}) -- cgit v1.2.1 From 90414449f9f74b675d3793c2c48a796f9bfe4b29 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 17:14:14 +0100 Subject: Adaptation de la commande de mise à jour des emplois du temps aux changements effectués sur la structure de la base de données --- management/commands/_private.py | 16 ++++++++-------- management/commands/timetables.py | 33 +++++++++++++++++---------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 4dd9262..171b6e9 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -19,7 +19,7 @@ import re from bs4 import BeautifulSoup from django.utils import timezone -from edt.models import Group, Room, Course +from edt.models import Course, Group, Room from edt.utils import get_week import requests @@ -30,10 +30,10 @@ def add_time(date, time): delta = datetime.timedelta(hours=ptime.hour, minutes=ptime.minute) return date + delta -def delete_courses_in_week(timetable, year, week, today): +def delete_courses_in_week(source, year, week, today): start, end = get_week(year, week) Course.objects.filter(begin__gte=max(start, today), begin__lt=end, - timetable=timetable).delete() + source=source).delete() def get_from_db_or_create(cls, **kwargs): obj = cls.objects.all().filter(**kwargs) @@ -45,7 +45,7 @@ def get_from_db_or_create(cls, **kwargs): return obj -def get_event(timetable, event, event_week, today): +def get_event(source, event, event_week, today): """Renvoie une classe Course à partir d’un événement récupéré par BS4""" # On récupère la date de l’évènement à partir de la semaine # et de la semaine référencée, puis l’heure de début et de fin @@ -58,10 +58,10 @@ def get_event(timetable, event, event_week, today): return # Création de l’objet cours - course = Course.objects.create(timetable=timetable, begin=begin, end=end) + course = Course.objects.create(source=source, begin=begin, end=end) # On récupère les groupes concernés par les cours - groups = [get_from_db_or_create(Group, timetable=timetable, + groups = [get_from_db_or_create(Group, source=source, celcat_name=item.text) for item in event.resources.group.find_all("item")] course.groups.add(*groups) @@ -96,7 +96,7 @@ def get_event(timetable, event, event_week, today): return course -def get_events(timetable, soup, weeks_in_soup, today, year=None, week=None): +def get_events(source, soup, weeks_in_soup, today, year=None, week=None): """Récupère tous les cours disponibles dans l’emploi du temps Celcat. Le traîtement se limitera à la semaine indiquée si il y en a une.""" for event in soup.find_all("event"): @@ -110,7 +110,7 @@ def get_events(timetable, soup, weeks_in_soup, today, year=None, week=None): year is None or week is None) and \ event.resources.group is not None and \ event.starttime is not None and event.endtime is not None: - course = get_event(timetable, event, event_week, today) + course = get_event(source, event, event_week, today) # On renvoie le cours si il n’est pas nul if course is not None: diff --git a/management/commands/timetables.py b/management/commands/timetables.py index ff00c8f..9734d13 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -20,12 +20,12 @@ from django.core.management.base import BaseCommand from django.db import transaction from django.db.models import Min -from edt.models import Course, Timetable +from edt.models import Course, Source from edt.utils import get_week, tz_now from ._private import delete_courses_in_week, get_events, get_update_date, get_weeks, get_xml @transaction.atomic -def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, week=None): +def process_timetable_week(source, soup, weeks_in_soup, force, year=None, week=None): if year is not None and week is not None: begin, end = get_week(year, week) @@ -41,7 +41,7 @@ def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, wee today = tz_now() # On récupère la mise à jour la plus ancienne dans les cours de l’emploi du temps - last_update_date = Course.objects.filter(timetable=timetable) + last_update_date = Course.objects.filter(source=source) if today is not None: # Cette date concerne les éléments commençant à partir d’aujourd’hui si la valeur @@ -71,7 +71,7 @@ def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, wee if year is not None and week is not None: # On efface la semaine à partir de maintenant si jamais # on demande le traitement d’une seule semaine - delete_courses_in_week(timetable, year, week, today) + delete_courses_in_week(source, year, week, today) else: # Sinon, on efface tous les cours à partir de maintenant. # Précisément, on prend la plus grande valeur entre la première semaine @@ -80,26 +80,26 @@ def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, wee if not force: # Si jamais on force la MàJ, on efface tout à partir de la première semaine delete_from = max(delete_from, today) - Course.objects.filter(timetable=timetable, begin__gte=delete_from).delete() + Course.objects.filter(source=source, begin__gte=delete_from).delete() # Tous les cours commençant sur la période traitée # sont parsés, puis enregistrés dans la base de données. - for course in get_events(timetable, soup, weeks_in_soup, today, year, week): + for course in get_events(source, soup, weeks_in_soup, today, year, week): course.save() # On renseigne la date de mise à jour de Celcat, à des fins de statistiques - timetable.last_update_date = new_update_date - timetable.save() + source.last_update_date = new_update_date + source.save() -def process_timetable(timetable, force, year=None, weeks=None): - soup = get_xml(timetable.url) +def process_timetable(source, force, year=None, weeks=None): + soup = get_xml(source.url) weeks_in_soup = get_weeks(soup) if year is not None and weeks is not None: for week in weeks: - process_timetable_week(timetable, soup, weeks_in_soup, force, year, week) + process_timetable_week(source, soup, weeks_in_soup, force, year, week) else: - process_timetable_week(timetable, soup, weeks_in_soup, force) + process_timetable_week(source, soup, weeks_in_soup, force) class Command(BaseCommand): @@ -131,16 +131,17 @@ class Command(BaseCommand): elif year is None: year = options["year"][0] - for timetable in Timetable.objects.all(): - self.stdout.write("Processing {0}".format(timetable)) + for source in Source.objects.all(): + timetables = ", ".join([str(timetable) for timetable in source.timetables.all()]) + self.stdout.write("Processing {0}".format(timetables)) try: - process_timetable(timetable, options["force"], year, weeks) + process_timetable(source, options["force"], year, weeks) except KeyboardInterrupt: break except Exception: self.stderr.write( - self.style.ERROR("Failed to process {0}:".format(timetable)) + self.style.ERROR("Failed to process {0}:".format(timetables)) ) self.stderr.write(self.style.ERROR(traceback.format_exc())) errcount += 1 -- cgit v1.2.1 From a5b0d082a728b06e5e02f4d64632ac0b0a572aec Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 17:18:41 +0100 Subject: Adaptation des commandes cleancourses et listtimetables au changements --- management/commands/cleancourses.py | 8 ++++---- management/commands/listtimetables.py | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/management/commands/cleancourses.py b/management/commands/cleancourses.py index f6041ef..310c843 100644 --- a/management/commands/cleancourses.py +++ b/management/commands/cleancourses.py @@ -22,15 +22,15 @@ class Command(BaseCommand): help = "Remove all courses and groups from the database" def add_arguments(self, parser): - parser.add_argument("--timetable", type=int, nargs="+") + parser.add_argument("--source", type=int, nargs="+") def handle(self, *args, **options): with transaction.atomic(): - if options["timetable"] is None: + if options["source"] is None: Course.objects.all().delete() Group.objects.all().delete() else: - Course.objects.filter(timetable__id__in=options["timetable"]).delete() - Group.objects.filter(timetable__id__in=options["timetable"]).delete() + Course.objects.filter(source__id__in=options["source"]).delete() + Group.objects.filter(source__id__in=options["source"]).delete() self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/management/commands/listtimetables.py b/management/commands/listtimetables.py index 6df7ba5..171fc2b 100644 --- a/management/commands/listtimetables.py +++ b/management/commands/listtimetables.py @@ -14,7 +14,7 @@ # along with celcatsanitizer. If not, see . from django.core.management.base import BaseCommand -from edt.models import Timetable +from edt.models import Source class Command(BaseCommand): @@ -24,14 +24,13 @@ class Command(BaseCommand): parser.add_argument("--order-by-id", action="store_true") def handle(self, *args, **options): - timetables = Timetable.objects.all() + sources = Source.objects.all() if options["order_by_id"]: - timetables = timetables.order_by("id") - else: - timetables = timetables.order_by("year__name", "name") + sources = sources.order_by("id") - for timetable in timetables: - self.stdout.write("{0} (id: {1})".format(timetable, timetable.id)) + for source in sources: + self.stdout.write("{0}\t: {1} (id: {2})".format(", ".join([str(timetable) for timetable in source.timetables.all()]), + source, source.id)) self.stdout.write("") self.stdout.write(self.style.SUCCESS("Done.")) -- cgit v1.2.1 From 8a71b0a55847e8783006b5a9514f64e0129578c9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 17:23:11 +0100 Subject: Fonction pour formater les emplois du temps à partir d’une source --- management/commands/listtimetables.py | 4 ++-- management/commands/timetables.py | 5 ++--- models.py | 4 ++++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/management/commands/listtimetables.py b/management/commands/listtimetables.py index 171fc2b..25f641b 100644 --- a/management/commands/listtimetables.py +++ b/management/commands/listtimetables.py @@ -29,8 +29,8 @@ class Command(BaseCommand): sources = sources.order_by("id") for source in sources: - self.stdout.write("{0}\t: {1} (id: {2})".format(", ".join([str(timetable) for timetable in source.timetables.all()]), - source, source.id)) + self.stdout.write("{0}\t: {1} (id: {2})".format(source.formatted_timetables, + source, source.id)) self.stdout.write("") self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 9734d13..86f389e 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -132,8 +132,7 @@ class Command(BaseCommand): year = options["year"][0] for source in Source.objects.all(): - timetables = ", ".join([str(timetable) for timetable in source.timetables.all()]) - self.stdout.write("Processing {0}".format(timetables)) + self.stdout.write("Processing {0}".format(source.formatted_timetables)) try: process_timetable(source, options["force"], year, weeks) @@ -141,7 +140,7 @@ class Command(BaseCommand): break except Exception: self.stderr.write( - self.style.ERROR("Failed to process {0}:".format(timetables)) + self.style.ERROR("Failed to process {0}:".format(source.formatted_timetables)) ) self.stderr.write(self.style.ERROR(traceback.format_exc())) errcount += 1 diff --git a/models.py b/models.py index b8c67d6..363cf33 100644 --- a/models.py +++ b/models.py @@ -56,6 +56,10 @@ class Source(models.Model): def __str__(self): return self.url + @property + def formatted_timetables(self): + return ", ".join([str(timetable) for timetable in self.timetables.iterator()]) + class Meta: verbose_name = "source d’emploi du temps" -- cgit v1.2.1 From af055dbf703af0eca03862194de1a0f34ac1fcc5 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 3 Dec 2017 15:27:04 +0100 Subject: Ajout d’un fichier requirements.txt pour pip, modification du readme --- README.md | 27 ++++++++++++++++++++++----- requirements.txt | 6 ++++++ 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index 7dc9d5c..c0a5f3c 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,35 @@ $ pip install --upgrade pip #### Installation des dépendances +Vous pouvez demander à pip d’installer les dépendances à partir du +fichier requirements.txt présent dans le dépôt : + +```bash +$ pip install -r requirements.txt +``` + +pip installera Django, BeautifulSoup, icalendar, requests, gunicorn, +psycopg2 ainsi que toutes leurs dépendances. C’est la manière +recommandée de l’installer dans un environnement de production. + +Alternativement, si vous vous montez un environnement de +développement dans le but de mettre à jour les dépendances, il est +possible d’installer les dépendances à la main : + ```bash -$ pip install requests django beautifulsoup4 icalendar +$ pip install requests django beautifulsoup4 icalendar psycopg2 gunicorn ``` -Si vous utilisez PostgreSQL, vous allez avoir besoin du driver -psycopg2 : +Il est aussi possible d’installer +[django-debug-toolbar](https://django-debug-toolbar.readthedocs.io/en/stable/) +(pour le développement seulement) : ```bash -$ pip install psycopg2 +$ pip install django-debug-toolbar ``` -SQLite n’a pas besoin de driver. +Réferez-vous à la documentation de django-debug-toolbar pour +l’installer et le configurer sur votre environnement. #### Création du répertoire Django diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2ae5c48 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +beautifulsoup4==4.6.0 +Django==1.11.7 +gunicorn==19.7.1 +icalendar==4.0.0 +psycopg2==2.7.3.2 +requests==2.18.4 -- cgit v1.2.1 From 81cb9b8c6a96fa8529c6d7f6ca55dfd56a1f2e5d Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 11 Jan 2018 14:09:33 +0100 Subject: On remplace les
dans le champ remarque par des retours à la ligne lors du parsage, puis on les remplace à nouveau par des
lors du rendu des templates. --- management/commands/_private.py | 2 +- templates/timetable_common.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 171b6e9..0a3d5fb 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -68,7 +68,7 @@ def get_event(source, event, event_week, today): # On récupère le champ « remarque » if event.notes is not None: - course.notes = event.notes.text + course.notes = "\n".join(event.notes.find_all(text=True)) # On récupère le champ « nom » if event.resources.module is not None: diff --git a/templates/timetable_common.html b/templates/timetable_common.html index d9e4ec7..21300f1 100644 --- a/templates/timetable_common.html +++ b/templates/timetable_common.html @@ -6,7 +6,7 @@
  • {{ course }}{% if course.type %} ({{ course.type }}){% endif %}, de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}
    {% if group_mode %}{{ course.rooms.all|format_rooms }}{% else %}{{ course.groups.all|join:", " }}{% endif %}{% endif %}{% if course.notes %}
    - Remarques : {{ course.notes }}{% endif %} + Remarques : {{ course.notes|linebreaksbr }}{% endif %}
  • {% endfor %} {% empty %} -- cgit v1.2.1 From e710ab4b1293ecbd0f61649b4abb3072379f362f Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 11 Jan 2018 14:09:33 +0100 Subject: On remplace les
    dans le champ remarque par des retours à la ligne lors du parsage, puis on les remplace à nouveau par des
    lors du rendu des templates. --- management/commands/_private.py | 2 +- templates/timetable_common.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 4dd9262..17a99fe 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -68,7 +68,7 @@ def get_event(timetable, event, event_week, today): # On récupère le champ « remarque » if event.notes is not None: - course.notes = event.notes.text + course.notes = "\n".join(event.notes.find_all(text=True)) # On récupère le champ « nom » if event.resources.module is not None: diff --git a/templates/timetable_common.html b/templates/timetable_common.html index 62b1d71..533f6c9 100644 --- a/templates/timetable_common.html +++ b/templates/timetable_common.html @@ -6,7 +6,7 @@
  • {{ course }}{% if course.type %} ({{ course.type }}){% endif %}, de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}
    {{ course.rooms.all|format_rooms }}{% endif %}{% if course.notes %}
    - Remarques : {{ course.notes }}{% endif %} + Remarques : {{ course.notes|linebreaksbr }}{% endif %}
  • {% endfor %} {% empty %} -- cgit v1.2.1 From 32cfe5e60b60d7859f6e4fb3b4cba03d47293918 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 11 Jan 2018 20:50:35 +0100 Subject: Version 0.12.1 --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 03ae5ff..f75615a 100644 --- a/__init__.py +++ b/__init__.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . -VERSION = "0.12.0" +VERSION = "0.12.1" __version__ = VERSION default_app_config = "edt.apps.EdtConfig" -- cgit v1.2.1 From 8f9c5eb32c6cf27aa84d1cbf14b7aaaa00acc0b9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 16 Jan 2018 20:31:20 +0100 Subject: Utilisation des valeurs par défaut de reduce() --- models.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/models.py b/models.py index 363cf33..56c1845 100644 --- a/models.py +++ b/models.py @@ -93,12 +93,10 @@ class Timetable(SlugModel): class GroupManager(Manager): def get_parents(self, group): - groups_criteria = Q(subgroup="") - - if len(group.subgroup) != 0: - groups_criteria |= reduce(lambda x, y: x | y, - [Q(subgroup=group.subgroup[:i]) - for i in range(1, len(group.subgroup) + 1)]) + groups_criteria = reduce(lambda x, y: x | y, + [Q(subgroup=group.subgroup[:i]) + for i in range(1, len(group.subgroup) + 1)], + Q(subgroup="")) return self.get_queryset().filter(groups_criteria, Q(semester=None) | Q(semester=group.semester), -- cgit v1.2.1 From b55297c4d0de64501a6baf3b1f255210de492e97 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 12:16:09 +0100 Subject: Correction d’URLs --- urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urls.py b/urls.py index 4c4abe3..22011c4 100644 --- a/urls.py +++ b/urls.py @@ -20,8 +20,8 @@ urlpatterns = [ url(r"^$", views.index, name="index"), url(r"^pages/", include("django.contrib.flatpages.urls")), url(r"^salles/$", views.rooms, name="rooms"), - url(r"^salles/(?P[-\w]+)$", views.room_timetable, name="room-timetable"), - url(r"^salles/(?P[-\w]+)/(?P[0-9]{4})/(?P[0-4]?[0-9]|5[0-3])/$", views.room_timetable, name="room-timetable"), + url(r"^salles/(?P[-\w]+)/$", views.room_timetable, name="room-timetable"), + url(r"^salles/(?P[-\w]+)/(?P[0-9]{4})/(?P[0-4]?[0-9]|5[0-3])$", views.room_timetable, name="room-timetable"), url(r"^(?P[-\w]+)/$", views.mention_list, name="mentions"), url(r"^(?P[-\w]+)/(?P[-\w]+)/$", views.group_list, name="groups"), url(r"^(?P[-\w]+)/(?P[-\w]+)/(?P[-\w]+)/$", views.timetable, name="timetable"), -- cgit v1.2.1 From a2fcd4c7c42b6c02ff8ff7dac4aa23b3d17407de Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 12:34:11 +0100 Subject: Base du formulaire de QSJPS --- forms.py | 21 +++++++++++++++++++++ templates/form_qsjps.html | 10 ++++++++++ urls.py | 3 ++- views.py | 20 +++++++++++++++++++- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 forms.py create mode 100644 templates/form_qsjps.html diff --git a/forms.py b/forms.py new file mode 100644 index 0000000..34081c0 --- /dev/null +++ b/forms.py @@ -0,0 +1,21 @@ +# Copyright (C) 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 +# 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 . + +from django import forms + + +class QSJPSForm(forms.Form): + begin = forms.DateTimeField(label="Début") + end = forms.DateTimeField(label="Fin") diff --git a/templates/form_qsjps.html b/templates/form_qsjps.html new file mode 100644 index 0000000..2d97a89 --- /dev/null +++ b/templates/form_qsjps.html @@ -0,0 +1,10 @@ +{% extends "index.html" %} + +{% block title %}QSJPS – {% endblock %} + +{% block body %} +
    + {{ form }} + +
    +{% endblock %} diff --git a/urls.py b/urls.py index 22011c4..f24bbba 100644 --- a/urls.py +++ b/urls.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 @@ -20,6 +20,7 @@ urlpatterns = [ url(r"^$", views.index, name="index"), url(r"^pages/", include("django.contrib.flatpages.urls")), url(r"^salles/$", views.rooms, name="rooms"), + url(r"^salles/qsjps$", views.qsjps, name="qsjps"), url(r"^salles/(?P[-\w]+)/$", views.room_timetable, name="room-timetable"), url(r"^salles/(?P[-\w]+)/(?P[0-9]{4})/(?P[0-4]?[0-9]|5[0-3])$", views.room_timetable, name="room-timetable"), url(r"^(?P[-\w]+)/$", views.mention_list, name="mentions"), diff --git a/views.py b/views.py index 97a1d28..e8fc259 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 @@ -19,7 +19,9 @@ 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 .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 @@ -144,5 +146,21 @@ 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é + return None + else: + # Formulaire invalide, on le raffiche avec une erreur + return render(request, "form_qsjps.html", {"form": form}) + else: + # Sinon, affichage d’un formulaire vide + form = QSJPSForm() + return render(request, "form_qsjps.html", {"form": form}) + def ctx_processor(request): return {"celcatsanitizer_version": edt.VERSION} -- cgit v1.2.1 From d2a52ee6844d363ae020a49781b46402d7717a27 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 16:09:28 +0100 Subject: Ajout d’un champ jour au formulaire qsjps Valeurs par défaut des champs du formulaire Format de validation --- forms.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/forms.py b/forms.py index 34081c0..d886dbf 100644 --- a/forms.py +++ b/forms.py @@ -13,9 +13,25 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . +from datetime import timedelta from django import forms +from .utils import tz_now + class QSJPSForm(forms.Form): - begin = forms.DateTimeField(label="Début") - end = forms.DateTimeField(label="Fin") + day = forms.DateField(label="Jour") + + # Ces champs n’acceptent pas les secondes + begin = forms.TimeField(label="Heure de début", input_formats=("%H:%M",)) + end = forms.TimeField(label="Heure de fin", input_formats=("%H:%M",)) + + def __init__(self, *args, **kwargs): + super(QSJPSForm, self).__init__(*args, **kwargs) + + # On définit les valeurs par défaut de cette manière pour + # éviter les mauvaises surprises. On retire les secondes des + # heures de début et de fin. + self.fields["day"].initial = tz_now() + self.fields["begin"].initial = tz_now().strftime("%H:%M") + self.fields["end"].initial = (tz_now() + timedelta(hours=1)).strftime("%H:%M") -- cgit v1.2.1 From 97f62a20f1716a9915358959b2e25912e8b17a90 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 16:50:08 +0100 Subject: Utilisation des champs de formulaires date et time à la place de text --- forms.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/forms.py b/forms.py index d886dbf..0d823eb 100644 --- a/forms.py +++ b/forms.py @@ -14,17 +14,21 @@ # along with celcatsanitizer. If not, see . from datetime import timedelta + from django import forms +from django.forms.widgets import DateInput, TimeInput from .utils import tz_now class QSJPSForm(forms.Form): - day = forms.DateField(label="Jour") + day = forms.DateField(label="Jour", widget=DateInput(attrs={"type": "date"})) # Ces champs n’acceptent pas les secondes - begin = forms.TimeField(label="Heure de début", input_formats=("%H:%M",)) - end = forms.TimeField(label="Heure de fin", input_formats=("%H:%M",)) + begin = forms.TimeField(label="Heure de début", input_formats=("%H:%M",), + widget=TimeInput(attrs={"type": "time"})) + end = forms.TimeField(label="Heure de fin", input_formats=("%H:%M",), + widget=TimeInput(attrs={"type": "time"})) def __init__(self, *args, **kwargs): super(QSJPSForm, self).__init__(*args, **kwargs) -- cgit v1.2.1 From 8418b6b82e892a435b16f2be90ae94bb7961416b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 21:19:15 +0100 Subject: Validation du formulaire --- forms.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/forms.py b/forms.py index 0d823eb..fbd6d02 100644 --- a/forms.py +++ b/forms.py @@ -39,3 +39,17 @@ class QSJPSForm(forms.Form): self.fields["day"].initial = tz_now() self.fields["begin"].initial = tz_now().strftime("%H:%M") self.fields["end"].initial = (tz_now() + timedelta(hours=1)).strftime("%H:%M") + + def clean(self): + form_data = self.cleaned_data + + # On vérifie que les valeurs de début et de fin sont correctes + # (si ce n’est pas le cas, elles ne se trouvent pas dans le + # dictionnaire), et, le cas échéant, on vérifie que l’heure de + # début est strictement inférieure à l’heure de fin. + if "begin" in form_data and "end" in form_data and \ + form_data["begin"] >= form_data["end"]: + # Si l’heure de fin est plus petite ou égale, on affiche + # une erreur. + self._errors["end"].append("L’heure de début doit être supérieure à celle de fin.") + return form_data -- cgit v1.2.1 From dd1758f543d45376ea64d6846152fbf229b936f9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 19:13:33 +0100 Subject: Meilleur formatage du formulaire avec des tableaux --- static/celcatsanitizer/style.css | 6 ++++++ templates/form_qsjps.html | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/static/celcatsanitizer/style.css b/static/celcatsanitizer/style.css index 825d140..b9d5dd9 100644 --- a/static/celcatsanitizer/style.css +++ b/static/celcatsanitizer/style.css @@ -36,6 +36,12 @@ li.course { margin-bottom: 20px; } +th { + text-align: right; + font-weight: normal; + vertical-align: top; +} + @media print { body, .content { max-width: none; diff --git a/templates/form_qsjps.html b/templates/form_qsjps.html index 2d97a89..3a9bcf8 100644 --- a/templates/form_qsjps.html +++ b/templates/form_qsjps.html @@ -3,8 +3,16 @@ {% block title %}QSJPS – {% endblock %} {% block body %} +

    Trouver une salle

    - {{ form }} - + + {% for field in form.visible_fields %} + + + + + {% endfor %} + +
    {{ field.label_tag }}{{ field }}{% if field.errors %}
    {{ field.errors|join:" " }}{% endif %}
    {% endblock %} -- cgit v1.2.1 From 85112aafbcfe684b8b4d4fa2a47fd6cc2dcd2e25 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 17 Jan 2018 19:15:09 +0100 Subject: Format correct de la date --- forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms.py b/forms.py index fbd6d02..5e76bc1 100644 --- a/forms.py +++ b/forms.py @@ -36,7 +36,7 @@ class QSJPSForm(forms.Form): # On définit les valeurs par défaut de cette manière pour # éviter les mauvaises surprises. On retire les secondes des # heures de début et de fin. - self.fields["day"].initial = tz_now() + self.fields["day"].initial = tz_now().strftime("%Y-%m-%d") self.fields["begin"].initial = tz_now().strftime("%H:%M") self.fields["end"].initial = (tz_now() + timedelta(hours=1)).strftime("%H:%M") -- cgit v1.2.1 From b1e5d205e37a2c0a5d4f42afe0bdce9e0fbf97dd Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 12:06:45 +0100 Subject: Modification de la gestion du formulaire qsjps Renommage de la template contenant le formulaire --- templates/form_qsjps.html | 18 ------------------ templates/qsjps_form.html | 18 ++++++++++++++++++ views.py | 11 ++++++----- 3 files changed, 24 insertions(+), 23 deletions(-) delete mode 100644 templates/form_qsjps.html create mode 100644 templates/qsjps_form.html diff --git a/templates/form_qsjps.html b/templates/form_qsjps.html deleted file mode 100644 index 3a9bcf8..0000000 --- a/templates/form_qsjps.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "index.html" %} - -{% block title %}QSJPS – {% endblock %} - -{% block body %} -

    Trouver une salle

    -
    - - {% for field in form.visible_fields %} - - - - - {% endfor %} - -
    {{ field.label_tag }}{{ field }}{% if field.errors %}
    {{ field.errors|join:" " }}{% endif %}
    -
    -{% endblock %} diff --git a/templates/qsjps_form.html b/templates/qsjps_form.html new file mode 100644 index 0000000..bc5e363 --- /dev/null +++ b/templates/qsjps_form.html @@ -0,0 +1,18 @@ +{% extends "index.html" %} + +{% block title %}Trouver une salle – {% endblock %} + +{% block body %} +

    Trouver une salle

    +
    + + {% for field in form.visible_fields %} + + + + + {% endfor %} + +
    {{ field.label_tag }}{{ field }}{% if field.errors %}
    {{ field.errors|join:" " }}{% endif %}
    +
    +{% endblock %} diff --git a/views.py b/views.py index e8fc259..1c82b03 100644 --- a/views.py +++ b/views.py @@ -154,13 +154,14 @@ def qsjps(request): if form.is_valid(): # Formulaire validé return None - else: - # Formulaire invalide, on le raffiche avec une erreur - return render(request, "form_qsjps.html", {"form": form}) + # Si le formulaire est invalide, on ré-affiche le formulaire + # avec les erreurs else: - # Sinon, affichage d’un formulaire vide + # Si le formulaire n’a pas été soumis, on en instancie un + # nouveau form = QSJPSForm() - return render(request, "form_qsjps.html", {"form": form}) + + return render(request, "qsjps_form.html", {"form": form}) def ctx_processor(request): return {"celcatsanitizer_version": edt.VERSION} -- cgit v1.2.1 From 175e1c1139a77a02f3c52d77a4d2b118c2b2e578 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 12:17:08 +0100 Subject: Ajout d’une template pour lister les salles trouvées --- templates/qsjps.html | 14 ++++++++++++++ views.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 templates/qsjps.html diff --git a/templates/qsjps.html b/templates/qsjps.html new file mode 100644 index 0000000..73290de --- /dev/null +++ b/templates/qsjps.html @@ -0,0 +1,14 @@ +{% extends "index.html" %} + +{% block title %}Trouver une salle – {% endblock %} + +{% block body %} +

    Trouver une salle entre {{ form.begin.value }} et {{ form.end.value }}

    +
      + {% for room in rooms %} +
    • {{ room }}
    • + {% empty %} +

      Aucune salle trouvée

      + {% endfor %} +
    +{% endblock %} diff --git a/views.py b/views.py index 1c82b03..0231c9e 100644 --- a/views.py +++ b/views.py @@ -153,7 +153,7 @@ def qsjps(request): form = QSJPSForm(request.POST) if form.is_valid(): # Formulaire validé - return None + return render(request, "qsjps.html", {"rooms": [], "form": form}) # Si le formulaire est invalide, on ré-affiche le formulaire # avec les erreurs else: -- cgit v1.2.1 From 3a5e355f92881597c22a434ea300175a624586ea Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 13:26:37 +0100 Subject: Adaptation des tests à la nouvelle structure de la base de données --- tests.py | 86 ++++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/tests.py b/tests.py index 1425a84..ac6bdaf 100644 --- a/tests.py +++ b/tests.py @@ -14,7 +14,7 @@ # along with celcatsanitizer. If not, see . from django.test import TestCase -from .models import Course, Group, Timetable, Year +from .models import Course, Group, Source, Timetable, Year from .utils import tz_now @@ -25,24 +25,27 @@ class CourseTestCase(TestCase): self.year = Year(name="L2", slug="l2") self.year.save() - self.timetable = Timetable(year=self.year, name="Test timetable 2", url="http://example.org/", slug="test-timetable2") + source = Source(url="http://example.org/") + source.save() + + self.timetable = Timetable(year=self.year, name="Test timetable 2", source=source, slug="test-timetable2") self.timetable.save() - cma = Group.objects.create(celcat_name="L1 info s2 CMA", timetable=self.timetable) - tda2 = Group.objects.create(celcat_name="L1 info s2 TDA2", timetable=self.timetable) - self.tpa21 = Group.objects.create(celcat_name="L1 info s2 TPA21", timetable=self.timetable) + cma = Group.objects.create(celcat_name="L1 info s2 CMA", source=source) + tda2 = Group.objects.create(celcat_name="L1 info s2 TDA2", source=source) + self.tpa21 = Group.objects.create(celcat_name="L1 info s2 TPA21", source=source) - cmb = Group.objects.create(celcat_name="L1 info s2 CMB", timetable=self.timetable) - tdb2 = Group.objects.create(celcat_name="L1 info s2 TDB2", timetable=self.timetable) - self.tpb21 = Group.objects.create(celcat_name="L1 info s2 TPB21", timetable=self.timetable) + cmb = Group.objects.create(celcat_name="L1 info s2 CMB", source=source) + tdb2 = Group.objects.create(celcat_name="L1 info s2 TDB2", source=source) + self.tpb21 = Group.objects.create(celcat_name="L1 info s2 TPB21", source=source) for group in (cma, tda2, self.tpa21, cmb, tdb2, self.tpb21,): - course = Course.objects.create(name="{0} course".format(group.name), type="cours", timetable=self.timetable, begin=dt, end=dt) + course = Course.objects.create(name="{0} course".format(group.name), type="cours", source=source, begin=dt, end=dt) course.groups.add(group) def test_get_courses_for_group(self): - tpa21_courses = Course.objects.get_courses_for_group(self.tpa21) - tpb21_courses = Course.objects.get_courses_for_group(self.tpb21) + tpa21_courses = Course.objects.get_courses(self.tpa21) + tpb21_courses = Course.objects.get_courses(self.tpb21) tpa21_course_names = ["L1 info s2 CMA course", "L1 info s2 TDA2 course", "L1 info s2 TPA21 course"] tpb21_course_names = ["L1 info s2 CMB course", "L1 info s2 TDB2 course", "L1 info s2 TPB21 course"] @@ -58,29 +61,32 @@ class GroupTestCase(TestCase): self.year = Year(name="L1", slug="l1") self.year.save() - self.timetable = Timetable(year=self.year, name="Test timetable", url="http://example.com/", slug="test-timetable") + self.source = Source(url="http://example.org/") + self.source.save() + + self.timetable = Timetable(year=self.year, name="Test timetable", source=self.source, slug="test-timetable") self.timetable.save() - Group.objects.create(celcat_name="L1 info s2 CMA", timetable=self.timetable) - Group.objects.create(celcat_name="L1 info s2 TDA2", timetable=self.timetable) - Group.objects.create(celcat_name="L1 info s2 TPA21", timetable=self.timetable) + Group.objects.create(celcat_name="L1 info s2 CMA", source=self.source) + Group.objects.create(celcat_name="L1 info s2 TDA2", source=self.source) + Group.objects.create(celcat_name="L1 info s2 TPA21", source=self.source) - Group.objects.create(celcat_name="L1 info s2 CMB", timetable=self.timetable) - Group.objects.create(celcat_name="L1 info s2 TDB2", timetable=self.timetable) - Group.objects.create(celcat_name="L1 info s2 TPB21", timetable=self.timetable) + Group.objects.create(celcat_name="L1 info s2 CMB", source=self.source) + Group.objects.create(celcat_name="L1 info s2 TDB2", source=self.source) + Group.objects.create(celcat_name="L1 info s2 TPB21", source=self.source) - Group.objects.create(celcat_name="L1 info (toutes sections et semestres confondus)", timetable=self.timetable) + Group.objects.create(celcat_name="L1 info (toutes sections et semestres confondus)", source=self.source) def test_corresponds(self): - cma = Group.objects.get(celcat_name="L1 info s2 CMA", timetable=self.timetable) - tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2", timetable=self.timetable) - tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21", timetable=self.timetable) + cma = Group.objects.get(celcat_name="L1 info s2 CMA", source=self.source) + tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2", source=self.source) + tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21", source=self.source) - cmb = Group.objects.get(celcat_name="L1 info s2 CMB", timetable=self.timetable) - tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", timetable=self.timetable) - tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", timetable=self.timetable) + cmb = Group.objects.get(celcat_name="L1 info s2 CMB", source=self.source) + tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", source=self.source) + tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", source=self.source) - general = Group.objects.get(celcat_name="L1 info (toutes sections et semestres confondus)", timetable=self.timetable) + general = Group.objects.get(celcat_name="L1 info (toutes sections et semestres confondus)", source=self.source) self.assertFalse(cma.corresponds_to(*tda2.group_info)) self.assertFalse(cma.corresponds_to(*tpa21.group_info)) @@ -121,15 +127,15 @@ class GroupTestCase(TestCase): self.assertTrue(tpb21.corresponds_to(*general.group_info)) def test_get(self): - cma = Group.objects.get(name="L1 info s2 CMA", timetable=self.timetable) - tda2 = Group.objects.get(name="L1 info s2 TDA2", timetable=self.timetable) - tpa21 = Group.objects.get(name="L1 info s2 TPA21", timetable=self.timetable) + cma = Group.objects.get(name="L1 info s2 CMA", source=self.source) + tda2 = Group.objects.get(name="L1 info s2 TDA2", source=self.source) + tpa21 = Group.objects.get(name="L1 info s2 TPA21", source=self.source) - cmb = Group.objects.get(name="L1 info s2 CMB", timetable=self.timetable) - tdb2 = Group.objects.get(name="L1 info s2 TDB2", timetable=self.timetable) - tpb21 = Group.objects.get(name="L1 info s2 TPB21", timetable=self.timetable) + cmb = Group.objects.get(name="L1 info s2 CMB", source=self.source) + tdb2 = Group.objects.get(name="L1 info s2 TDB2", source=self.source) + tpb21 = Group.objects.get(name="L1 info s2 TPB21", source=self.source) - general = Group.objects.get(celcat_name="L1 info (toutes sections et semestres confondus)", timetable=self.timetable) + general = Group.objects.get(celcat_name="L1 info (toutes sections et semestres confondus)", source=self.source) self.assertEqual(cma.celcat_name, "L1 info s2 CMA") self.assertEqual(tda2.celcat_name, "L1 info s2 TDA2") @@ -142,15 +148,15 @@ class GroupTestCase(TestCase): self.assertEqual(general.celcat_name, "L1 info (toutes sections et semestres confondus)") def test_parse(self): - cma = Group.objects.get(celcat_name="L1 info s2 CMA", timetable=self.timetable) - tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2", timetable=self.timetable) - tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21", timetable=self.timetable) + cma = Group.objects.get(celcat_name="L1 info s2 CMA", source=self.source) + tda2 = Group.objects.get(celcat_name="L1 info s2 TDA2", source=self.source) + tpa21 = Group.objects.get(celcat_name="L1 info s2 TPA21", source=self.source) - cmb = Group.objects.get(celcat_name="L1 info s2 CMB", timetable=self.timetable) - tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", timetable=self.timetable) - tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", timetable=self.timetable) + cmb = Group.objects.get(celcat_name="L1 info s2 CMB", source=self.source) + tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", source=self.source) + tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", source=self.source) - general = Group.objects.get(celcat_name="L1 info (toutes sections et semestres confondus)", timetable=self.timetable) + general = Group.objects.get(celcat_name="L1 info (toutes sections et semestres confondus)", source=self.source) self.assertEqual(cma.group_info, ("L1 info", 2, "A")) self.assertEqual(tda2.group_info, ("L1 info", 2, "A2")) -- cgit v1.2.1 From bc7cac9459ac3ae55a31e9dd215cfc8f054e5fb1 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 13:35:29 +0100 Subject: get_courses() émet une exception si l’objet passé n’est ni un groupe ni une salle --- models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models.py b/models.py index 56c1845..5de1df2 100644 --- a/models.py +++ b/models.py @@ -179,6 +179,8 @@ class CourseManager(Manager): .prefetch_related("rooms") elif isinstance(obj, Room): qs = qs.filter(rooms__in=(obj,), **criteria) + else: + raise(TypeError, "obj must be a Group or a Room") return qs.order_by("begin") -- cgit v1.2.1 From 76fef8f3e0b3ad77f632a9e4d2c048607a1cf21b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 13:42:49 +0100 Subject: Mise à jour des copyrights --- management/commands/_private.py | 2 +- models.py | 2 +- tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 0a3d5fb..e78c3c2 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.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 diff --git a/models.py b/models.py index 5de1df2..4903b11 100644 --- a/models.py +++ b/models.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 diff --git a/tests.py b/tests.py index ac6bdaf..8a21a3f 100644 --- a/tests.py +++ b/tests.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 -- cgit v1.2.1 From 7a4bdfda6df3ca46f4805c145a42793cacc0809f Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 18:02:22 +0100 Subject: Meilleur rendu du formulaire sur appareils mobiles --- static/celcatsanitizer/style.css | 7 +++++++ templates/qsjps.html | 10 +++++----- templates/qsjps_form.html | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/static/celcatsanitizer/style.css b/static/celcatsanitizer/style.css index b9d5dd9..5d05f9e 100644 --- a/static/celcatsanitizer/style.css +++ b/static/celcatsanitizer/style.css @@ -42,6 +42,13 @@ th { vertical-align: top; } +@media screen and (max-width: 439px) { + th, td { + display: block; + text-align: left; + } +} + @media print { body, .content { max-width: none; diff --git a/templates/qsjps.html b/templates/qsjps.html index 73290de..bb07b60 100644 --- a/templates/qsjps.html +++ b/templates/qsjps.html @@ -5,10 +5,10 @@ {% block body %}

    Trouver une salle entre {{ form.begin.value }} et {{ form.end.value }}

      - {% for room in rooms %} -
    • {{ room }}
    • - {% empty %} -

      Aucune salle trouvée

      - {% endfor %} + {% for room in rooms %} +
    • {{ room }}
    • + {% empty %} +

      Aucune salle trouvée

      + {% endfor %}
    {% endblock %} diff --git a/templates/qsjps_form.html b/templates/qsjps_form.html index bc5e363..b68143f 100644 --- a/templates/qsjps_form.html +++ b/templates/qsjps_form.html @@ -9,7 +9,7 @@ {% for field in form.visible_fields %} {{ field.label_tag }} - {{ field }}{% if field.errors %}
    {{ field.errors|join:" " }}{% endif %} + {{ field }}{% if field.errors %}
    {{ field.errors|join:" " }}{% endif %} {% endfor %} -- cgit v1.2.1 From d7dfa0c001a9b84541e1545e45d0536a87d6880e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 19:20:21 +0100 Subject: Liens permettant de naviguer sur les pages des semaines suivantes et précédentes --- templates/timetable.html | 7 ++++++- views.py | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/templates/timetable.html b/templates/timetable.html index 8fafeed..cc90ed5 100644 --- a/templates/timetable.html +++ b/templates/timetable.html @@ -1,4 +1,5 @@ {% extends "index.html" %} +{% load dt_week %} {% block head %}{% if group_mode %} @@ -18,4 +19,8 @@ {% if last_update %}Dernière mise à jour le {{ last_update|date:"l j F o" }} à {{ last_update|date:"H:i" }}{% endif %}

    {% include "timetable_common.html" %} - {% if group_mode %}{% endif %}{% endblock %} + {% endblock %} diff --git a/views.py b/views.py index 0231c9e..10dcfa2 100644 --- a/views.py +++ b/views.py @@ -79,12 +79,19 @@ def timetable_common(request, obj, year=None, week=None, timetable=None): 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_week = getattr(Course.objects.get_courses(obj, begin__lt=start).last(), "begin", None) + next_week = getattr(Course.objects.get_courses(obj, begin__gte=end).first(), "begin", None) + last_update = courses.aggregate(Max("last_update"))["last_update__max"] 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}) -- cgit v1.2.1 From 2d5bb761ac83bfa4643006681587db2c5e11f60b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 20:27:21 +0100 Subject: Adaptation des liens vers les semaines suivantes et précédentes pour les emplois du temps des salles --- templates/timetable.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/timetable.html b/templates/timetable.html index cc90ed5..7811427 100644 --- a/templates/timetable.html +++ b/templates/timetable.html @@ -20,7 +20,7 @@

    {% include "timetable_common.html" %} {% endblock %} -- cgit v1.2.1 From 8b0626139036e50396f14cf9ae39b12e2540af85 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 20:48:58 +0100 Subject: Préchargement des groupes et des salles lorsqu’on demande la liste des salles. Réduit le nombre de requêtes à effectuer ainsi que le temps de traitement. --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index 4903b11..ae04fb8 100644 --- a/models.py +++ b/models.py @@ -178,7 +178,8 @@ class CourseManager(Manager): qs = qs.filter(groups__in=Group.objects.get_parents(obj), **criteria) \ .prefetch_related("rooms") elif isinstance(obj, Room): - qs = qs.filter(rooms__in=(obj,), **criteria) + qs = qs.filter(rooms__in=(obj,), **criteria) \ + .prefetch_related("groups", "rooms") else: raise(TypeError, "obj must be a Group or a Room") -- cgit v1.2.1 From 046e92137ace30cd645ba0f42421c283a60ba0cd Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 21:31:35 +0100 Subject: N’affiche plus la liste des groupes dans l’emploi du temps des salles si un cours n’en a pas, au lieu de se baser sur le nombre de salles d’un cours pour faire ce choix. Suppression du préchargement des salles lorsqu’on demande les cours d’une salle. Cela permet de réduire le nombre de requêtes effectuées. --- models.py | 3 ++- templates/timetable_common.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/models.py b/models.py index ae04fb8..15f378a 100644 --- a/models.py +++ b/models.py @@ -31,6 +31,7 @@ class SlugModel(models.Model): super(SlugModel, self).save() + class Meta: abstract = True @@ -179,7 +180,7 @@ class CourseManager(Manager): .prefetch_related("rooms") elif isinstance(obj, Room): qs = qs.filter(rooms__in=(obj,), **criteria) \ - .prefetch_related("groups", "rooms") + .prefetch_related("groups") else: raise(TypeError, "obj must be a Group or a Room") diff --git a/templates/timetable_common.html b/templates/timetable_common.html index 21300f1..6e59322 100644 --- a/templates/timetable_common.html +++ b/templates/timetable_common.html @@ -4,7 +4,7 @@

    {% filter title %}{{ day.0.begin|date:"l j F o" }}{% endfilter %} – de {{ day.0.begin|date:"H:i" }} à {% with day|last as last %}{{ last.end|date:"H:i" }}{% endwith %}

      {% for course in day %}
    • - {{ course }}{% if course.type %} ({{ course.type }}){% endif %}, de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}
      + {{ course }}{% if course.type %} ({{ course.type }}){% endif %}, de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if group_mode and course.rooms.all|length > 0 or not group_mode and course.groups.all|length > 0 %}
      {% if group_mode %}{{ course.rooms.all|format_rooms }}{% else %}{{ course.groups.all|join:", " }}{% endif %}{% endif %}{% if course.notes %}
      Remarques : {{ course.notes|linebreaksbr }}{% endif %}
    • {% endfor %} -- cgit v1.2.1 From a0101b9a7566b5296c3490ce3592984976553807 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 20 Jan 2018 22:28:21 +0100 Subject: On cache les groupes qui n’ont plus de cours La requête est assez longue à s’effectuer sur SQLite, mais pas sur PostgreSQL --- models.py | 9 ++++++++- views.py | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/models.py b/models.py index 15f378a..c122bfd 100644 --- a/models.py +++ b/models.py @@ -16,7 +16,7 @@ from functools import reduce from django.db import models -from django.db.models import Manager, Q +from django.db.models import Count, Manager, OuterRef, Q, Subquery from django.db.models.functions import ExtractWeek, ExtractYear from django.utils import timezone from django.utils.text import slugify @@ -104,6 +104,13 @@ class GroupManager(Manager): mention=group.mention, source=group.source) + def get_relevant_groups(self, start, **criteria): + courses = Course.objects.filter(groups=OuterRef("pk"), begin__gte=start) \ + .only("pk")[:1] + return self.get_queryset().annotate(c=Subquery(courses, + output_field=models.IntegerField())) \ + .filter(c__isnull=False, **criteria).order_by("name") + class Group(SlugModel): objects = GroupManager() diff --git a/views.py b/views.py index 10dcfa2..9b53f3a 100644 --- a/views.py +++ b/views.py @@ -15,7 +15,7 @@ import datetime -from django.db.models import Count, Max +from django.db.models import Max from django.db.models.functions import ExtractWeek, ExtractYear, Length from django.http import Http404 from django.shortcuts import get_object_or_404, render @@ -39,11 +39,11 @@ def mention_list(request, year_slug): 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(source=timetable.source, hidden=False).order_by("name") 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) for group in groups: -- cgit v1.2.1 From 6f4822291d89dadcb6bae94c897e89e309218e7e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 21 Jan 2018 19:35:25 +0100 Subject: Correction d’une erreur d’importation --- views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views.py b/views.py index 9b53f3a..842a02e 100644 --- a/views.py +++ b/views.py @@ -15,7 +15,7 @@ import datetime -from django.db.models import Max +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 -- cgit v1.2.1 From 8ad4a03f9d6ab6d218ddcbe277ca3766f05f6d79 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 20 Jan 2018 17:24:18 +0100 Subject: Légère optimisation de la page des groupes --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index c122bfd..b809a53 100644 --- a/models.py +++ b/models.py @@ -200,7 +200,8 @@ class CourseManager(Manager): .annotate(year=ExtractYear("begin"), week=ExtractWeek("begin")) \ .values("groups__mention", "groups__semester", - "groups__subgroup", "year", "week") + "groups__subgroup", "year", "week") \ + .annotate(c=Count("*")) class Course(models.Model): -- cgit v1.2.1 From e815411b1d10ba0c94fa19a942033957a341f3fc Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 21 Jan 2018 19:29:45 +0100 Subject: Améliorations de navigation Lien pour retourner à la liste des groupes sur la page des emplois du temps Lien pour retourner à la liste des années sur la page de liste des mentions Lien pour retourner à la liste des mentions sur la page des groupes Lien pour retourner à la liste des années sur la liste des salles Lien pour accéder à la liste des salles sur la page principale Lien pour retourner à la liste des salles sur le formulaire QSJPS --- templates/group_list.html | 1 + templates/index.html | 1 + templates/qsjps_form.html | 1 + templates/timetable.html | 1 + 4 files changed, 4 insertions(+) diff --git a/templates/group_list.html b/templates/group_list.html index 7fe1fe8..5f53cb4 100644 --- a/templates/group_list.html +++ b/templates/group_list.html @@ -10,4 +10,5 @@
    • {{ group }} — {% for week in group.weeks %}{{ week|dt_prettyprint }} {% if not forloop.last %}– {% endif %}{% empty %}aucun cours dans le mois à venir{% endfor %}
    • {% endfor %}
    + {% if timetable %}Retour à la liste des mentions{% else %}Retour à la liste des années{% endif %} {% endblock %} diff --git a/templates/index.html b/templates/index.html index 71665bc..5779b3e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -21,6 +21,7 @@

    Aucun emploi du temps à afficher

    {% endfor %} + {% if year %}Retour à la liste des années{% else %}Emploi du temps des salles{% endif %} {% endblock %}