_private.py 6.93 KB
Newer Older
Alban Gruin's avatar
Alban Gruin committed
#    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
Alban Gruin's avatar
Alban Gruin committed
#    (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/>.
from bs4 import BeautifulSoup
from django.utils import timezone
from edt.models import Group, Room, Course
from edt.utils import get_week
import edt
    ptime = datetime.datetime.strptime(time, "%H:%M")
    delta = datetime.timedelta(hours=ptime.hour, minutes=ptime.minute)
def delete_courses_in_week(timetable, year, week, today):
    start, end = get_week(year, week)
    Course.objects.filter(begin__gte=max(start, today), begin__lt=end,
Alban Gruin's avatar
Alban Gruin committed
def get_from_db_or_create(cls, **kwargs):
def get_event(timetable, event, event_week, today):
    """Renvoie une classe Course à partir d’un événement récupéré par BS4"""
    # On récupère la date de l’évènement à partir de la semaine
    # et de la semaine référencée, puis l’heure de début et de fin
    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

    # 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
    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 »
        course.notes = event.notes.text

    # On récupère le champ « nom »
    if event.resources.module is not None:
        course.name = event.resources.module.item.text
    elif event.category is not None:
        # 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.

    # 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 \
           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:
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)
    # 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"))
    user_agent = "celcatsanitizer/" + edt.VERSION
    req = requests.get(url, headers={"User-Agent": user_agent})
    soup = BeautifulSoup(req.content, "html.parser")