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