diff options
| author | Alban Gruin | 2017-11-07 18:04:31 +0100 | 
|---|---|---|
| committer | Alban Gruin | 2017-11-07 18:04:31 +0100 | 
| commit | ffffd9842dbaba0b0e89ff5f434f45792e2b73b6 (patch) | |
| tree | 8e771d2e87e3fcc28efd1bd3beb4583af8231f88 /management | |
| parent | ae3358c1296a02352409910a9ffcc2307d5ea87a (diff) | |
| parent | 04ca9906dccbfd290a08aef037873302ff7eac3d (diff) | |
Merge branch 'stable/0.11.z' into prod/pa1ch/0.y.zv0.11.0-pa1ch
Diffstat (limited to 'management')
| -rw-r--r-- | management/commands/_private.py | 73 | ||||
| -rw-r--r-- | management/commands/timetables.py | 78 | 
2 files changed, 83 insertions, 68 deletions
diff --git a/management/commands/_private.py b/management/commands/_private.py index 8f195a1..b663454 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -23,51 +23,16 @@ from edt.models import Group, Room, Course  from edt.utils import get_week  import requests +import edt  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 consolidate_group(group): -    group_content_key = ("mention", "subgroup", "td", "tp") -    group_content_list = group.group_info[1:] - -    if group.subgroup is not None: -        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_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 is None: -            consolidate_group(group) - -def delete_courses_in_week(timetable, year, week): +def delete_courses_in_week(timetable, year, week, today):      start, end = get_week(year, week) -    Course.objects.filter(begin__gte=start, begin__lt=end, +    Course.objects.filter(begin__gte=max(start, today), begin__lt=end,                            timetable=timetable).delete()  def get_from_db_or_create(cls, **kwargs): @@ -80,7 +45,7 @@ def get_from_db_or_create(cls, **kwargs):      return obj -def get_event(timetable, event, event_week): +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 @@ -88,31 +53,34 @@ def get_event(timetable, event, event_week):      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, on les -    # « consolide », puis on les insère dans l’objet cours. +    # 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")] -    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 +    # On récupère le champ « nom »      if event.resources.module is not None:          course.name = event.resources.module.item.text -    else: +    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, on déplace le champ « remarque » de -        # l’objet dans le champ « nom ». -        course.name, course.notes = course.notes, None +        # 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: @@ -128,7 +96,7 @@ def get_event(timetable, event, event_week):      return course -def get_events(timetable, soup, weeks_in_soup, year=None, week=None): +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"): @@ -142,7 +110,11 @@ 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: -            yield get_event(timetable, event, event_week) +            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 @@ -187,7 +159,8 @@ def get_weeks(soup):      return weeks  def get_xml(url): -    req = requests.get(url) +    user_agent = "celcatsanitizer/" + edt.VERSION +    req = requests.get(url, headers={"User-Agent": user_agent})      req.encoding = "utf8"      soup = BeautifulSoup(req.content, "html.parser") diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 76f0a7c..ff00c8f 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -14,42 +14,80 @@  #    along with celcatsanitizer.  If not, see <http://www.gnu.org/licenses/>.  import datetime +import traceback  from django.core.management.base import BaseCommand  from django.db import transaction  from django.db.models import Min -from django.utils import timezone  from edt.models import Course, Timetable -from edt.utils import get_week +from edt.utils import get_week, tz_now  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): -    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"] +    # Si on force la mise à jour, on définit de moment +    # de la mise à jour au début de la semaine +    if force and year is not None and week is not None: +        today = begin +    elif force: +        # Si la mise à jour est faite sur tout l’emploi du temps, +        # alors la date de début est indéfinie. +        today = None +    else: +        today = tz_now() + +    # On récupère la mise à jour la plus ancienne dans les cours de l’emploi du temps +    last_update_date = Course.objects.filter(timetable=timetable) + +    if today is not None: +        # Cette date concerne les éléments commençant à partir d’aujourd’hui si la valeur +        # n’est pas nulle. +        last_update_date = last_update_date.filter(begin__gte=today) + +    if year is not None and week is not None: +        # Si jamais on traite une semaine spécifique, on limite les cours sélectionnés +        # à ceux qui commencent entre le début du traitement et la fin de la semaine +        last_update_date = last_update_date.filter(begin__lt=end) + +    last_update_date = last_update_date.aggregate(Min("last_update")) \ +                       ["last_update__min"] + +    # Date de mise à jour de Celcat, utilisée à des fins de statistiques      new_update_date = get_update_date(soup) +    # On ne fait pas la mise à jour si jamais la dernière date de MàJ est plus récente +    # que celle indiquée par Celcat. +    # Attention, le champ last_update de la classe Course représente l’heure à laquelle +    # le cours a été inséré dans la base de données, et non pas la date indiquée par +    # Celcat.      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      if year is not None and week is not None: -        delete_courses_in_week(timetable, year, week) +        # On efface la semaine à partir de maintenant si jamais +        # on demande le traitement d’une seule semaine +        delete_courses_in_week(timetable, year, week, today)      else: -        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): +        # Sinon, on efface tous les cours à partir de maintenant. +        # Précisément, on prend la plus grande valeur entre la première semaine +        # présente dans Celcat et maintenant. +        delete_from = min(weeks_in_soup.values()) +        if not force: +            # Si jamais on force la MàJ, on efface tout à partir de la première semaine +            delete_from = max(delete_from, today) +        Course.objects.filter(timetable=timetable, begin__gte=delete_from).delete() + +    # Tous les cours commençant sur la période traitée +    # sont parsés, puis enregistrés dans la base de données. +    for course in get_events(timetable, soup, weeks_in_soup, today, year, week):          course.save() +    # On renseigne la date de mise à jour de Celcat, à des fins de statistiques      timetable.last_update_date = new_update_date      timetable.save() @@ -80,16 +118,16 @@ class Command(BaseCommand):          if options["all"]:              weeks = None          elif options["week"] is None: -            _, week, day = timezone.now().isocalendar() +            _, week, day = tz_now().isocalendar()              if day >= 6: -                year, week, _ = (timezone.now() + datetime.timedelta(weeks=1)).isocalendar() +                year, week, _ = (tz_now() + datetime.timedelta(weeks=1)).isocalendar()              weeks = [week]          else:              weeks = options["week"]          if not options["all"]:              if options["year"] is None and year is None: -                year = timezone.now().year +                year = tz_now().year              elif year is None:                  year = options["year"][0] @@ -98,9 +136,13 @@ class Command(BaseCommand):              try:                  process_timetable(timetable, options["force"], year, weeks) -            except Exception as exc: +            except KeyboardInterrupt: +                break +            except Exception:                  self.stderr.write( -                    self.style.ERROR("Failed to process {0}: {1}".format(timetable, exc))) +                    self.style.ERROR("Failed to process {0}:".format(timetable)) +                ) +                self.stderr.write(self.style.ERROR(traceback.format_exc()))                  errcount += 1          if errcount == 0:  | 
