From c5d409e7c38cd5dd5686ce2311928587796349f9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 12 Sep 2017 21:32:35 +0200 Subject: Lecture du contenu de la réponse avec r.content et non r.text pour limiter les problèmes --- management/commands/_private.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 17896c4..2b57599 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -159,5 +159,5 @@ def get_xml(url): r = requests.get(url) r.encoding = "utf8" - soup = BeautifulSoup(r.text, "html.parser") + soup = BeautifulSoup(r.content, "html.parser") return soup -- cgit v1.2.1 From c0f19d41c8dfb6d499defcfc63c273bbc8918584 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 9 Sep 2017 19:37:12 +0200 Subject: Génération automatique d’un slug pour les modèles Year et Timetable --- admin.py | 1 + models.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/admin.py b/admin.py index 2fc0e78..48930e0 100644 --- a/admin.py +++ b/admin.py @@ -21,6 +21,7 @@ from .models import Timetable, LastUpdate, Group, Room, Course, Year class YearAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} list_display = ("name",) + ordering = ("name",) @admin.register(Timetable) diff --git a/models.py b/models.py index 6bb9733..c263510 100644 --- a/models.py +++ b/models.py @@ -21,7 +21,19 @@ from django.utils.text import slugify from .utils import parse_group -class Year(models.Model): +class SlugModel(models.Model): + def save(self): + if not self.slug: + self.slug = slugify(self.name) + + super(SlugModel, self).save() + + + class Meta: + abstract = True + + +class Year(SlugModel): name = models.CharField(max_length=16, verbose_name="année") slug = models.SlugField(max_length=16, unique=True, default="") @@ -34,7 +46,7 @@ class Year(models.Model): verbose_name_plural = "années" -class Timetable(models.Model): +class Timetable(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") -- cgit v1.2.1 From 445bfdcde5309645ac4d6f5c1d565530e6dbfeed Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 9 Sep 2017 21:07:09 +0200 Subject: Tri du modèle Timetable --- admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/admin.py b/admin.py index 48930e0..fff2c19 100644 --- a/admin.py +++ b/admin.py @@ -29,6 +29,7 @@ class TimetableAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} list_display = ("name", "year", "url",) list_filter = ("year__name",) + ordering = ("year", "name",) @admin.register(LastUpdate) -- cgit v1.2.1 From 32bd236f2c53bc8dfd515018a7ae0ec06f65c115 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 10 Sep 2017 11:54:38 +0200 Subject: La consolidation a lieu pour le parent d’un groupe mais aussi ses enfants --- management/commands/_private.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 2b57599..c140f51 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -45,19 +45,34 @@ def add_time(date, time): def consolidate_group(group): group_content_key = ("mention", "subgroup", "td", "tp") group_content_list = group.group_info[1:] - group_content = dict(zip(group_content_key, group_content_list)) - for i in range(len(group_content_list))[::-1]: - del group_content[group_content_key[i]] - group_content[group_content_key[i] + "__isnull"] = True + if group.subgroup is not None: + group_content = dict(zip(group_content_key, group_content_list)) - if group_content_list[i] is not None: - break + for i in range(len(group_content_list))[::-1]: + del group_content[group_content_key[i]] + group_content[group_content_key[i] + "__isnull"] = True - if "subgroup" in group_content: - group.parent = Group.objects.filter(**group_content).first() + if group_content_list[i] is not None: + break + + group.parent = Group.objects.filter(timetable=group.timetable, + **group_content).first() group.save() + if group.tp is None: + group_content = dict(zip(group_content_key, group_content_list)) + last_is_none = False + + for i, key in enumerate(group_content_key): + if group_content_list[i] is None or last_is_none: + del group_content[key] + group_content[key + "__isnull"] = last_is_none + last_is_none = True + + Group.objects.filter(timetable=group.timetable, parent__isnull=True, + **group_content).update(parent=group) + def consolidate_groups(groups): for group in groups: if group.parent == None: -- cgit v1.2.1 From 5a391f46fafee98b653f9c64219ad17844f7e340 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 10 Sep 2017 11:59:32 +0200 Subject: Filtrage des cours et des groupes par l’emploi du temps et non par son nom --- admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/admin.py b/admin.py index fff2c19..16e34f3 100644 --- a/admin.py +++ b/admin.py @@ -44,7 +44,8 @@ class GroupAdmin(admin.ModelAdmin): (None, {"fields": ("name", "celcat_name", "timetable",)}), ("Groupes", {"fields": ("mention", "subgroup", "td", "tp", "parent",)}),) list_display = ("name", "timetable",) - list_filter = ("timetable__name",) + list_filter = ("timetable",) + ordering = ("timetable",) readonly_fields = ("celcat_name", "mention", "subgroup", "td", "tp",) @@ -60,5 +61,5 @@ class CourseAdmin(admin.ModelAdmin): ("Horaires", {"fields": ("begin", "end",)}), ("Remarques", {"fields": ("notes",)}),) list_display = ("name", "type", "timetable", "begin", "end",) - list_filter = ("type", "timetable__name", "groups",) + list_filter = ("type", "timetable", "groups",) ordering = ("begin",) -- cgit v1.2.1 From 5490a366c2acba45cc617a825904ba95eeb6b374 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 12 Sep 2017 21:42:52 +0200 Subject: Ajout d’une page contact. L’adresse email est brouillée. --- templates/contact.html | 9 +++++++++ templates/index.html | 2 +- templatetags/email.py | 24 ++++++++++++++++++++++++ urls.py | 1 + views.py | 4 ++++ 5 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 templates/contact.html create mode 100644 templatetags/email.py diff --git a/templates/contact.html b/templates/contact.html new file mode 100644 index 0000000..1359a16 --- /dev/null +++ b/templates/contact.html @@ -0,0 +1,9 @@ +{% extends "index.html" %} +{% load email %} + +{% block title %}Contacter – {% endblock %} + +{% block body %} +

Contacter

+

Pour contacter l’administrateur du service, envoyez un mail à l’adresse suivante :
{{ email|format_email }}.

+{% endblock %} diff --git a/templates/index.html b/templates/index.html index f4b105c..a685031 100644 --- a/templates/index.html +++ b/templates/index.html @@ -23,7 +23,7 @@ {% endblock %} diff --git a/templatetags/email.py b/templatetags/email.py new file mode 100644 index 0000000..68dbd84 --- /dev/null +++ b/templatetags/email.py @@ -0,0 +1,24 @@ +# Copyright (C) 2017 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 template + +register = template.Library() + +@register.filter +def format_email(address): + return address.replace("+", " [plus] ") \ + .replace("@", " [arobase] ") \ + .replace(".", " [point] ") diff --git a/urls.py b/urls.py index 2e731b5..58cf019 100644 --- a/urls.py +++ b/urls.py @@ -18,6 +18,7 @@ from . import feeds, views urlpatterns = [ url(r"^$", views.index, name="index"), + url(r"^contact$", views.contact, name="contact"), 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 f445725..302b375 100644 --- a/views.py +++ b/views.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . +from django.conf import settings from django.shortcuts import get_object_or_404, render from .models import Timetable, LastUpdate, Group, Course, Year @@ -65,3 +66,6 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No grouped_courses = group_courses(courses) return render(request, "timetable.html", {"group": group, "courses": grouped_courses, "last_update": last_update.date, "year": year, "week": int(week)}) + +def contact(request): + return render(request, "contact.html", {"email": settings.ADMINS[0][1]}) -- cgit v1.2.1 From ddbaba1d2cc65215a8922f123ba580064b2cbe93 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 10 Sep 2017 12:49:09 +0200 Subject: Augmentation de la taille maximale des mentions --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models.py b/models.py index c263510..f223317 100644 --- a/models.py +++ b/models.py @@ -92,7 +92,7 @@ class Group(models.Model): celcat_name = models.CharField(max_length=255, verbose_name="nom dans Celcat") timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, verbose_name="emploi du temps") - mention = models.CharField(max_length=32) + mention = models.CharField(max_length=128) subgroup = models.CharField(max_length=1, verbose_name="sous-groupe", null=True) td = models.IntegerField(verbose_name="groupe de TD", null=True) tp = models.IntegerField(verbose_name="groupe de TP", null=True) -- cgit v1.2.1 From bd26d6ddac7ffb47f1d6dcaedc5edf8091bcb312 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 17 Sep 2017 18:24:00 +0200 Subject: git ignore les ctags et la configuration locale de GNU Emacs --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9377785..0975a47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ __pycache__/ migrations/ +.dir-locals.el +TAGS -- cgit v1.2.1 From 2a4033d88be2d1ccd66c1cf788f6dd3e4a468138 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 23 Sep 2017 13:02:59 +0200 Subject: Changement de la regex des groupes * Suppression de groupes inutiles * Validation du nom du groupe même si il y a un commentaire après le numéro --- utils.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/utils.py b/utils.py index d343def..3ded68e 100644 --- a/utils.py +++ b/utils.py @@ -48,26 +48,29 @@ def group_courses(courses): def parse_group(name): # Explication de la regex # - # ^([\w ]+?)(\s*(((CM)(\w))|((TD)(\w)(\d))|((TP)(\w)(\d)(\d))))?$ - # ^ début de la ligne - # ([\w ]+?) correspond à au moins un caractère - # (\s* zéro, un ou plusieurs espaces - # (((CM)(\w))| correspond à CM suivi d'une lettre ou… - # ((TD)(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… - # ((TP)(\w)(\d)(\d))) … à TP suivi d’une lettre et de deux chiffres - # )? groupe optionel - # $ fin de la ligne - group_regex = re.compile("^([\w ]+?)(\s*(((CM)(\w))|((TD)(\w)(\d))|((TP)(\w)(\d)(\d))))?$") + # ^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s\(.+\))?$ + # ^ début de la ligne + # ([\w ]+?) correspond à au moins un caractère + # \s+ un ou plusieurs espaces + # ((CM(\w))| correspond à CM suivi d'une lettre ou… + # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… + # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres + # )? groupe optionnel + # (\s un espace + # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses + # ? groupe optionnel + # $ fin de la ligne + group_regex = re.compile("^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s\(.+\))?$") search = group_regex.search(name) if search is None: return name, None, None, None - parts = search.groups(0) - if parts[1] == 0: + parts = search.groups() + if parts[1] is None: # Pas de groupe précis indiqué return parts[0], None, None, None - elif parts[4] == "CM": - return parts[0], parts[5], None, None - elif parts[7] == "TD": - return parts[0], parts[8], parts[9], None - elif parts[11] == "TP": - return parts[0], parts[12], parts[13], parts[14] + elif parts[2] is not None: # Groupe de CM + return parts[0], parts[3], None, None + elif parts[4] is not None: # Groupe de TD + return parts[0], parts[5], parts[6], None + elif parts[7] is not None: # Groupe de TP + return parts[0], parts[8], parts[9], parts[10] -- cgit v1.2.1 From f6da809651874179c6deb2e008e7a294375d2264 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 23 Sep 2017 14:21:45 +0200 Subject: Changement du readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc50388..82e6053 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ celcatsanitizer est un système qui permet de récupérer des emplois du temps Celcat au format XML pour les afficher correctement. ## Pourquoi ? -Parce que j'en avait ma claque de consulter un emploi du temps mal formaté. Parce que j'en avait ma claque de voir mon Firefox se figer pour pouvoir vérifier mes horaires de la journée. Parce que j'en avait ma claque d'avoir de devoir retrouver une pépite d'information dans un océan de texte. (et parce que j'avais un peu trop de temps libre aussi) +Parce que les emplois du temps Celcat sont peu lisibles et peuvent facilement faire planter un navigateur, à cause du surplus d’informations affichées. ## Comment faire tourner celcatsanitizer chez moi ? celcatsanitizer est écrit en Python 3. Il dépend des bibliothèques suivantes : -- cgit v1.2.1 From 6f026e4f0bca3cc3bb137bc26967283510c135de Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 14:37:09 +0200 Subject: Ajout des propriétés uid et prodid dans les iCalendar --- feeds.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feeds.py b/feeds.py index 2dd2479..a75e60b 100644 --- a/feeds.py +++ b/feeds.py @@ -27,7 +27,7 @@ from .models import Course, Group, LastUpdate from .templatetags.rooms import format_rooms from .utils import get_current_or_next_week, get_week, group_courses -ICAL_NAMES = ["summary", "description", "location", "start", "dtstart", "dtend"] +ICAL_NAMES = ["uid", "summary", "description", "location", "start", "dtstart", "dtend"] class IcalFeedGenerator(SyndicationFeed): @@ -35,6 +35,7 @@ class IcalFeedGenerator(SyndicationFeed): def write(self, outfile, encoding): calendar = Calendar() + calendar.add("prodid", "-//celcatsanitizer//NONSGML v1.0//EN") calendar.add("version", "2.0") self.write_events(calendar) @@ -71,7 +72,8 @@ class IcalFeed(Feed): return Course.objects.get_courses_for_group(obj).order_by("begin") def item_extra_kwargs(self, item): - return {"dtstart": item.begin, + return {"uid": "{0}@celcatsanitizer".format(item.id), + "dtstart": item.begin, "dtend": item.end, "summary": item.name, "location": format_rooms(item.rooms.all())} -- cgit v1.2.1 From 0949b09c0040a66ffd1869da23a9425da891d56f Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 15:01:59 +0200 Subject: Ajout du champ dtstamp aux fichiers iCalendar. Suppression du order_by("begin") redondant lors de la récupération des cours --- feeds.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feeds.py b/feeds.py index a75e60b..6dfb851 100644 --- a/feeds.py +++ b/feeds.py @@ -17,6 +17,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.contrib.syndication.views import Feed from django.db.models import Q +from django.db.models.functions import ExtractWeek, ExtractYear from django.template import loader from django.urls import reverse from django.utils.feedgenerator import Atom1Feed, SyndicationFeed @@ -27,7 +28,7 @@ from .models import Course, Group, LastUpdate from .templatetags.rooms import format_rooms from .utils import get_current_or_next_week, get_week, group_courses -ICAL_NAMES = ["uid", "summary", "description", "location", "start", "dtstart", "dtend"] +ICAL_NAMES = ["uid", "summary", "description", "location", "start", "dtstart", "dtend", "dtstamp"] class IcalFeedGenerator(SyndicationFeed): @@ -69,12 +70,13 @@ class IcalFeed(Feed): return "" def items(self, obj): - return Course.objects.get_courses_for_group(obj).order_by("begin") + return Course.objects.get_courses_for_group(obj).annotate(year=ExtractYear("begin"), week=ExtractWeek("begin")) def item_extra_kwargs(self, item): return {"uid": "{0}@celcatsanitizer".format(item.id), "dtstart": item.begin, "dtend": item.end, + "dtstamp": LastUpdate.objects.get(timetable=item.timetable, year=item.year, week=item.week).updated_at, "summary": item.name, "location": format_rooms(item.rooms.all())} -- cgit v1.2.1 From 77748ec01d1ef59d8436597a9b52675b309e1c4d Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 15:50:58 +0200 Subject: Ajout du champ last_update dans le modèle des cours --- admin.py | 2 +- models.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/admin.py b/admin.py index 16e34f3..4a238be 100644 --- a/admin.py +++ b/admin.py @@ -57,7 +57,7 @@ class RoomAdmin(admin.ModelAdmin): @admin.register(Course) class CourseAdmin(admin.ModelAdmin): fieldsets = ( - (None, {"fields": ("name", "type", "timetable", "groups", "rooms",)}), + (None, {"fields": ("name", "type", "timetable", "groups", "rooms", "last_update",)}), ("Horaires", {"fields": ("begin", "end",)}), ("Remarques", {"fields": ("notes",)}),) list_display = ("name", "type", "timetable", "begin", "end",) diff --git a/models.py b/models.py index f223317..4a25b01 100644 --- a/models.py +++ b/models.py @@ -161,6 +161,8 @@ class Course(models.Model): begin = models.DateTimeField(verbose_name="début du cours", db_index=True) end = models.DateTimeField(verbose_name="fin du cours") + last_update = models.DateTimeField(verbose_name="dernière mise à jour") + def __str__(self): return self.name -- cgit v1.2.1 From e30b0c673cae607175c5bae6df6e87d8b100bdf3 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 17:16:10 +0200 Subject: Suppression du modèle LastUpdate, utilisation du champ last_update à la place. --- admin.py | 8 +------- feeds.py | 16 +++++++++------- management/commands/cleancourses.py | 4 +--- management/commands/timetables.py | 18 +++++++----------- models.py | 18 ------------------ views.py | 12 ++++++++---- 6 files changed, 26 insertions(+), 50 deletions(-) diff --git a/admin.py b/admin.py index 4a238be..8a409e3 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, LastUpdate, Group, Room, Course, Year +from .models import Timetable, Group, Room, Course, Year @admin.register(Year) @@ -32,12 +32,6 @@ class TimetableAdmin(admin.ModelAdmin): ordering = ("year", "name",) -@admin.register(LastUpdate) -class LastUpdateAdmin(admin.ModelAdmin): - list_display = ("timetable", "week", "year", "date", "updated_at",) - list_filter = ("timetable__name",) - - @admin.register(Group) class GroupAdmin(admin.ModelAdmin): fieldsets = ( diff --git a/feeds.py b/feeds.py index 6dfb851..d7f3ae3 100644 --- a/feeds.py +++ b/feeds.py @@ -16,7 +16,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.contrib.syndication.views import Feed -from django.db.models import Q +from django.db.models import Count, Max, Q from django.db.models.functions import ExtractWeek, ExtractYear from django.template import loader from django.urls import reverse @@ -24,7 +24,7 @@ from django.utils.feedgenerator import Atom1Feed, SyndicationFeed from icalendar import Calendar, Event -from .models import Course, Group, LastUpdate +from .models import Course, Group from .templatetags.rooms import format_rooms from .utils import get_current_or_next_week, get_week, group_courses @@ -70,13 +70,13 @@ class IcalFeed(Feed): return "" def items(self, obj): - return Course.objects.get_courses_for_group(obj).annotate(year=ExtractYear("begin"), week=ExtractWeek("begin")) + return Course.objects.get_courses_for_group(obj) def item_extra_kwargs(self, item): return {"uid": "{0}@celcatsanitizer".format(item.id), "dtstart": item.begin, "dtend": item.end, - "dtstamp": LastUpdate.objects.get(timetable=item.timetable, year=item.year, week=item.week).updated_at, + "dtstamp": item.last_update, "summary": item.name, "location": format_rooms(item.rooms.all())} @@ -84,12 +84,14 @@ class IcalFeed(Feed): class RSSFeed(Feed): def get_object(self, request, year_slug, timetable_slug, group_slug): year, week = get_current_or_next_week() + begin, end = get_week(year, week) + try: group = Group.objects.get(timetable__year__slug=year_slug, timetable__slug=timetable_slug, slug=group_slug) - updates = LastUpdate.objects.filter(Q(year=year, week__lte=week) | Q(year__lt=year), timetable__year__slug=year_slug, timetable__slug=timetable_slug).order_by("-year", "-week")[:5] except: raise ObjectDoesNotExist else: + updates = Course.objects.get_courses_for_group(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 def link(self, obj): @@ -115,9 +117,9 @@ class RSSFeed(Feed): group = obj[0] for update in obj[1]: - start, end = get_week(update.year, update.week) + 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), "last_update": update, "year": update.year, "week": update.week} + context = {"group": group, "courses": group_courses(courses), "last_update": update["last_update__max"], "year": update["year"], "week": update["week"]} update.group = group update.description = template.render(context) diff --git a/management/commands/cleancourses.py b/management/commands/cleancourses.py index ca2ef94..f6041ef 100644 --- a/management/commands/cleancourses.py +++ b/management/commands/cleancourses.py @@ -15,7 +15,7 @@ from django.core.management.base import BaseCommand from django.db import transaction -from edt.models import Course, Group, LastUpdate +from edt.models import Course, Group class Command(BaseCommand): @@ -29,10 +29,8 @@ class Command(BaseCommand): if options["timetable"] is None: Course.objects.all().delete() Group.objects.all().delete() - LastUpdate.objects.all().delete() else: Course.objects.filter(timetable__id__in=options["timetable"]).delete() Group.objects.filter(timetable__id__in=options["timetable"]).delete() - LastUpdate.objects.filter(timetable__id__in=options["timetable"]).delete() self.stdout.write(self.style.SUCCESS("Done.")) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index c82b0e4..45a57ee 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -17,20 +17,19 @@ from django.core.management.base import BaseCommand from django.db import transaction from django.utils import timezone -from edt.models import Timetable, LastUpdate, Course +from edt.models import Timetable, Course +from edt.utils import get_week from ._private import delete_courses_in_week, get_events, get_update_date, get_weeks, get_xml import datetime @transaction.atomic def process_timetable_week(timetable, year, week, soup, weeks_in_soup): - last_update_date = None + begin, end = get_weeks(year, week) + + last_update_date = Course.objects.filter(begin__gte=start, begin__lt=end).aggregate(Max("last_update"))["last_update__max"] new_update_date = get_update_date(soup) - try: - last_update = LastUpdate.objects.get(timetable=timetable, year=year, week=week) - last_update_date = last_update.updated_at - except: - last_update = LastUpdate(timetable=timetable, year=year, week=week) + updated_at = timezone.make_aware(datetime.datetime.now()) if last_update_date is not None and new_update_date is not None and \ last_update_date >= new_update_date: @@ -43,6 +42,7 @@ def process_timetable_week(timetable, year, week, soup, weeks_in_soup): course.name = name course.type = type_ course.notes = notes + course.updated_at = updated_at course.groups.add(*groups) if rooms is not None: @@ -50,10 +50,6 @@ def process_timetable_week(timetable, year, week, soup, weeks_in_soup): course.save() - last_update.date = timezone.make_aware(datetime.datetime.now()) - last_update.updated_at = new_update_date - last_update.save() - def process_timetable(timetable, year, weeks): soup = get_xml(timetable.url) weeks_in_soup = get_weeks(soup) diff --git a/models.py b/models.py index 4a25b01..5128c8a 100644 --- a/models.py +++ b/models.py @@ -62,24 +62,6 @@ class Timetable(SlugModel): verbose_name_plural = "emplois du temps" -class LastUpdate(models.Model): - timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, verbose_name="emploi du temps") - week = models.IntegerField(verbose_name="semaine") - year = models.IntegerField(verbose_name="année") - date = models.DateTimeField(verbose_name="date de mise à jour") - - updated_at = models.DateTimeField(verbose_name="date de publication", null=True) - - def __str__(self): - return "{0}, semaine {1} de {2}".format(self.timetable, self.week, self.year) - - - class Meta: - unique_together = ("timetable", "week", "year",) - verbose_name = "dernière mise à jour" - verbose_name_plural = "dernières mises à jour" - - class GroupManager(Manager): def get_relevant_groups(self, *args, **criteria): return self.get_queryset().filter(*args, **criteria).annotate(children_count=Count("children")).filter(children_count=0) diff --git a/views.py b/views.py index 302b375..eb6a7ac 100644 --- a/views.py +++ b/views.py @@ -14,9 +14,11 @@ # along with celcatsanitizer. If not, see . from django.conf import settings +from django.db.models import Max +from django.http import Http404 from django.shortcuts import get_object_or_404, render -from .models import Timetable, LastUpdate, Group, Course, Year +from .models import Timetable, Group, Course, Year from .utils import get_current_week, get_week, group_courses def index(request): @@ -60,12 +62,14 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) group = get_object_or_404(Group, slug=group_slug, timetable=timetable) - last_update = get_object_or_404(LastUpdate, timetable=timetable, week=week, year=year) - courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) + + courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end).annotate(Max("last_update")) + if courses.count == 0: + raise Http404 grouped_courses = group_courses(courses) - return render(request, "timetable.html", {"group": group, "courses": grouped_courses, "last_update": last_update.date, "year": year, "week": int(week)}) + return render(request, "timetable.html", {"group": group, "courses": grouped_courses, "last_update": courses.first().last_update__max, "year": year, "week": int(week)}) def contact(request): return render(request, "contact.html", {"email": settings.ADMINS[0][1]}) -- cgit v1.2.1 From dc5618c3b3af18a5f6e1d50844097c745317125a Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 17:36:40 +0200 Subject: Ajout de la date et de l’heure automatiquement lors de la création d’un cours --- management/commands/timetables.py | 5 ++--- models.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 45a57ee..0716a91 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -15,6 +15,7 @@ from django.core.management.base import BaseCommand from django.db import transaction +from django.db.models import Max from django.utils import timezone from edt.models import Timetable, Course @@ -27,9 +28,8 @@ import datetime def process_timetable_week(timetable, year, week, soup, weeks_in_soup): begin, end = get_weeks(year, week) - last_update_date = Course.objects.filter(begin__gte=start, begin__lt=end).aggregate(Max("last_update"))["last_update__max"] + last_update_date = Course.objects.filter(begin__gte=begin, begin__lt=end).aggregate(Max("last_update"))["last_update__max"] new_update_date = get_update_date(soup) - updated_at = timezone.make_aware(datetime.datetime.now()) if last_update_date is not None and new_update_date is not None and \ last_update_date >= new_update_date: @@ -42,7 +42,6 @@ def process_timetable_week(timetable, year, week, soup, weeks_in_soup): course.name = name course.type = type_ course.notes = notes - course.updated_at = updated_at course.groups.add(*groups) if rooms is not None: diff --git a/models.py b/models.py index 5128c8a..e284c28 100644 --- a/models.py +++ b/models.py @@ -143,7 +143,7 @@ class Course(models.Model): begin = models.DateTimeField(verbose_name="début du cours", db_index=True) end = models.DateTimeField(verbose_name="fin du cours") - last_update = models.DateTimeField(verbose_name="dernière mise à jour") + last_update = models.DateTimeField(verbose_name="dernière mise à jour", auto_now_add=True) def __str__(self): return self.name -- cgit v1.2.1 From a4fbe45dcdc89dbcfe50afd5058b0ecf09ed7642 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 17:37:21 +0200 Subject: Suppression des import inutilisés --- feeds.py | 2 +- management/commands/timetables.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/feeds.py b/feeds.py index d7f3ae3..7e8cd80 100644 --- a/feeds.py +++ b/feeds.py @@ -16,7 +16,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from django.contrib.syndication.views import Feed -from django.db.models import Count, Max, Q +from django.db.models import Count, Max from django.db.models.functions import ExtractWeek, ExtractYear from django.template import loader from django.urls import reverse diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 0716a91..efdd611 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -19,7 +19,6 @@ from django.db.models import Max from django.utils import timezone from edt.models import Timetable, Course -from edt.utils import get_week from ._private import delete_courses_in_week, get_events, get_update_date, get_weeks, get_xml import datetime -- cgit v1.2.1 From 020d204d393ebf8c7f2bc9f6848cd4892b278f70 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 18:13:44 +0200 Subject: Récupération de la dernière mise à jour en filtrant par emploi du temps Changement de la valeur par défaut de last_update, qui posait problème get_week, pas get_weeks --- management/commands/timetables.py | 5 +++-- models.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index efdd611..e136f53 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -19,15 +19,16 @@ from django.db.models import Max from django.utils import timezone from edt.models import Timetable, Course +from edt.utils import get_week from ._private import delete_courses_in_week, get_events, get_update_date, get_weeks, get_xml import datetime @transaction.atomic def process_timetable_week(timetable, year, week, soup, weeks_in_soup): - begin, end = get_weeks(year, week) + begin, end = get_week(year, week) - last_update_date = Course.objects.filter(begin__gte=begin, begin__lt=end).aggregate(Max("last_update"))["last_update__max"] + last_update_date = Course.objects.filter(timetable=timetable, begin__gte=begin, begin__lt=end).aggregate(Max("last_update"))["last_update__max"] new_update_date = get_update_date(soup) if last_update_date is not None and new_update_date is not None and \ diff --git a/models.py b/models.py index e284c28..b1d6d27 100644 --- a/models.py +++ b/models.py @@ -16,6 +16,7 @@ from django.db import models from django.db.models import Count, Manager, Q from django.db.models.functions import ExtractWeek, ExtractYear +from django.utils import timezone from django.utils.text import slugify from .utils import parse_group @@ -143,7 +144,7 @@ class Course(models.Model): begin = models.DateTimeField(verbose_name="début du cours", db_index=True) end = models.DateTimeField(verbose_name="fin du cours") - last_update = models.DateTimeField(verbose_name="dernière mise à jour", auto_now_add=True) + last_update = models.DateTimeField(verbose_name="dernière mise à jour", default=timezone.now) def __str__(self): return self.name -- cgit v1.2.1 From 89bc69f85d9ed2c697db58dc742c4129283f755b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 24 Sep 2017 18:24:39 +0200 Subject: Correction du bas de page --- templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index a685031..efbb21c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -23,7 +23,7 @@ {% endblock %} -- cgit v1.2.1 From 701e3d62efe34a0b6b0a599238175704196097de Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 25 Sep 2017 15:39:05 +0200 Subject: Correction des flux RSS et Atom : update était à l’origine un objet, c’est maintenant un dictionnaire. --- feeds.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/feeds.py b/feeds.py index 7e8cd80..19c9e47 100644 --- a/feeds.py +++ b/feeds.py @@ -103,14 +103,22 @@ class RSSFeed(Feed): return "Emploi du temps du groupe {0}".format(obj[0]) 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, "year": item.year, "week": item.week}) + group = item["group"] + return reverse("timetable", + kwargs={"year_slug": group.timetable.year.slug, + "timetable_slug": group.timetable.slug, + "group_slug": group.slug, + "year": item["year"], + "week": item["week"]}) def item_description(self, item): - return item.description + return item["description"] + + def item_title(self, item): + return "{0}, semaine {1} de {2}".format(item["group"], item["week"], item["year"]) def item_updateddate(self, item): - return item.date + return item["last_update__max"] def items(self, obj): template = loader.get_template("timetable_common.html") @@ -121,8 +129,8 @@ class RSSFeed(Feed): courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) context = {"group": group, "courses": group_courses(courses), "last_update": update["last_update__max"], "year": update["year"], "week": update["week"]} - update.group = group - update.description = template.render(context) + update["group"] = group + update["description"] = template.render(context) return obj[1] -- cgit v1.2.1 From 3d2fa6b15c58b775bc7e60e148f3a6bf1f2631d0 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 25 Sep 2017 15:50:02 +0200 Subject: Découpage des lignes dépassant les 80 caractères de long dans feeds.py et models.py --- feeds.py | 39 +++++++++++++++++++++++++++++++-------- models.py | 54 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/feeds.py b/feeds.py index 19c9e47..ff00066 100644 --- a/feeds.py +++ b/feeds.py @@ -28,7 +28,8 @@ from .models import Course, Group from .templatetags.rooms import format_rooms from .utils import get_current_or_next_week, get_week, group_courses -ICAL_NAMES = ["uid", "summary", "description", "location", "start", "dtstart", "dtend", "dtstamp"] +ICAL_NAMES = ["uid", "summary", "description", "location", + "start", "dtstart", "dtend", "dtstamp"] class IcalFeedGenerator(SyndicationFeed): @@ -57,7 +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, slug=group_slug) + group = Group.objects.get(timetable__year__slug=year_slug, + timetable__slug=timetable_slug, + slug=group_slug) except: raise ObjectDoesNotExist else: @@ -87,16 +90,28 @@ class RSSFeed(Feed): begin, end = get_week(year, week) try: - group = Group.objects.get(timetable__year__slug=year_slug, timetable__slug=timetable_slug, slug=group_slug) + group = Group.objects.get(timetable__year__slug=year_slug, + timetable__slug=timetable_slug, + slug=group_slug) except: raise ObjectDoesNotExist else: - updates = Course.objects.get_courses_for_group(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] + updates = Course.objects.get_courses_for_group(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 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}) + link = reverse("timetable", + kwargs={"year_slug": group.timetable.year.slug, + "timetable_slug": group.timetable.slug, + "group_slug": group.slug}) return link def title(self, obj): @@ -115,7 +130,9 @@ class RSSFeed(Feed): return item["description"] def item_title(self, item): - return "{0}, semaine {1} de {2}".format(item["group"], item["week"], item["year"]) + return "{0}, semaine {1} de {2}".format(item["group"], + item["week"], + item["year"]) def item_updateddate(self, item): return item["last_update__max"] @@ -126,8 +143,14 @@ class RSSFeed(Feed): for update in obj[1]: 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), "last_update": update["last_update__max"], "year": update["year"], "week": update["week"]} + courses = Course.objects.get_courses_for_group(group, + begin__gte=start, + begin__lt=end) + context = {"group": group, + "courses": group_courses(courses), + "last_update": update["last_update__max"], + "year": update["year"], + "week": update["week"]} update["group"] = group update["description"] = template.render(context) diff --git a/models.py b/models.py index b1d6d27..4be7ef5 100644 --- a/models.py +++ b/models.py @@ -48,7 +48,8 @@ class Year(SlugModel): class Timetable(SlugModel): - year = models.ForeignKey(Year, on_delete=models.CASCADE, verbose_name="année") + 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="") @@ -65,26 +66,36 @@ class Timetable(SlugModel): class GroupManager(Manager): def get_relevant_groups(self, *args, **criteria): - return self.get_queryset().filter(*args, **criteria).annotate(children_count=Count("children")).filter(children_count=0) + return self.get_queryset().filter(*args, **criteria) \ + .annotate(children_count=Count("children")) \ + .filter(children_count=0) class Group(models.Model): objects = GroupManager() 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, verbose_name="emploi du temps") + celcat_name = models.CharField(max_length=255, + verbose_name="nom dans Celcat") + timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, + verbose_name="emploi du temps") mention = models.CharField(max_length=128) - subgroup = models.CharField(max_length=1, verbose_name="sous-groupe", null=True) + subgroup = models.CharField(max_length=1, verbose_name="sous-groupe", + null=True) td = models.IntegerField(verbose_name="groupe de TD", null=True) tp = models.IntegerField(verbose_name="groupe de TP", null=True) - parent = models.ForeignKey("self", verbose_name="groupe parent", null=True, default=None, related_name="children") + parent = models.ForeignKey("self", verbose_name="groupe parent", null=True, + default=None, related_name="children") slug = models.SlugField(max_length=64, default="") def corresponds_to(self, timetable_id, mention, subgroup, td, tp): - return self.timetable.id == timetable_id and self.mention.startswith(mention) and (self.subgroup == subgroup or self.subgroup is None) and (self.td == td or self.td is None or td is None) and (self.tp == tp or self.tp is None or tp is None) + return self.timetable.id == timetable_id and \ + self.mention.startswith(mention) and \ + (self.subgroup == subgroup or self.subgroup is None) and \ + (self.td == td or self.td is None or td is None) and \ + (self.tp == tp or self.tp is None or tp is None) @property def group_info(self): @@ -104,7 +115,9 @@ class Group(models.Model): class Meta: index_together = ("mention", "subgroup", "td", "tp",) - unique_together = (("name", "timetable",), ("celcat_name", "timetable",), ("slug", "timetable",),) + unique_together = (("name", "timetable",), + ("celcat_name", "timetable",), + ("slug", "timetable",),) verbose_name = "groupe" verbose_name_plural = "groupes" @@ -124,18 +137,32 @@ class Room(models.Model): class CourseManager(Manager): def get_courses_for_group(self, group, **criteria): - return self.get_queryset().filter(Q(groups__td__isnull=True) | Q(groups__td=group.td), Q(groups__tp__isnull=True) | Q(groups__tp=group.tp), Q(groups__subgroup__isnull=True) | Q(groups__subgroup=group.subgroup), groups__mention=group.mention, timetable=group.timetable, **criteria).order_by("begin") + return self.get_queryset() \ + .filter(Q(groups__td__isnull=True) | Q(groups__td=group.td), + Q(groups__tp__isnull=True) | Q(groups__tp=group.tp), + Q(groups__subgroup__isnull=True) | \ + Q(groups__subgroup=group.subgroup), + groups__mention=group.mention, + timetable=group.timetable, **criteria) \ + .order_by("begin") def get_weeks(self, **criteria): - return self.get_queryset().filter(**criteria).order_by("groups__name", "year", "week").annotate(_=Count(("groups", "year", "week", "begin")), year=ExtractYear("begin"), week=ExtractWeek("begin")) + return self.get_queryset() \ + .filter(**criteria) \ + .order_by("groups__name", "year", "week") \ + .annotate(_=Count(("groups", "year", "week", "begin")), + year=ExtractYear("begin"), + week=ExtractWeek("begin")) class Course(models.Model): objects = CourseManager() name = models.CharField(max_length=255, verbose_name="nom", null=True) - type_ = models.CharField(name="type", max_length=255, verbose_name="type de cours", null=True) - timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, verbose_name="emploi du temps") + type_ = models.CharField(name="type", max_length=255, + verbose_name="type de cours", null=True) + timetable = models.ForeignKey(Timetable, 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") @@ -144,7 +171,8 @@ class Course(models.Model): begin = models.DateTimeField(verbose_name="début du cours", db_index=True) end = models.DateTimeField(verbose_name="fin du cours") - last_update = models.DateTimeField(verbose_name="dernière mise à jour", default=timezone.now) + last_update = models.DateTimeField(verbose_name="dernière mise à jour", + default=timezone.now) def __str__(self): return self.name -- cgit v1.2.1 From aa7ccf13a8735f162e2ea859ad1d8ebe9f34f657 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 25 Sep 2017 16:08:01 +0200 Subject: Utilisation de pylint pour améliorer la qualité du code --- feeds.py | 2 +- management/commands/_private.py | 20 ++++++++++---------- management/commands/timetables.py | 19 +++++++++++++------ utils.py | 6 +++--- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/feeds.py b/feeds.py index ff00066..cdecd73 100644 --- a/feeds.py +++ b/feeds.py @@ -87,7 +87,7 @@ class IcalFeed(Feed): class RSSFeed(Feed): def get_object(self, request, year_slug, timetable_slug, group_slug): year, week = get_current_or_next_week() - begin, end = get_week(year, week) + _, end = get_week(year, week) try: group = Group.objects.get(timetable__year__slug=year_slug, diff --git a/management/commands/_private.py b/management/commands/_private.py index c140f51..2d01e67 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -13,15 +13,15 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . +import datetime +import re + from bs4 import BeautifulSoup from django.utils import timezone from edt.models import Group, Room, Course from edt.utils import get_week -import datetime -import re - import requests @@ -31,8 +31,8 @@ class Week: self.start = timezone.make_aware( datetime.datetime.strptime(start, "%d/%m/%Y")) - def get_day(self, id): - return self.start + datetime.timedelta(id) + def get_day(self, day_id): + return self.start + datetime.timedelta(day_id) @property def year(self): @@ -75,7 +75,7 @@ def consolidate_group(group): def consolidate_groups(groups): for group in groups: - if group.parent == None: + if group.parent is None: consolidate_group(group) def delete_courses_in_week(timetable, year, week): @@ -153,7 +153,7 @@ def get_update_date(soup): # (\d+) au moins un nombre # : un deux-points # (\d+) au moins un nombre - datetime_regex = re.compile("(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)") + datetime_regex = re.compile(r"(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)") search = datetime_regex.search(soup.footer.text) if search is None: return None @@ -171,8 +171,8 @@ def get_weeks(soup): return weeks def get_xml(url): - r = requests.get(url) - r.encoding = "utf8" + req = requests.get(url) + req.encoding = "utf8" - soup = BeautifulSoup(r.content, "html.parser") + soup = BeautifulSoup(req.content, "html.parser") return soup diff --git a/management/commands/timetables.py b/management/commands/timetables.py index e136f53..f01ac3c 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -13,6 +13,8 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . +import datetime + from django.core.management.base import BaseCommand from django.db import transaction from django.db.models import Max @@ -22,13 +24,15 @@ from edt.models import Timetable, Course from edt.utils import get_week from ._private import delete_courses_in_week, get_events, get_update_date, get_weeks, get_xml -import datetime - @transaction.atomic def process_timetable_week(timetable, year, week, soup, weeks_in_soup): begin, end = get_week(year, week) - last_update_date = Course.objects.filter(timetable=timetable, begin__gte=begin, begin__lt=end).aggregate(Max("last_update"))["last_update__max"] + last_update_date = Course.objects.filter(timetable=timetable, + begin__gte=begin, + begin__lt=end) \ + .aggregate(Max("last_update")) \ + ["last_update__max"] new_update_date = get_update_date(soup) if last_update_date is not None and new_update_date is not None and \ @@ -38,7 +42,9 @@ def process_timetable_week(timetable, year, week, soup, weeks_in_soup): delete_courses_in_week(timetable, year, week) for name, type_, groups, rooms, notes, begin, end in \ get_events(timetable, year, week, soup, weeks_in_soup): - course = Course.objects.create(timetable=timetable, begin=begin, end=end) + course = Course.objects.create(timetable=timetable, + begin=begin, + end=end) course.name = name course.type = type_ course.notes = notes @@ -86,8 +92,9 @@ class Command(BaseCommand): try: process_timetable(timetable, year, weeks) - except Exception as e: - self.stderr.write(self.style.ERROR("Failed to process {0}: {1}".format(timetable, e))) + except Exception as exc: + self.stderr.write( + self.style.ERROR("Failed to process {0}: {1}".format(timetable, exc))) errcount += 1 if errcount == 0: diff --git a/utils.py b/utils.py index 3ded68e..c3b4531 100644 --- a/utils.py +++ b/utils.py @@ -13,11 +13,11 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . -from django.utils import timezone - import datetime import re +from django.utils import timezone + def get_current_week(): return timezone.now().isocalendar()[:2] @@ -60,7 +60,7 @@ def parse_group(name): # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses # ? groupe optionnel # $ fin de la ligne - group_regex = re.compile("^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s\(.+\))?$") + group_regex = re.compile(r"^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s\(.+\))?$") search = group_regex.search(name) if search is None: return name, None, None, None -- cgit v1.2.1 From ba343196f8305dfadf01f8a617b05e6d9723ce13 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 27 Sep 2017 13:48:44 +0200 Subject: On compare le résultat de la fonction à 0, pas la fonction --- views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views.py b/views.py index eb6a7ac..66f0a03 100644 --- a/views.py +++ b/views.py @@ -64,7 +64,7 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No 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).annotate(Max("last_update")) - if courses.count == 0: + if courses.count() == 0: raise Http404 grouped_courses = group_courses(courses) -- cgit v1.2.1 From a43f7fa835fcdf9f591c3f759f5ab7545c9df5b3 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 27 Sep 2017 14:58:06 +0200 Subject: Affichage d’un message lorsque quelqu’un consulte un emploi du temps périmé --- templates/timetable.html | 7 ++++++- views.py | 12 ++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/templates/timetable.html b/templates/timetable.html index 7a30595..21afa24 100644 --- a/templates/timetable.html +++ b/templates/timetable.html @@ -4,6 +4,11 @@ {% block body %}

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

-

Dernière mise à jour le {{ last_update|date:"l j F o" }} à {{ last_update|date:"H:i" }}

+

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

{% include "timetable_common.html" %} {% endblock %} diff --git a/views.py b/views.py index 66f0a03..442ada6 100644 --- a/views.py +++ b/views.py @@ -55,8 +55,13 @@ 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): + current_year, current_week = get_current_week() + is_old_timetable = False + if year is None or week is None: - year, week = get_current_week() + year, week = current_year, current_week + elif (int(year), int(week)) < (current_year, current_week): + is_old_timetable = True start, end = get_week(int(year), int(week)) @@ -69,7 +74,10 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No grouped_courses = group_courses(courses) - return render(request, "timetable.html", {"group": group, "courses": grouped_courses, "last_update": courses.first().last_update__max, "year": year, "week": int(week)}) + return render(request, "timetable.html", {"group": group, "courses": grouped_courses, + "last_update": courses.first().last_update__max, + "year": year, "week": int(week), + "is_old_timetable": is_old_timetable}) def contact(request): return render(request, "contact.html", {"email": settings.ADMINS[0][1]}) -- cgit v1.2.1 From 436099ebef2acb8a3965cc39b07d99f61fa1eba2 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Wed, 27 Sep 2017 15:06:54 +0200 Subject: Suppression des lignes trop longues --- views.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/views.py b/views.py index 442ada6..404b7cb 100644 --- a/views.py +++ b/views.py @@ -37,11 +37,15 @@ def group_list(request, year_slug, timetable_slug): year, week = get_current_week() start, _ = get_week(year, week) - groups_weeks = Course.objects.get_weeks(begin__gte=start, timetable=timetable).values("groups__mention", "groups__subgroup", "groups__td", "groups__tp", "year", "week") + groups_weeks = Course.objects.get_weeks(begin__gte=start, timetable=timetable) \ + .values("groups__mention", "groups__subgroup", + "groups__td", "groups__tp", "year", "week") for group in groups: for group_week in groups_weeks: - if group.corresponds_to(timetable.id, group_week["groups__mention"], group_week["groups__subgroup"], group_week["groups__td"], group_week["groups__tp"]): + if group.corresponds_to(timetable.id, group_week["groups__mention"], + group_week["groups__subgroup"], group_week["groups__td"], + group_week["groups__tp"]): if not hasattr(group, "weeks"): group.weeks = [] @@ -68,7 +72,8 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No 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).annotate(Max("last_update")) + courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) \ + .annotate(Max("last_update")) if courses.count() == 0: raise Http404 -- cgit v1.2.1 From cd2c473be7ebc33d7992441ae3ff1bf52785488c Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 28 Sep 2017 13:30:38 +0200 Subject: Ajout du type de cours dans le nom de l’évèmenent ICS --- feeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feeds.py b/feeds.py index cdecd73..564b285 100644 --- a/feeds.py +++ b/feeds.py @@ -80,7 +80,7 @@ class IcalFeed(Feed): "dtstart": item.begin, "dtend": item.end, "dtstamp": item.last_update, - "summary": item.name, + "summary": item.name + " (" + item.type + ")", "location": format_rooms(item.rooms.all())} -- cgit v1.2.1 From 0b3fabb03d98cf2455818aa2650df1ab86846431 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 28 Sep 2017 21:35:35 +0200 Subject: Si un utilisateur consulte un groupe qui a des enfants, on lui propose une liste de groupes enfants --- views.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/views.py b/views.py index 404b7cb..82ef639 100644 --- a/views.py +++ b/views.py @@ -31,10 +31,7 @@ def mention_list(request, year_slug): return render(request, "mention_list.html", {"year": year, "timetables": timetables}) -def group_list(request, year_slug, timetable_slug): - timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) - groups = Group.objects.get_relevant_groups(timetable=timetable).order_by("name") - +def group_list_common(request, timetable, groups): year, week = get_current_week() start, _ = get_week(year, week) groups_weeks = Course.objects.get_weeks(begin__gte=start, timetable=timetable) \ @@ -58,6 +55,11 @@ def group_list(request, year_slug, timetable_slug): return render(request, "group_list.html", {"timetable": timetable, "groups": groups}) +def group_list(request, year_slug, timetable_slug): + timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) + groups = Group.objects.get_relevant_groups(timetable=timetable).order_by("name") + return group_list_common(request, timetable, groups) + def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): current_year, current_week = get_current_week() is_old_timetable = False @@ -72,6 +74,9 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No 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.children.count(): + return group_list_common(request, timetable, group.children.order_by("name")) + courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) \ .annotate(Max("last_update")) if courses.count() == 0: -- cgit v1.2.1 From 1c3a7336ad48742da5da56b301496a10a38b92b0 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 28 Sep 2017 21:36:08 +0200 Subject: Changement de la regex de groupe --- utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/utils.py b/utils.py index c3b4531..256e136 100644 --- a/utils.py +++ b/utils.py @@ -48,19 +48,19 @@ def group_courses(courses): def parse_group(name): # Explication de la regex # - # ^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s\(.+\))?$ - # ^ début de la ligne - # ([\w ]+?) correspond à au moins un caractère - # \s+ un ou plusieurs espaces - # ((CM(\w))| correspond à CM suivi d'une lettre ou… - # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… - # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres - # )? groupe optionnel - # (\s un espace - # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses - # ? groupe optionnel - # $ fin de la ligne - group_regex = re.compile(r"^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s\(.+\))?$") + # ^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$ + # ^ début de la ligne + # ([\w ]+?) correspond à au moins un caractère + # \s+ un ou plusieurs espaces + # ((CM(\w))| correspond à CM suivi d'une lettre ou… + # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… + # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres + # )? groupe optionnel + # (\s+ un ou plusieurs espaces + # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses + # ? groupe optionnel + # $ fin de la ligne + group_regex = re.compile(r"^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$") search = group_regex.search(name) if search is None: return name, None, None, None -- cgit v1.2.1 From a1231e340dbb9236f3804679360c0ab7895ae903 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 28 Sep 2017 21:57:54 +0200 Subject: Affichage seulement des groupes intéressants pour éviter des clics inutiles : Si les enfants d’un groupe ont eux-mêmes des enfants, on n’affiche pas ce groupe mais ces enfants. --- models.py | 7 +++++++ views.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index 4be7ef5..0625e34 100644 --- a/models.py +++ b/models.py @@ -65,6 +65,13 @@ class Timetable(SlugModel): class GroupManager(Manager): + def get_relevant_children(self, group): + parent_in = self.get_queryset().filter(parent=group) + return self.get_queryset().filter(Q(parent=group) | Q(parent__in=parent_in)) \ + .annotate(children_count=Count("children")) \ + .filter(children_count=0) \ + .order_by("name") + def get_relevant_groups(self, *args, **criteria): return self.get_queryset().filter(*args, **criteria) \ .annotate(children_count=Count("children")) \ diff --git a/views.py b/views.py index 82ef639..23bced4 100644 --- a/views.py +++ b/views.py @@ -75,7 +75,7 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No group = get_object_or_404(Group, slug=group_slug, timetable=timetable) if group.children.count(): - return group_list_common(request, timetable, group.children.order_by("name")) + return group_list_common(request, timetable, Group.objects.get_relevant_children(group)) courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) \ .annotate(Max("last_update")) -- cgit v1.2.1 From 30b58f8b8229c7ac62d886dfd79cbf7ec027f6f2 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 28 Sep 2017 22:02:16 +0200 Subject: Ajout du nom de l’emploi du temps dans la page des groupes --- templates/group_list.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/group_list.html b/templates/group_list.html index 2530865..eeb3a35 100644 --- a/templates/group_list.html +++ b/templates/group_list.html @@ -1,6 +1,8 @@ {% extends "index.html" %} {% load dt_week %} +{% block title %}{{ timetable }} – {% endblock %} + {% block body %}

