Newer
Older
# celcatsanitizer is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# celcatsanitizer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with celcatsanitizer. If not, see <http://www.gnu.org/licenses/>.
import datetime
import re
from bs4 import BeautifulSoup
from django.utils import timezone
from edt.models import Group, Room, Course
import requests
def add_time(date, time):
ptime = datetime.datetime.strptime(time, "%H:%M")
delta = datetime.timedelta(hours=ptime.hour, minutes=ptime.minute)
return date + delta
def delete_courses_in_week(timetable, year, week, today):
Course.objects.filter(begin__gte=max(start, today), begin__lt=end,
Alban Gruin
committed
timetable=timetable).delete()
Alban Gruin
committed
obj = cls.objects.all().filter(**kwargs)
obj = obj.first()
if obj is None:
Alban Gruin
committed
obj = cls(**kwargs)
obj.save()
return obj
def get_event(timetable, event, event_week, today):
Alban Gruin
committed
"""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)
# On ne traite pas le cours si il commence après le moment du traitement
if begin < today:
return
Alban Gruin
committed
# 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
Alban Gruin
committed
groups = [get_from_db_or_create(Group, timetable=timetable,
celcat_name=item.text)
for item in event.resources.group.find_all("item")]
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 champ « nom »
Alban Gruin
committed
if event.resources.module is not None:
course.name = event.resources.module.item.text
elif event.category is not None:
Alban Gruin
committed
# 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, si le cours a un type, il devient son nom.
course.type = event.category.text
# Si il n’a pas de type (mais je ne pense pas que ça soit possible…),
# il obtiendra une valeur par défaut définie à l’avance.
Alban Gruin
committed
# 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, today, year=None, week=None):
"""Récupère tous les cours disponibles dans l’emploi du temps Celcat.
Le traîtement se limitera à la semaine indiquée si il y en a une."""
for event in soup.find_all("event"):
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_num == week and event_week.year == year or \
year is None or week is None) and \
Alban Gruin
committed
event.resources.group is not None and \
event.starttime is not None and event.endtime is not None:
course = get_event(timetable, event, event_week, today)
# On renvoie le cours si il n’est pas nul
if course is not None:
yield course
def get_update_date(soup):
# Explication de la regex
#
# (\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)
# (\d+) au moins un nombre
# / un slash
# (\d+) au moins un nombre
# / un slash
# (\d+) au moins un nombre
# \s+ au moins un espace
# (\d+) au moins un nombre
# : un deux-points
# (\d+) au moins un nombre
# : un deux-points
# (\d+) au moins un nombre
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
day, month, year, hour, minute, second = [int(v) for v in search.groups()]
date = datetime.datetime(year, month, day, hour, minute, second)
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.
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"))
return weeks
def get_xml(url):
user_agent = "celcatsanitizer/" + edt.VERSION
req = requests.get(url, headers={"User-Agent": user_agent})
req.encoding = "utf8"
soup = BeautifulSoup(req.content, "html.parser")