# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. # # This program 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. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """Accès aux emplois du temps XXX usage uniquement experimental pour tests implémentations """ import re import icalendar from flask import flash, g, url_for from app import log from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig import app.scodoc.sco_utils as scu def formsemestre_load_calendar( formsemestre: FormSemestre, ) -> icalendar.cal.Calendar | None: """Load ics data, return calendar or None if not configured or not available""" edt_id = formsemestre.get_edt_id() if not edt_id: flash( "accès aux emplois du temps non configuré pour ce semestre (pas d'edt_id)" ) return None edt_ics_path = ScoDocSiteConfig.get("edt_ics_path") if not edt_ics_path.strip(): return None ics_filename = edt_ics_path.format(edt_id=edt_id) try: with open(ics_filename, "rb") as file: log(f"Loading edt from {ics_filename}") calendar = icalendar.Calendar.from_ical(file.read()) except FileNotFoundError: flash("erreur chargement du calendrier") log( f"formsemestre_load_calendar: ics not found for {formsemestre}\npath='{ics_filename}'" ) return None return calendar _COLOR_PALETTE = [ "#ff6961", "#ffb480", "#f8f38d", "#42d6a4", "#08cad1", "#59adf6", "#9d94ff", "#c780e8", ] def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]: """EDT complet du semestre, comme une liste de dict serialisable en json. Fonction appellée par l'API /formsemestre//edt TODO: spécifier intervalle de dates start et end TODO: cacher ? """ # Correspondances id edt -> id scodoc pour groupes, modules et enseignants edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre) group_colors = { group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1] for i, group_name in enumerate(edt2group) } default_group = formsemestre.get_default_group() edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre) # Chargement du calendier ics calendar = formsemestre_load_calendar(formsemestre) if not calendar: return [] # Génération des événements, avec titre et champs utiles pour l'affichage dans ScoDoc events = [e for e in calendar.walk() if e.name == "VEVENT"] events_dict = [] for event in events: if "DESCRIPTION" in event: # --- Group edt_group = extract_event_group(event) # si pas de groupe dans l'event, prend toute la promo ("tous") group: GroupDescr = ( edt2group.get(edt_group, None) if edt_group else default_group ) background_color = ( group_colors.get(edt_group, "rgb(214, 233, 248)") if group else "lightgrey" ) group_disp = ( f"""
{group.get_nom_with_part(default="promo")}
""" if group else f"""
{edt_group} {scu.EMO_WARNING} non reconnu
""" ) # --- ModuleImpl edt_module = extract_event_module(event) modimpl: ModuleImpl = edt2modimpl.get(edt_module, None) mod_disp = ( f"""
{ modimpl.module.code}
""" if modimpl else f"""
{ scu.EMO_WARNING} {edt_module}
""" ) # --- Lien saisie abs link_abs = ( f"""""" if modimpl and group else "" ) d = { # Champs utilisés par tui.calendar "calendarId": "cal1", "title": extract_event_title(event) + group_disp + mod_disp + link_abs, "start": event.decoded("dtstart").isoformat(), "end": event.decoded("dtend").isoformat(), "backgroundColor": background_color, # Infos brutes pour usage API éventuel "group_id": group.id if group else None, "group_edt_id": edt_group, "moduleimpl_id": modimpl.id if modimpl else None, } events_dict.append(d) return events_dict def extract_event_title(event: icalendar.cal.Event) -> str: """Extrait le titre à afficher dans nos calendriers (si on ne retrouve pas le module ScoDoc) En effet, le titre présent dans l'ics emploi du temps est souvent complexe et peu parlant. Par exemple, à l'USPN, Hyperplanning nous donne: 'Matière : VRETR113 - Mathematiques du sig (VRETR113\nEnseignant : 1234 - M. DUPONT PIERRE\nTD : TDB\nSalle : L112 (IUTV) - L112\n' """ # TODO: fonction ajustée à l'USPN, devra être paramétrable d'une façon ou d'une autre: regexp ? if not event.has_key("DESCRIPTION"): return "-" description = event.decoded("DESCRIPTION").decode("utf-8") # assume ics in utf8 # ici on prend le nom du module m = re.search(r"Matière : \w+ - ([\w\.\s']+)", description) if m and len(m.groups()) > 0: return m.group(1) # fallback: full description return description def extract_event_module(event: icalendar.cal.Event) -> str: """Extrait le code module de l'emplois du temps. Chaine vide si ne le trouve pas. Par exemple, à l'USPN, Hyperplanning nous donne le code 'VRETR113' dans DESCRIPTION 'Matière : VRETR113 - Mathematiques du sig (VRETR113\nEnseignant : 1234 - M. DUPONT PIERRE\nTD : TDB\nSalle : L112 (IUTV) - L112\n' """ # TODO: fonction ajustée à l'USPN, devra être paramétrable d'une façon ou d'une autre: regexp ? if not event.has_key("DESCRIPTION"): return "-" description = event.decoded("DESCRIPTION").decode("utf-8") # assume ics in utf8 # extraction du code: m = re.search(r"Matière : ([A-Z][A-Z0-9]+)", description) if m and len(m.groups()) > 0: return m.group(1) return "" def extract_event_group(event: icalendar.cal.Event) -> str: """Extrait le nom du groupe (TD, ...). "" si pas de match.""" # TODO: fonction ajustée à l'USPN, devra être paramétrable d'une façon ou d'une autre: regexp ? # Utilise ici le SUMMARY # qui est de la forme # SUMMARY;LANGUAGE=fr:TP2 GPR1 - VCYR303 - Services reseaux ava (VCYR303) - 1234 - M. VIENNET EMMANUEL - V2ROM - BUT2 RT pa. ROM - Groupe 1 if not event.has_key("SUMMARY"): return "-" summary = event.decoded("SUMMARY").decode("utf-8") # assume ics in utf8 # extraction du code: m = re.search(r".*- ([\w\s]+)$", summary) if m and len(m.groups()) > 0: return m.group(1).strip() return "" def formsemestre_retreive_modimpls_from_edt_id( formsemestre: FormSemestre, ) -> dict[str, ModuleImpl]: """Construit un dict donnant le moduleimpl de chaque edt_id""" edt2modimpl = {modimpl.get_edt_id(): modimpl for modimpl in formsemestre.modimpls} edt2modimpl.pop("", None) return edt2modimpl def formsemestre_retreive_groups_from_edt_id( formsemestre: FormSemestre, ) -> dict[str, GroupDescr]: """Construit un dict donnant le groupe de chaque edt_id""" edt2group = {} for partition in formsemestre.partitions: edt2group.update({g.get_edt_id(): g for g in partition.groups}) edt2group.pop("", None) return edt2group