{{ timetable }}

    -- cgit v1.2.1 From 1d5068de5e4babb8c2c969c626fd1d8b53bc28ea Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 28 Sep 2017 22:27:50 +0200 Subject: Mise en commun de la template de sélection de l’année et de la mention --- templates/group_list.html | 4 ++-- templates/index.html | 14 +++++++------- templates/mention_list.html | 14 -------------- templates/timetable.html | 8 ++++---- views.py | 4 ++-- 5 files changed, 15 insertions(+), 29 deletions(-) delete mode 100644 templates/mention_list.html diff --git a/templates/group_list.html b/templates/group_list.html index eeb3a35..aebb0db 100644 --- a/templates/group_list.html +++ b/templates/group_list.html @@ -6,8 +6,8 @@ {% block body %}

    {{ timetable }}

      - {% for group in groups %} + {% for group in groups %}
    • {{ group }} – {% for week in group.weeks %}{{ week|dt_prettyprint }} {% empty %}aucun cours{% endfor %}
    • - {% endfor %} + {% endfor %}
    {% endblock %} diff --git a/templates/index.html b/templates/index.html index efbb21c..8d7634e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,7 +3,7 @@ - {% block title %}{% endblock %}celcatsanitizer + {% block title %}{% if year %}{{ year }} – {% endif %}{% endblock %}celcatsanitizer @@ -12,13 +12,13 @@
    {% block body %} -

    Choisissez votre année

    +

    {% if year %}{{ year }} – Choisissez votre mention{% else %}Choisissez votre année{% endif %}

      - {% for year in years %} -
    • {{ year }}
    • - {% empty %} -

      Aucun emploi du temps à afficher

      - {% endfor %} + {% for element in elements %} +
    • {{ element }}
    • + {% empty %} +

      Aucun emploi du temps à afficher

      + {% endfor %}
    {% endblock %}
    diff --git a/templates/mention_list.html b/templates/mention_list.html deleted file mode 100644 index 71cfaac..0000000 --- a/templates/mention_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "index.html" %} - -{% block title %}{{ year }} – {% endblock %} - -{% block body %} -

    {{ year }} – Choisissez votre mention

    -
      -{% for timetable in timetables %} -
    • {{ timetable }}
    • -{% empty %} -

      Aucun emploi du temps à afficher

      -{% endfor %} -
    -{% endblock %} diff --git a/templates/timetable.html b/templates/timetable.html index 21afa24..9143c2f 100644 --- a/templates/timetable.html +++ b/templates/timetable.html @@ -5,10 +5,10 @@ {% block body %}

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

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

    {% include "timetable_common.html" %} {% endblock %} diff --git a/views.py b/views.py index 23bced4..14a4208 100644 --- a/views.py +++ b/views.py @@ -23,13 +23,13 @@ from .utils import get_current_week, get_week, group_courses def index(request): years = Year.objects.order_by("name") - return render(request, "index.html", {"years": years}) + return render(request, "index.html", {"elements": years}) def mention_list(request, year_slug): year = get_object_or_404(Year, slug=year_slug) timetables = Timetable.objects.order_by("name").filter(year=year) - return render(request, "mention_list.html", {"year": year, "timetables": timetables}) + return render(request, "index.html", {"year": year, "elements": timetables}) def group_list_common(request, timetable, groups): year, week = get_current_week() -- cgit v1.2.1 From a30de7e84c7c7208f76178586befe5374cd1e02d Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 29 Sep 2017 14:33:55 +0200 Subject: Affichage des cours des groupes enfants si le groupe demandé en possède --- models.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/models.py b/models.py index 0625e34..cca4df8 100644 --- a/models.py +++ b/models.py @@ -144,11 +144,17 @@ class Room(models.Model): class CourseManager(Manager): def get_courses_for_group(self, group, **criteria): + groups_criteria = [] + if group.subgroup is not None: + groups_criteria.append(Q(groups__subgroup__isnull=True) | \ + Q(groups__subgroup=group.subgroup)) + if group.td is not None: + groups_criteria.append(Q(groups__td__isnull=True) | Q(groups__td=group.td)) + if group.tp is not None: + groups_criteria.append(Q(groups__tp__isnull=True) | Q(groups__tp=group.tp)) + return self.get_queryset() \ - .filter(Q(groups__td__isnull=True) | Q(groups__td=group.td), - Q(groups__tp__isnull=True) | Q(groups__tp=group.tp), - Q(groups__subgroup__isnull=True) | \ - Q(groups__subgroup=group.subgroup), + .filter(*groups_criteria, groups__mention=group.mention, timetable=group.timetable, **criteria) \ .order_by("begin") -- cgit v1.2.1 From a8d35aee63f073674993b8afde78a8d5c1517e05 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 29 Sep 2017 21:19:01 +0200 Subject: get_events() renvoie des objets Course au lieu d’un tuple de données. Ajout de commentaires dans la fonction get_events() Les paramètres year et week des fonctions get_events(), process_timetable_week() et process_timetable() sont maintenant optionnels. --- management/commands/_private.py | 59 +++++++++++++++++++++++++-------------- management/commands/timetables.py | 25 ++++++----------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 2d01e67..e018e3a 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -93,50 +93,67 @@ def get_from_db_or_create(cls, **kwargs): return obj -def get_events(timetable, year, week, soup, weeks_in_soup): +def get_events(timetable, soup, weeks_in_soup, 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"): - title = None - type_ = None - groups = None - rooms = None - notes = None - - if weeks_in_soup[event.rawweeks.text].number == week and \ - weeks_in_soup[event.rawweeks.text].year == year and \ + event_week = weeks_in_soup[event.rawweeks.text] + + # On passe le traitement si la semaine de l’événement ne correspond pas + # à la semaine passée, ou qu’il ne contient pas de groupe ou n’a pas de + # date de début ou de fin. + if (event_week.number == week and event_week.year == year or \ + 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: - date = weeks_in_soup[event.rawweeks.text].get_day(int( - event.day.text)) - + # On récupère la date de l’évènement à partir de la semaine + # et de la semaine référencée, puis la date de début et de fin + date = event_week.get_day(int(event.day.text)) begin = add_time(date, datetime.datetime.strptime( event.starttime.text, "%H:%M")) end = add_time(date, datetime.datetime.strptime( event.endtime.text, "%H:%M")) + # Création de l’objet cours + course = Course.objects.create(timetable=timetable, begin=begin, + end=end) + + # On récupère les groupes concernés par les cours, on les + # « consolide », puis on les insère dans l’objet cours. groups = [get_from_db_or_create(Group, timetable=timetable, celcat_name=item.text) for item in event.resources.group.find_all("item")] consolidate_groups(groups) + course.groups.add(*groups) + # On récupère le champ « remarque » if event.notes is not None: - notes = event.notes.text + course.notes = event.notes.text + # On récupère le nom du cours if event.resources.module is not None: - title = event.resources.module.item.text - elif notes is not None: - title = notes - notes = None + course.name = event.resources.module.item.text else: - title = "Aucune information" - + # Il est possible qu’un cours n’ait pas de nom. Oui oui. + # Qui sont les concepteurs de ce système ? Quels sont leurs + # réseaux ? + # Bref, dans ce cas, on déplace le champ « remarque » de + # l’objet dans le champ « nom ». + course.name, course.notes = course.notes, None + + # Récupération du type de cours if event.category is not None: - type_ = event.category.text + course.type = event.category.text + # Si un cours a une salle attribuée (oui, il est possible qu’il n’y + # en ait pas… qui sont ils, leurs réseaux, tout ça…), on les insère + # dans la base de données, et on les ajoute dans l’objet cours if event.resources.room is not None: rooms = [get_from_db_or_create(Room, name=item.text) for item in event.resources.room.find_all("item")] + course.rooms.add(*rooms) - yield title, type_, groups, rooms, notes, begin, end + yield course def get_update_date(soup): # Explication de la regex diff --git a/management/commands/timetables.py b/management/commands/timetables.py index f01ac3c..7ee63af 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -25,7 +25,7 @@ from edt.utils import get_week from ._private import delete_courses_in_week, get_events, get_update_date, get_weeks, get_xml @transaction.atomic -def process_timetable_week(timetable, year, week, soup, weeks_in_soup): +def process_timetable_week(timetable, soup, weeks_in_soup, year=None, week=None): begin, end = get_week(year, week) last_update_date = Course.objects.filter(timetable=timetable, @@ -40,27 +40,18 @@ def process_timetable_week(timetable, year, week, soup, weeks_in_soup): return delete_courses_in_week(timetable, year, week) - for name, type_, groups, rooms, notes, begin, end in \ - get_events(timetable, year, week, soup, weeks_in_soup): - course = Course.objects.create(timetable=timetable, - begin=begin, - end=end) - course.name = name - course.type = type_ - course.notes = notes - - course.groups.add(*groups) - if rooms is not None: - course.rooms.add(*rooms) - + for course in get_events(timetable, soup, weeks_in_soup, year, week): course.save() -def process_timetable(timetable, year, weeks): +def process_timetable(timetable, year=None, weeks=None): soup = get_xml(timetable.url) weeks_in_soup = get_weeks(soup) - for week in weeks: - process_timetable_week(timetable, year, week, soup, weeks_in_soup) + if year is not None and weeks is not None: + for week in weeks: + process_timetable_week(timetable, soup, weeks_in_soup, year, week) + else: + process_timetable_week(timetable, soup, weeks_in_soup) class Command(BaseCommand): -- cgit v1.2.1 From 8d7b9f5e9156e9153b6060a8a7a0c33b188f21b7 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 29 Sep 2017 21:39:13 +0200 Subject: Ajout d’une option pour récupérer tout un emploi du temps Pas encore testé parce que j’aime vivre dangereusement --- management/commands/timetables.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 7ee63af..279de89 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -60,12 +60,15 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument("--week", type=int, choices=range(1, 54), nargs="+") parser.add_argument("--year", type=int, nargs=1) + parser.add_argument("--all", const=True, default=False, action="store_const") def handle(self, *args, **options): year = None errcount = 0 - if options["week"] is None: + if options["all"]: + weeks = None + elif options["week"] is None: _, week, day = timezone.now().isocalendar() if day >= 6: year, week, _ = (timezone.now() + datetime.timedelta(weeks=1)).isocalendar() @@ -73,10 +76,11 @@ class Command(BaseCommand): else: weeks = options["week"] - if options["year"] is None and year is None: - year = timezone.now().year - elif year is None: - year = options["year"][0] + if not options["all"]: + if options["year"] is None and year is None: + year = timezone.now().year + elif year is None: + year = options["year"][0] for timetable in Timetable.objects.all(): self.stdout.write("Processing {0}".format(timetable)) -- cgit v1.2.1 From bc2c2cae1fd6a52fb17771b4b537e07bca103987 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 29 Sep 2017 22:09:51 +0200 Subject: Suppression de la classe Week \o/ --- management/commands/_private.py | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index e018e3a..0b26b4e 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -24,22 +24,9 @@ from edt.utils import get_week import requests - -class Week: - def __init__(self, number, start): - self.number = number - self.start = timezone.make_aware( - datetime.datetime.strptime(start, "%d/%m/%Y")) - - def get_day(self, day_id): - return self.start + datetime.timedelta(day_id) - - @property - def year(self): - return self.start.year - def add_time(date, time): - delta = datetime.timedelta(hours=time.hour, minutes=time.minute) + ptime = datetime.datetime.strptime(time, "%H:%M") + delta = datetime.timedelta(hours=ptime.hour, minutes=ptime.minute) return date + delta def consolidate_group(group): @@ -98,21 +85,20 @@ def get_events(timetable, soup, weeks_in_soup, year=None, week=None): Le traîtement se limitera à la semaine indiquée si il y en a une.""" for event in soup.find_all("event"): event_week = weeks_in_soup[event.rawweeks.text] + event_week_num = event_week.isocalendar()[1] # Numéro de semaine # On passe le traitement si la semaine de l’événement ne correspond pas # à la semaine passée, ou qu’il ne contient pas de groupe ou n’a pas de # date de début ou de fin. - if (event_week.number == week and event_week.year == year or \ + if (event_week_num == week and event_week.year == year or \ 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: # On récupère la date de l’évènement à partir de la semaine - # et de la semaine référencée, puis la date de début et de fin - date = event_week.get_day(int(event.day.text)) - begin = add_time(date, datetime.datetime.strptime( - event.starttime.text, "%H:%M")) - end = add_time(date, datetime.datetime.strptime( - event.endtime.text, "%H:%M")) + # et de la semaine référencée, puis l’heure de début et de fin + date = event_week + datetime.timedelta(int(event.day.text)) + begin = add_time(date, event.starttime.text) + end = add_time(date, event.endtime.text) # Création de l’objet cours course = Course.objects.create(timetable=timetable, begin=begin, @@ -182,8 +168,8 @@ def get_update_date(soup): def get_weeks(soup): weeks = {} for span in soup.find_all("span"): - weeks[span.alleventweeks.text] = Week(int(span.title.text), - span["date"]) + weeks[span.alleventweeks.text] = timezone.make_aware( + datetime.datetime.strptime(span["date"], "%d/%m/%Y")) return weeks -- cgit v1.2.1 From bb15653aecf1bd13fd892eb54d04c8f3e0fee270 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 29 Sep 2017 22:18:53 +0200 Subject: Ajout d’un paramètre à la commande timetables pour forcer la mise à jour --- management/commands/timetables.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 279de89..c5c6dfa 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -25,7 +25,7 @@ from edt.utils import get_week 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, year=None, week=None): +def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, week=None): begin, end = get_week(year, week) last_update_date = Course.objects.filter(timetable=timetable, @@ -35,7 +35,7 @@ def process_timetable_week(timetable, soup, weeks_in_soup, year=None, week=None) ["last_update__max"] new_update_date = get_update_date(soup) - if last_update_date is not None and new_update_date is not None and \ + if not force and last_update_date is not None and new_update_date is not None and \ last_update_date >= new_update_date: return @@ -43,24 +43,25 @@ def process_timetable_week(timetable, soup, weeks_in_soup, year=None, week=None) for course in get_events(timetable, soup, weeks_in_soup, year, week): course.save() -def process_timetable(timetable, year=None, weeks=None): +def process_timetable(timetable, force, year=None, weeks=None): soup = get_xml(timetable.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, year, week) + process_timetable_week(timetable, soup, weeks_in_soup, force, year, week) else: - process_timetable_week(timetable, soup, weeks_in_soup) + process_timetable_week(timetable, soup, weeks_in_soup, force) class Command(BaseCommand): help = "Fetches registered celcat timetables" def add_arguments(self, parser): + parser.add_argument("--all", const=True, default=False, action="store_const") + parser.add_argument("--force", const=True, default=False, action="store_const") parser.add_argument("--week", type=int, choices=range(1, 54), nargs="+") parser.add_argument("--year", type=int, nargs=1) - parser.add_argument("--all", const=True, default=False, action="store_const") def handle(self, *args, **options): year = None @@ -86,7 +87,7 @@ class Command(BaseCommand): self.stdout.write("Processing {0}".format(timetable)) try: - process_timetable(timetable, year, weeks) + process_timetable(timetable, options["force"], year, weeks) except Exception as exc: self.stderr.write( self.style.ERROR("Failed to process {0}: {1}".format(timetable, exc))) -- cgit v1.2.1 From a796926c1d1e4ea19d1de205678be638fbfe8c06 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 30 Sep 2017 11:51:54 +0200 Subject: Déplacement de la logique de création de l’objet Course dans sa propre fonction --- management/commands/_private.py | 95 +++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 0b26b4e..23083e1 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -80,6 +80,54 @@ def get_from_db_or_create(cls, **kwargs): return obj +def get_event(timetable, event, event_week): + """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 + date = event_week + datetime.timedelta(int(event.day.text)) + begin = add_time(date, event.starttime.text) + end = add_time(date, event.endtime.text) + + # Création de l’objet cours + course = Course.objects.create(timetable=timetable, begin=begin, end=end) + + # On récupère les groupes concernés par les cours, on les + # « consolide », puis on les insère dans l’objet cours. + groups = [get_from_db_or_create(Group, timetable=timetable, + celcat_name=item.text) + for item in event.resources.group.find_all("item")] + consolidate_groups(groups) + course.groups.add(*groups) + + # On récupère le champ « remarque » + if event.notes is not None: + course.notes = event.notes.text + + # On récupère le nom du cours + if event.resources.module is not None: + course.name = event.resources.module.item.text + else: + # Il est possible qu’un cours n’ait pas de nom. Oui oui. + # Qui sont les concepteurs de ce système ? Quels sont leurs + # réseaux ? + # Bref, dans ce cas, on déplace le champ « remarque » de + # l’objet dans le champ « nom ». + course.name, course.notes = course.notes, None + + # Récupération du type de cours + if event.category is not None: + course.type = event.category.text + + # Si un cours a une salle attribuée (oui, il est possible qu’il n’y + # en ait pas… qui sont ils, leurs réseaux, tout ça…), on les insère + # dans la base de données, et on les ajoute dans l’objet cours + if event.resources.room is not None: + rooms = [get_from_db_or_create(Room, name=item.text) + for item in event.resources.room.find_all("item")] + course.rooms.add(*rooms) + + return course + def get_events(timetable, soup, weeks_in_soup, 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.""" @@ -94,52 +142,7 @@ def get_events(timetable, soup, weeks_in_soup, 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: - # 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 - date = event_week + datetime.timedelta(int(event.day.text)) - begin = add_time(date, event.starttime.text) - end = add_time(date, event.endtime.text) - - # Création de l’objet cours - course = Course.objects.create(timetable=timetable, begin=begin, - end=end) - - # On récupère les groupes concernés par les cours, on les - # « consolide », puis on les insère dans l’objet cours. - groups = [get_from_db_or_create(Group, timetable=timetable, - celcat_name=item.text) - for item in event.resources.group.find_all("item")] - consolidate_groups(groups) - course.groups.add(*groups) - - # On récupère le champ « remarque » - if event.notes is not None: - course.notes = event.notes.text - - # On récupère le nom du cours - if event.resources.module is not None: - course.name = event.resources.module.item.text - else: - # Il est possible qu’un cours n’ait pas de nom. Oui oui. - # Qui sont les concepteurs de ce système ? Quels sont leurs - # réseaux ? - # Bref, dans ce cas, on déplace le champ « remarque » de - # l’objet dans le champ « nom ». - course.name, course.notes = course.notes, None - - # Récupération du type de cours - if event.category is not None: - course.type = event.category.text - - # Si un cours a une salle attribuée (oui, il est possible qu’il n’y - # en ait pas… qui sont ils, leurs réseaux, tout ça…), on les insère - # dans la base de données, et on les ajoute dans l’objet cours - if event.resources.room is not None: - rooms = [get_from_db_or_create(Room, name=item.text) - for item in event.resources.room.find_all("item")] - course.rooms.add(*rooms) - - yield course + yield get_event(timetable, event, event_week) def get_update_date(soup): # Explication de la regex -- cgit v1.2.1 From 45402cbdab2dfdbd9e2f72c70d27f966f932789b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 30 Sep 2017 12:20:48 +0200 Subject: Ajout de commentaires à la fonction get_weeks() --- management/commands/_private.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/management/commands/_private.py b/management/commands/_private.py index 23083e1..8f195a1 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -169,8 +169,18 @@ def get_update_date(soup): return timezone.make_aware(date) def get_weeks(soup): + # Les semaines sont référencées de manière assez… exotique + # En gros, il y a une liste d’éléments span qui contiennent une sorte d’ID + # de la semaine, formaté de la manière suivante : + # NNNNNNNNNNNNNNNNNNNYNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN + # Tous sont de la même longueur, contiennent 51 N et un seul Y. + # Allez savoir pourquoi. Il se trouve dans la balise « alleventweeks ». + # Un paramètre du span (« date ») représente la date de début. + # Un cours contient donc un ID de semaine, puis le nombre de jours après le + # début de cette semaine. weeks = {} - for span in soup.find_all("span"): + for span in soup.find_all("span"): # Liste de toutes les semaines définies + # On parse la date et on la fait correspondre à l’ID weeks[span.alleventweeks.text] = timezone.make_aware( datetime.datetime.strptime(span["date"], "%d/%m/%Y")) -- cgit v1.2.1 From 7a34fc91521373dddacd1595354da53187d46a5d Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 30 Sep 2017 16:30:21 +0200 Subject: Suppression de tous les cours d’un edt si jamais aucune semaine n’est fournie. Pas de filtre de début ou de fin dans le cas où aucune semaine n’est fournie. Changement du type d’aggrégation (minimum au lieu de maximum). --- management/commands/timetables.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index c5c6dfa..58c86fd 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -17,29 +17,35 @@ import datetime from django.core.management.base import BaseCommand from django.db import transaction -from django.db.models import Max +from django.db.models import Min from django.utils import timezone -from edt.models import Timetable, Course +from edt.models import Course, Timetable from edt.utils import get_week 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): - begin, end = get_week(year, week) - - last_update_date = Course.objects.filter(timetable=timetable, - begin__gte=begin, - begin__lt=end) \ - .aggregate(Max("last_update")) \ - ["last_update__max"] + criteria = {} + if year is not None and week is not None: + begin, end = get_week(year, week) + criteria["begin__gte"] = begin + criteria["begin__lt"] = end + + last_update_date = Course.objects.filter(timetable=timetable, **criteria) \ + .aggregate(Min("last_update")) \ + ["last_update__min"] new_update_date = get_update_date(soup) if not force and last_update_date is not None and new_update_date is not None and \ last_update_date >= new_update_date: return - delete_courses_in_week(timetable, year, week) + if year is not None and week is not None: + delete_courses_in_week(timetable, year, week) + else: + Course.objects.filter(timetable=timetable).delete() + for course in get_events(timetable, soup, weeks_in_soup, year, week): course.save() -- cgit v1.2.1 From be6f084c336e285652088fe2d86694b2645a0b93 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 30 Sep 2017 16:34:58 +0200 Subject: On ne supprime pas les éléments plus anciens que ceux référencés par Celcat --- management/commands/timetables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 58c86fd..e82fd55 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -44,7 +44,8 @@ def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, wee if year is not None and week is not None: delete_courses_in_week(timetable, year, week) else: - Course.objects.filter(timetable=timetable).delete() + Course.objects.filter(timetable=timetable, + begin__gte=min(weeks_in_soup.values())).delete() for course in get_events(timetable, soup, weeks_in_soup, year, week): course.save() -- cgit v1.2.1 From 551c85e026d7241e71b9701d523a72c56dcdb14b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 30 Sep 2017 16:59:58 +0200 Subject: Changement de la regex des groupes pour gérer les tirets dans le nom --- utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/utils.py b/utils.py index 256e136..c07c74f 100644 --- a/utils.py +++ b/utils.py @@ -48,19 +48,19 @@ def group_courses(courses): def parse_group(name): # Explication de la regex # - # ^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$ - # ^ début de la ligne - # ([\w ]+?) correspond à au moins un caractère - # \s+ un ou plusieurs espaces - # ((CM(\w))| correspond à CM suivi d'une lettre ou… - # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… - # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres - # )? groupe optionnel - # (\s+ un ou plusieurs espaces - # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses - # ? groupe optionnel - # $ fin de la ligne - group_regex = re.compile(r"^([\w ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$") + # ^([\w\- ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$ + # ^ début de la ligne + # ([\w\- ]+?) correspond à au moins un caractère + # \s+ un ou plusieurs espaces + # ((CM(\w))| correspond à CM suivi d'une lettre ou… + # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… + # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres + # )? groupe optionnel + # (\s+ un ou plusieurs espaces + # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses + # ? groupe optionnel + # $ fin de la ligne + group_regex = re.compile(r"^([\w\- ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$") search = group_regex.search(name) if search is None: return name, None, None, None -- cgit v1.2.1 From bf7c0b026284d7abe0390912253f07a65fa991d7 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 30 Sep 2017 17:01:32 +0200 Subject: On affiche que les groupes qui commencent par le nom de l’emploi du temps --- views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/views.py b/views.py index 14a4208..2d59c7c 100644 --- a/views.py +++ b/views.py @@ -57,7 +57,9 @@ def group_list_common(request, timetable, groups): def group_list(request, year_slug, timetable_slug): timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) - groups = Group.objects.get_relevant_groups(timetable=timetable).order_by("name") + groups = Group.objects.get_relevant_groups(timetable=timetable, name__startswith=timetable) \ + .order_by("name") + return group_list_common(request, timetable, groups) def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): -- cgit v1.2.1 From e3594050b78d3b28b5696e9a546163945d8073da Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 11:22:29 +0200 Subject: Revert "On affiche que les groupes qui commencent par le nom de l’emploi du temps" This reverts commit bf7c0b026284d7abe0390912253f07a65fa991d7. --- views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/views.py b/views.py index 2d59c7c..14a4208 100644 --- a/views.py +++ b/views.py @@ -57,9 +57,7 @@ def group_list_common(request, timetable, groups): def group_list(request, year_slug, timetable_slug): timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) - groups = Group.objects.get_relevant_groups(timetable=timetable, name__startswith=timetable) \ - .order_by("name") - + groups = Group.objects.get_relevant_groups(timetable=timetable).order_by("name") return group_list_common(request, timetable, groups) def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): -- cgit v1.2.1 From d015314d3ccbb35f52a28698444de8ff60002e10 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 11:26:18 +0200 Subject: On affiche pas plus d’un mois en avance --- views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views.py b/views.py index 14a4208..d34fadd 100644 --- a/views.py +++ b/views.py @@ -13,6 +13,8 @@ # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . +import datetime + from django.conf import settings from django.db.models import Max from django.http import Http404 @@ -34,7 +36,8 @@ def mention_list(request, year_slug): def group_list_common(request, timetable, groups): year, week = get_current_week() start, _ = get_week(year, week) - groups_weeks = Course.objects.get_weeks(begin__gte=start, timetable=timetable) \ + end = start + datetime.timedelta(weeks=4) + groups_weeks = Course.objects.get_weeks(begin__gte=start, begin__lt=end, timetable=timetable) \ .values("groups__mention", "groups__subgroup", "groups__td", "groups__tp", "year", "week") -- cgit v1.2.1 From f7016ebb865a5b334aff2d84ab009bb491921e54 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 11:38:41 +0200 Subject: Suppression des variables inutiles --- views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views.py b/views.py index d34fadd..4fc0147 100644 --- a/views.py +++ b/views.py @@ -34,9 +34,9 @@ def mention_list(request, year_slug): return render(request, "index.html", {"year": year, "elements": timetables}) def group_list_common(request, timetable, groups): - year, week = get_current_week() - start, _ = get_week(year, week) + start, _ = get_week(*get_current_week()) end = start + datetime.timedelta(weeks=4) + groups_weeks = Course.objects.get_weeks(begin__gte=start, begin__lt=end, timetable=timetable) \ .values("groups__mention", "groups__subgroup", "groups__td", "groups__tp", "year", "week") -- cgit v1.2.1 From e004bf8b70d1f8142e63748ea737e8c0ac8fcfda Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 15:18:14 +0200 Subject: Changement de la valeur par défaut des cours --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models.py b/models.py index cca4df8..ae9265a 100644 --- a/models.py +++ b/models.py @@ -171,7 +171,7 @@ class CourseManager(Manager): class Course(models.Model): objects = CourseManager() - name = models.CharField(max_length=255, verbose_name="nom", null=True) + name = models.CharField(max_length=255, verbose_name="nom", default="Cours") type_ = models.CharField(name="type", max_length=255, verbose_name="type de cours", null=True) timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, -- cgit v1.2.1 From f0c61f1a3a26f8c76f43b21f7860d83041b7fe89 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 16:44:53 +0200 Subject: Ajout d’une colonne pour stocker la date de dernière mise à jour --- management/commands/timetables.py | 3 +++ models.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/management/commands/timetables.py b/management/commands/timetables.py index e82fd55..76f0a7c 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -50,6 +50,9 @@ def process_timetable_week(timetable, soup, weeks_in_soup, force, year=None, wee for course in get_events(timetable, soup, weeks_in_soup, year, week): course.save() + timetable.last_update_date = new_update_date + timetable.save() + def process_timetable(timetable, force, year=None, weeks=None): soup = get_xml(timetable.url) weeks_in_soup = get_weeks(soup) diff --git a/models.py b/models.py index ae9265a..649368e 100644 --- a/models.py +++ b/models.py @@ -54,6 +54,8 @@ class Timetable(SlugModel): 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) + def __str__(self): return self.year.name + " " + self.name -- cgit v1.2.1 From 5d59112a9c7badc2cc0a48d1a8e33d48e1e6e719 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 16:54:42 +0200 Subject: Ajout d’une colonne pour cacher un groupe dans la liste des groupes. Ajout d’actions personnalisées dans l’interface d’administration pour cacher et afficher plusieurs groupes à la fois. Filtrage des groupes cachés dans get_relevant_children() et get_relevant_groups(). --- admin.py | 14 ++++++++++++-- models.py | 6 ++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/admin.py b/admin.py index 8a409e3..e17948a 100644 --- a/admin.py +++ b/admin.py @@ -16,6 +16,14 @@ from django.contrib import admin from .models import Timetable, Group, Room, Course, Year +def make_hidden(modeladmin, request, queryset): + queryset.update(hidden=True) +make_hidden.short_description = "Cacher les groupes sélectionnés" + +def make_visible(modeladmin, request, queryset): + queryset.update(hidden=False) +make_visible.short_description = "Afficher les groupes sélectionnés" + @admin.register(Year) class YearAdmin(admin.ModelAdmin): @@ -35,12 +43,14 @@ class TimetableAdmin(admin.ModelAdmin): @admin.register(Group) class GroupAdmin(admin.ModelAdmin): fieldsets = ( - (None, {"fields": ("name", "celcat_name", "timetable",)}), + (None, {"fields": ("name", "celcat_name", "timetable", "hidden",)}), ("Groupes", {"fields": ("mention", "subgroup", "td", "tp", "parent",)}),) - list_display = ("name", "timetable",) + list_display = ("name", "timetable", "hidden",) + list_editable = ("hidden",) list_filter = ("timetable",) ordering = ("timetable",) readonly_fields = ("celcat_name", "mention", "subgroup", "td", "tp",) + actions = (make_hidden, make_visible,) @admin.register(Room) diff --git a/models.py b/models.py index 649368e..8bd2399 100644 --- a/models.py +++ b/models.py @@ -71,13 +71,13 @@ class GroupManager(Manager): parent_in = self.get_queryset().filter(parent=group) return self.get_queryset().filter(Q(parent=group) | Q(parent__in=parent_in)) \ .annotate(children_count=Count("children")) \ - .filter(children_count=0) \ + .filter(children_count=0, hidden=False) \ .order_by("name") def get_relevant_groups(self, *args, **criteria): return self.get_queryset().filter(*args, **criteria) \ .annotate(children_count=Count("children")) \ - .filter(children_count=0) + .filter(children_count=0, hidden=False) class Group(models.Model): @@ -99,6 +99,8 @@ class Group(models.Model): slug = models.SlugField(max_length=64, default="") + hidden = models.BooleanField(verbose_name="caché", default=False) + def corresponds_to(self, timetable_id, mention, subgroup, td, tp): return self.timetable.id == timetable_id and \ self.mention.startswith(mention) and \ -- cgit v1.2.1 From 62c45f003e338440061c0d3e8356f2ab940e6edc Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 1 Oct 2017 18:58:34 +0200 Subject: Re-changement de la valeur par défaut du nom des cours --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models.py b/models.py index 8bd2399..ec141a2 100644 --- a/models.py +++ b/models.py @@ -175,7 +175,8 @@ class CourseManager(Manager): class Course(models.Model): objects = CourseManager() - name = models.CharField(max_length=255, verbose_name="nom", default="Cours") + name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom", + null=True) type_ = models.CharField(name="type", max_length=255, verbose_name="type de cours", null=True) timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, -- cgit v1.2.1 From ef693288dab169d6a45cb0d02635187c21c3b0ab Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 2 Oct 2017 15:23:27 +0200 Subject: Affichage de la semaine prochaine dans l’emploi du temps général le dimanche --- views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views.py b/views.py index 4fc0147..bd93712 100644 --- a/views.py +++ b/views.py @@ -21,7 +21,7 @@ from django.http import Http404 from django.shortcuts import get_object_or_404, render from .models import Timetable, Group, Course, Year -from .utils import get_current_week, get_week, group_courses +from .utils import get_current_week, get_current_or_next_week, get_week, group_courses def index(request): years = Year.objects.order_by("name") @@ -64,7 +64,7 @@ def group_list(request, year_slug, timetable_slug): return group_list_common(request, timetable, groups) def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): - current_year, current_week = get_current_week() + current_year, current_week = get_current_or_next_week() is_old_timetable = False if year is None or week is None: -- cgit v1.2.1 From 175fcd46f56d8c1c5d10a50b401c09c25cfcdf82 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 2 Oct 2017 15:36:38 +0200 Subject: Changement de la regex des groupes pour prendre en compte n’importe quel caractère --- utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/utils.py b/utils.py index c07c74f..bd337e4 100644 --- a/utils.py +++ b/utils.py @@ -48,19 +48,19 @@ def group_courses(courses): def parse_group(name): # Explication de la regex # - # ^([\w\- ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$ - # ^ début de la ligne - # ([\w\- ]+?) correspond à au moins un caractère - # \s+ un ou plusieurs espaces - # ((CM(\w))| correspond à CM suivi d'une lettre ou… - # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… - # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres - # )? groupe optionnel - # (\s+ un ou plusieurs espaces - # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses - # ? groupe optionnel - # $ fin de la ligne - group_regex = re.compile(r"^([\w\- ]+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$") + # ^(.+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$ + # ^ début de la ligne + # (.+?) correspond à au moins un caractère + # \s+ un ou plusieurs espaces + # ((CM(\w))| correspond à CM suivi d'une lettre ou… + # (TD(\w)(\d))| … à TD suivi d’une lettre et d'un chiffre ou… + # (TP(\w)(\d)(\d)) … à TP suivi d’une lettre et de deux chiffres + # )? groupe optionnel + # (\s+ un ou plusieurs espaces + # \(.+\)) un ou plusieurs caractères quelconques entre parenthèses + # ? groupe optionnel + # $ fin de la ligne + group_regex = re.compile(r"^(.+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$") search = group_regex.search(name) if search is None: return name, None, None, None -- cgit v1.2.1