aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md19
-rw-r--r--management/commands/_private.py22
-rw-r--r--management/commands/cleancourses.py39
-rw-r--r--management/commands/listtimetables.py39
-rw-r--r--management/commands/sendmails.py33
-rw-r--r--management/commands/timetables.py11
-rw-r--r--models.py22
-rw-r--r--templates/mail/mail_timetable.txt11
-rw-r--r--templates/timetable.html3
-rw-r--r--templatetags/dt_week.py2
-rw-r--r--templatetags/rooms.py27
-rw-r--r--urls.py3
-rw-r--r--utils.py27
-rw-r--r--views.py5
14 files changed, 188 insertions, 75 deletions
diff --git a/README.md b/README.md
index afa2b35..ece8601 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Pour tester celcatsanitizer, il est recommandé d'utiliser SQLite ou PostgreSQL.
Pour la production, il est recommandé d'utiliser PostgreSQL (avec le driver psycopg2) et de mettre le tout dans un environnement virtuel.
-Aucun autre SGBD n'a été testé aujourd'hui. Toute modification visant à faire fonctionner celcatsanitizer avec un autre SGBD sera bien entendu acceptée.
+Aucun autre SGBD n'a été testé. Toute modification visant à faire fonctionner celcatsanitizer avec un autre SGBD sera bien entendu acceptée.
### Installation
Il est préférable d'utiliser un environnement virtuel, mais ce n'est pas obligatoire. Si vous ne souhaitez pas utiliser un environnement virtuel, passez directement à l'installation des dépendances.
@@ -60,13 +60,7 @@ SQLite n'a pas besoin de driver.
> $ git clone https://git.pa1ch.fr/alban/celcatsanitizer.git edt
-Pour la production, il est recommandé d'utiliser une version stable, accessibles à travers les tags git :
-
-> $ cd edt
-
-> $ git checkout v0.4.0
-
-> $ cd ..
+Pour la production, il est recommandé d'utiliser une version stable, accessibles à travers les tags git.
#### Configuration de Django
Dans le fichier celcatsanitizer/settings.py, vous devrez renseigner plusieurs informations.
@@ -74,8 +68,6 @@ Dans le fichier celcatsanitizer/settings.py, vous devrez renseigner plusieurs in
##### Configuration du serveur mail
[Vous pouvez trouver la documentation concernant l'envoi des mails sur le site de Django.](https://docs.djangoproject.com/fr/1.10/topics/email/)
-Pour l'instant, la désactivation des mails n'est pas encore supporté.
-
##### Configuration des administrateurs
[Vous pouvez retrouver la documentation de la variable ADMIN sur le site de Django.](https://docs.djangoproject.com/fr/1.10/ref/settings/#admins)
@@ -106,9 +98,14 @@ Vous avez besoin de générer les migrations de celcatsanitizer, puis appliquez-
> $ ./manage.py migrate
+##### Gestion des fichiers statiques
+Si vous êtes en production, vous devez renseigner l'emplacement de vos fichiers statiques dans la variable [STATIC_ROOT](https://docs.djangoproject.com/fr/1.10/ref/settings/#std:setting-STATIC_ROOT) de la configuration de Django, puis exécuter la commande suivante :
+
+> $ ./manage.py collectstatic
+
### Lancement de celcatsanitizer
Si vous êtes en mode de débuggage, lancez le serveur de cette manière :
> $ ./manage.py runserver
-Si vous êtes en production, il n'est pas recommandé d'utiliser ce serveur. Exécutez Django avec le module mod_wsgi d'Apache, ou avec le serveur gunicorn, derrière un serveur nginx.
+Si vous êtes en production, il n'est pas recommandé d'utiliser ce serveur. Exécutez Django avec le module mod_wsgi d'Apache, ou avec un serveur [gunicorn](http://gunicorn.org/) derrière nginx.
diff --git a/management/commands/_private.py b/management/commands/_private.py
index f14dae6..3cd23ca 100644
--- a/management/commands/_private.py
+++ b/management/commands/_private.py
@@ -27,7 +27,8 @@ import requests
class Week:
def __init__(self, number, start):
self.number = number
- self.start = timezone.make_aware(datetime.datetime.strptime(start, "%d/%m/%Y"))
+ self.start = timezone.make_aware(
+ datetime.datetime.strptime(start, "%d/%m/%Y"))
def get_day(self, id):
return self.start + datetime.timedelta(id)
@@ -45,20 +46,17 @@ def delete_courses_in_week(timetable, year, week):
Course.objects.filter(begin__gte=start, begin__lt=end,
timetable=timetable).delete()
-def get_from_db_or_create(cls, timetable=None, **kwargs):
+def get_from_db_or_create(cls, **kwargs):
obj = cls.objects.all().filter(**kwargs)
- if timetable is not None:
- obj = obj.filter(timetable=timetable)
obj = obj.first()
if obj is None:
obj = cls(**kwargs)
- obj.timetable = timetable
obj.save()
return obj
-def get_events(soup, weeks, year, week, timetable):
+def get_events(timetable, year, week, soup, weeks_in_soup):
for event in soup.find_all("event"):
title = None
type_ = None
@@ -66,18 +64,19 @@ def get_events(soup, weeks, year, week, timetable):
rooms = None
notes = None
- if weeks[event.rawweeks.text].number == week and \
- weeks[event.rawweeks.text].year == year and \
+ if weeks_in_soup[event.rawweeks.text].number == week and \
+ weeks_in_soup[event.rawweeks.text].year == year and \
event.resources.group is not None and \
event.starttime is not None and event.endtime is not None:
- date = weeks[event.rawweeks.text].get_day(int(event.day.text))
+ date = weeks_in_soup[event.rawweeks.text].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"))
- groups = [get_from_db_or_create(Group, timetable,
+ groups = [get_from_db_or_create(Group, timetable=timetable,
celcat_name=item.text)
for item in event.resources.group.find_all("item")]
@@ -104,7 +103,8 @@ def get_events(soup, weeks, year, week, timetable):
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] = Week(int(span.title.text),
+ span["date"])
return weeks
diff --git a/management/commands/cleancourses.py b/management/commands/cleancourses.py
new file mode 100644
index 0000000..5f71861
--- /dev/null
+++ b/management/commands/cleancourses.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 Alban Gruin
+#
+# celcatsanitizer is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.core.management.base import BaseCommand
+from django.db import transaction
+from edt.models import Course, Group, LastUpdate
+
+
+class Command(BaseCommand):
+ help = "Remove all courses and groups from the database"
+
+ def add_arguments(self, parser):
+ parser.add_argument("--timetable", type=int, nargs="+")
+
+ def handle(self, *args, **options):
+ with transaction.atomic():
+ 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/listtimetables.py b/management/commands/listtimetables.py
new file mode 100644
index 0000000..c5ef41f
--- /dev/null
+++ b/management/commands/listtimetables.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 Alban Gruin
+#
+# celcatsanitizer is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.core.management.base import BaseCommand
+from edt.models import Timetable
+
+
+class Command(BaseCommand):
+ help = "List timetables in the database"
+
+ def add_arguments(self, parser):
+ parser.add_argument("--order-by-id", action="store_true")
+
+ def handle(self, *args, **options):
+ timetables = Timetable.objects.all()
+ if options["order_by_id"]:
+ print("oui")
+ timetables = timetables.order_by("id")
+ else:
+ timetables = timetables.order_by("name")
+
+ for timetable in timetables:
+ self.stdout.write("{0} (id: {1})".format(timetable, timetable.id))
+
+ self.stdout.write("")
+ self.stdout.write(self.style.SUCCESS("Done."))
diff --git a/management/commands/sendmails.py b/management/commands/sendmails.py
index 5a2054f..c7725b7 100644
--- a/management/commands/sendmails.py
+++ b/management/commands/sendmails.py
@@ -14,21 +14,22 @@
# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import BaseCommand
from django.core.mail import send_mass_mail
-from django.utils import timezone, translation
+from django.utils import translation
from django.template import Context, loader
from django.conf import settings
-from edt.models import Group, Subscription, Course
+from edt.models import Course, Subscription
from edt.utils import get_current_or_next_week, get_week, group_courses
-import datetime
-
class Command(BaseCommand):
help = "Sends emails to subscribed users"
+ def add_arguments(self, parser):
+ parser.add_argument("--test", help="Print the content of mails instead of sending them", action="store_true")
+
def handle(self, *args, **options):
translation.activate(settings.LANGUAGE_CODE)
@@ -38,26 +39,30 @@ class Command(BaseCommand):
subscriptions = Subscription.objects.filter(active=True)
content = {}
mails = []
+
footer = loader.get_template("mail/mail_footer.txt")
+ timetable = loader.get_template("mail/mail_timetable.txt")
print("Generating messages...")
for subscription in subscriptions:
if subscription.group.id not in content:
courses = Course.objects.get_courses_for_group(subscription.group, begin__gte=start, begin__lt=end)
- if len(courses) > 0:
- grouped_courses = group_courses(courses)
+ grouped_courses = group_courses(courses)
+ context = Context({"courses": grouped_courses, "group": subscription.group, "week": week})
+ content[subscription.group.id] = timetable.render(context)
- template = loader.get_template("mail/mail_timetable.txt")
- context = Context({"subscription": subscription, "courses": grouped_courses, "week": week})
- content[subscription.group.id] = template.render(context)
+ if options["test"]:
+ print(subscription.group)
+ print(content[subscription.group.id])
- if subscription.group.id in content:
+ if not options["test"]:
context = Context({"admins": settings.ADMINS, "token": subscription.token, "domain": settings.DEFAULT_DOMAIN})
mail_content = content[subscription.group.id] + footer.render(context)
-
mails.append(("{0} - {1} - Semaine {2}".format(subscription.group.timetable.name, subscription.group.name, week), mail_content, settings.DEFAULT_FROM_EMAIL, [subscription.email],))
- print("Sending mails...")
- send_mass_mail(mails)
+ if not options["test"]:
+ print("Sending mails...")
+ send_mass_mail(mails)
+
self.stdout.write(self.style.SUCCESS("Done."))
diff --git a/management/commands/timetables.py b/management/commands/timetables.py
index 88d9008..d39075d 100644
--- a/management/commands/timetables.py
+++ b/management/commands/timetables.py
@@ -14,22 +14,19 @@
# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils import timezone
-from edt.models import Timetable, LastUpdate, Group, Room, Course
+from edt.models import Timetable, LastUpdate, Course
-from bs4 import BeautifulSoup
-
-from ._private import delete_courses_in_week, get_events, get_weeks, get_xml, Week
+from ._private import delete_courses_in_week, get_events, get_weeks, get_xml
import datetime
-import requests
@transaction.atomic
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(soup, weeks_in_soup, year, week, timetable):
+ 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_
diff --git a/models.py b/models.py
index 1e96a88..478b48f 100644
--- a/models.py
+++ b/models.py
@@ -20,6 +20,8 @@ from django.db.models.expressions import RawSQL
from django.db.models.functions import Extract, ExtractYear
from django.utils.text import slugify
+from .utils import parse_group
+
import hashlib
import os
@@ -81,23 +83,9 @@ class Group(models.Model):
return self.name
def save(self, *args, **kwargs):
- try:
- parts = self.celcat_name.split("-")[-2:]
- group = parts[1].strip()[2:]
-
- self.mention = parts[0].strip()
- self.subgroup = group[0]
-
- if len(group) > 1:
- self.td = int(group[1])
- if len(group) > 2:
- self.tp = int(group[2])
- except:
- print("Malformed name: {0}. Ignoring".format(self.celcat_name))
- finally:
- if self.name == "":
- self.name = self.celcat_name
-
+ self.mention, self.subgroup, self.td, self.tp = parse_group(self.celcat_name)
+ if self.name == "":
+ self.name = self.celcat_name
self.slug = slugify(self.name)
super(Group, self).save()
diff --git a/templates/mail/mail_timetable.txt b/templates/mail/mail_timetable.txt
index 65b5c01..f6b8364 100644
--- a/templates/mail/mail_timetable.txt
+++ b/templates/mail/mail_timetable.txt
@@ -1,6 +1,7 @@
-{% autoescape off %}{% for day in courses %}{% filter title %}{{ day.0.begin|date:"l j F o" }}{% endfilter %} - de {{ day.0.begin|date:"H:i" }} à {% with day|last as last %}{{ last.end|date:"H:i" }}{% endwith %}
-{% for course in day %}
- * {{ course.name }} ({{ course.type }}), de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}
- Salle{% if course.rooms.all|length > 1 %}s{% endif %} {% for room in course.rooms.all %}{{ room }} {% endfor %}{% endif %}
-{% endfor %}
+{% load rooms %}{% autoescape off %}{% for day in courses %}{% filter title %}{{ day.0.begin|date:"l j F o" }}{% endfilter %} - de {{ day.0.begin|date:"H:i" }} à {% with day|last as last %}{{ last.end|date:"H:i" }}{% endwith %}
+{% for course in day %} * {{ course.name }} ({{ course.type }}), de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}
+ {{ course.rooms.all|format_rooms }}{% endif %}{% if course.notes is not None %}
+ Remarques : {{ course.notes }}{% endif %}
+
+{% endfor %}{% empty %}Aucun cours pour le groupe {{ group }} pendant la semaine {{ week }}.
{% endfor %}{% endautoescape %}
diff --git a/templates/timetable.html b/templates/timetable.html
index 0d3432c..78dcfed 100644
--- a/templates/timetable.html
+++ b/templates/timetable.html
@@ -1,4 +1,5 @@
{% extends "index.html" %}
+{% load rooms %}
{% block title %}{{ group.timetable.name }} – {{ group.name }} – Semaine {{ week }} – {% endblock %}
@@ -8,7 +9,7 @@
<section>
<h3>{% filter title %}{{ day.0.begin|date:"l j F o" }}{% endfilter %} &ndash; de {{ day.0.begin|date:"H:i" }} à {% with day|last as last %}{{ last.end|date:"H:i" }}{% endwith %}</h3>
<ul>{% for course in day %}
- <li class="course"><b>{{ course.name }}</b> ({{ course.type }}), de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}<br /><em>Salle{% if course.rooms.all|length > 1 %}s{% endif %} {% for room in course.rooms.all %}{{ room }} {% endfor %}</em>{% endif %}</li>{% endfor %}
+ <li class="course"><b>{{ course.name }}</b> ({{ course.type }}), de {{ course.begin|date:"H:i" }} à {{ course.end|date:"H:i" }}{% if course.rooms.all|length > 0 %}<br /><em>{{ course.rooms.all|format_rooms }}</em>{% endif %}{% if course.notes is not None %}<br /><small>Remarques : {{ course.notes }}</small>{% endif %}</li>{% endfor %}
</ul>
</section>{% endfor %}
<p><a class="subscribe" href="{% url "subscribe" group.timetable.slug group.slug year week %}">S'abonner à cet emploi du temps</a></p>{% endblock %}
diff --git a/templatetags/dt_week.py b/templatetags/dt_week.py
index 97d9b8b..eae5523 100644
--- a/templatetags/dt_week.py
+++ b/templatetags/dt_week.py
@@ -16,8 +16,6 @@
from django import template
-import datetime
-
register = template.Library()
@register.filter
diff --git a/templatetags/rooms.py b/templatetags/rooms.py
new file mode 100644
index 0000000..d76f0ee
--- /dev/null
+++ b/templatetags/rooms.py
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 Alban Gruin
+#
+# celcatsanitizer is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django import template
+
+register = template.Library()
+
+@register.filter
+def format_rooms(rooms):
+ joined = ", ".join([str(room) for room in rooms])
+ if len(rooms) > 1:
+ return "Salles {0}".format(joined)
+ else:
+ return "Salle {0}".format(joined)
diff --git a/urls.py b/urls.py
index 38c33a0..5557612 100644
--- a/urls.py
+++ b/urls.py
@@ -14,8 +14,7 @@
# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from django.conf.urls import url, handler404
-
+from django.conf.urls import url
from . import views
urlpatterns = [
diff --git a/utils.py b/utils.py
index 4ce3a21..cff4c9c 100644
--- a/utils.py
+++ b/utils.py
@@ -17,6 +17,7 @@
from django.utils import timezone
import datetime
+import re
def get_current_week():
return timezone.now().isocalendar()[:2]
@@ -44,3 +45,29 @@ def group_courses(courses):
grouped_courses[-1].append(course)
return grouped_courses
+
+def parse_group(name):
+ # Explication de la regex
+ #
+ # ^(.+?)\s*\-\s*(((CM)(\w))|((TD)(\w)(\d))|((TP)(\w)(\d)(\d)))$
+ # ^ début de la ligne
+ # (.+?) correspond à au moins un caractère
+ # \s* zéro, un ou plusieurs espaces
+ # \- un tiret
+ # \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
+ # $ fin de la ligne
+ group_regex = re.compile("^(.+?)\s*\-\s*(((CM)(\w))|((TD)(\w)(\d))|((TP)(\w)(\d)(\d)))$")
+ search = group_regex.search(name)
+ if search is None:
+ return None, None, None, None
+
+ parts = search.groups(0)
+ if parts[3] == "CM":
+ return parts[0], parts[4], None, None
+ elif parts[6] == "TD":
+ return parts[0], parts[7], parts[8], None
+ elif parts[10] == "TP":
+ return parts[0], parts[11], parts[12], parts[13]
diff --git a/views.py b/views.py
index 651533a..22c08ac 100644
--- a/views.py
+++ b/views.py
@@ -14,10 +14,7 @@
# with celcatsanitizer; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from django.http import HttpResponse, Http404
-from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect, render
-from django.utils import timezone
from django.core.mail import send_mail
from django.conf import settings
from django.template import Context, loader
@@ -26,8 +23,6 @@ from .forms import SubscribeForm
from .models import Timetable, LastUpdate, Group, Subscription, Course
from .utils import get_current_week, get_week, group_courses
-import datetime
-
def index(request):
timetables = Timetable.objects.all().order_by("name")
groups = Group.objects.filter(tp__isnull=False).order_by("name")