diff --git a/colloscope/create_calendar.py b/colloscope/create_calendar.py new file mode 100644 index 0000000..9382481 --- /dev/null +++ b/colloscope/create_calendar.py @@ -0,0 +1,335 @@ +import pandas +import datetime +import icalendar +import uuid +from pathlib import Path + + + +pd_eleves = pandas.read_csv('Resources/eleves-v2.csv', delimiter=";", header=None) +np_eleves = pd_eleves.to_numpy() + +pd_colles = pandas.read_csv('Resources/colloscope-v3.csv', delimiter=";", header=None) +np_colles = pd_colles.to_numpy() + +local_tz = "Europe/Paris" + +jour_to_delta = { + "lundi": 0, + "mardi": 1, + "mercredi": 2, + "jeudi": 3, + "vendredi": 4, +} + +jour_to_byday = { + "lundi": "MO", + "mardi": "TU", + "mercredi": "WE", + "jeudi": "TH", + "vendredi": "FR", +} + + +langues = ["Anglais", "Allemand", "Espagnol"] + +option_langues = { + "EN": ["Anglais LV1"], + "EN-DE": ["Anglais LV1", "Allemand LV2"], + "EN-ES": ["Anglais LV1", "Espagnol LV2"], + "DE-EN": ["Allemand LV1", "Anglais LV2"], + "ES-EN": ["Espangol LV1", "Anglais LV2"] +} + +def display(cal): + return cal.to_ical().decode("utf-8").replace('\r\n', '\n').strip() + + +def open_ics(filename: str) -> icalendar.Calendar: + """ + Ouvre un fichier .ics et renvoie un objet Calendar + """ + p = Path(__file__).with_name('Resources') + p = Path(p, filename) + with p.open('r') as f: + opened_cal = icalendar.Calendar.from_ical(f.read()) + + return opened_cal + + + +def add_events_from_ics(filename: str, new_cal: icalendar.Calendar): + """ + Ajoute des évènements depuis un fichier .ics vers un objet icalendar.Calendar + """ + cal_to_import = open_ics(filename=filename) + + for event in cal_to_import.walk("vevent"): + new_cal.add_component(event) + + return new_cal + + +def get_td_groupe(groupe: str) -> str: + """ + Renvoie le groupe de TD d'un groupe de colle (A, B ou SI) + """ + for row in np_eleves[1:]: + if row[2] == groupe: + return row[3] + + +def debut_semaine_to_datetime(date_string: str) -> datetime.time: + """ + Transforme un string de debut de semaine au format dd/mm/yyyy en datetime.time + """ + return datetime.datetime.strptime(date_string, "%d/%m/%y") + + +def heure_to_timedelta(heure: str) -> datetime.timedelta: + """ + Transforme une heure au format "8h30" ou "1h30" ou "2h" en timedelta. + """ + heure = heure.split("h") + delta = datetime.timedelta(hours=int(heure[0])) + + if heure[1] != '': + delta += datetime.timedelta(minutes=int(heure[1])) + + return delta + +def creer_evenement(titre: str, debut: datetime.datetime, duree: datetime.timedelta, description: str = None, localisation: str = None, rrule: str = None) -> icalendar.Event: + """ + Renvoie un évènement icalendar.Event() + + titre str: le titre de l'évènement + debut datetime.datetime: la date de début + duree datetime.timedelta: la durée + description str: la description (Optionelle) + localisation str: localisation (Optionelle) + rrule str: règle de récurrence https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html (Optionelle) + """ + new_event = icalendar.Event() + new_event.add("summary", titre) + new_event.add("dtstart", debut, parameters={"tzid": local_tz}) + debut += duree + new_event.add("dtend", debut, parameters={"tzid": local_tz}) + + if localisation is not None: + new_event.add("location", localisation) + if description is not None: + new_event.add("description", description) + if rrule is not None: + new_event.add("rrule", rrule) + + new_event.add("uid", str(uuid.uuid4())) + new_event.add("dtstamp", datetime.datetime.now()) + + # nécessaire pour se conformer à la norme RFC 5545 + + return new_event + +def get_colles_groupe(np_colles, groupe, option_langue): + """ + Renvoie les colles pour un groupe sous la forme d'une liste d'évènements ICS + + np_colles: l'array 2D numpy contenant le colloscope + groupe: str, le numéro du groupe + option_langue: str, les options de langue + """ + liste_colles = [] + for row in np_colles[4:40]: + if pandas.isnull(row[1]) or row[1] == "pas de colle": + # il n'y a pas de colle, on skip ! + continue + + + # Si la colle n'est pas la LV1 de l'élève, on skip + if row[1] == "Anglais" and not option_langue.startswith("EN"): + continue + elif row[1] == "Allemand" and not option_langue.startswith("DE"): + continue + elif row[1] == "Espagnol" and not option_langue.startswith("ES"): + continue + + for index, colle in enumerate(row[8:]): + if pandas.isnull(colle): + continue + # les huit premières cases ne sont pas concernées + + if "," in list(colle): + if groupe not in colle.split(","): + continue + elif colle != groupe: + continue + # dans le cas où plusieurs groupes de colles soient concernés + + duree = heure_to_timedelta(row[4]) + # transforme la durée en timedelta + + date = debut_semaine_to_datetime(np_colles[1][index+8]) + + date += datetime.timedelta(days=jour_to_delta[row[2]]) + date += heure_to_timedelta(row[3]) + # génère la date à partir de la semaine, du jour et de l'heure + + localisation = row[7] if not pandas.isnull(row[7]) else None + + new_colle = creer_evenement( + titre=f"Colle {row[1]}", + description=f"{row[5]}" if not pandas.isnull(row[5]) else None, + localisation=localisation, + debut=date, + duree=duree + ) + + liste_colles.append(new_colle) + + return liste_colles + +def get_cours(np_colles, groupe): + """ + Renvoie les colles pour un groupe sous la forme d'une liste d'évènements ICS, cad + icalendar.Event + + np_colles: l'array 2D numpy contenant le colloscope + groupe: str, le numéro du groupe + """ + listes_cours = [] + groupe_td = get_td_groupe(groupe) + + for row in np_colles[41:66]: + + if pandas.isnull(row[1]): + continue + + first_date = np_colles[1][8] + + eleves = row[8] + + + + if "à" in list(eleves): + + eleves = eleves.split(" ") + debut, fin = [grp for grp in eleves if grp.isdigit()] + + if not int(debut) <= int(groupe) <= int(fin): + continue + + elif "+" in list(eleves): + eleves = eleves.split("+") + + if groupe not in [grp for grp in eleves if grp.isdigit()]: + + if groupe_td not in [grp for grp in eleves]: + if int(row[6]) > 1: + if groupe in [grp for grp in row[9].split("+") if grp.isdigit()]: + first_date = np_colles[1][9] + elif groupe_td in [grp for grp in row[9].split("+")]: + first_date = np_colles[1][9] + else: + + continue + + else: + continue + + # si la 1ère occurence du cours est la 1ère ou la 2e semaine + + + elif groupe != eleves and groupe_td != eleves: + if pandas.isnull(row[9]) or (groupe != row[9] and groupe_td != row[9]): + continue + else: + first_date = np_colles[1][9] + + # vérification que le groupe est concerné par le cours + + first_date = debut_semaine_to_datetime(first_date) + + first_date += heure_to_timedelta(row[3]) + first_date += datetime.timedelta(days=jour_to_delta[row[2]]) + + duree = heure_to_timedelta(row[4]) + + localisation = row[7] if not pandas.isnull(row[7]) else None + + last_date = debut_semaine_to_datetime(np_colles[1][-1]) + datetime.timedelta(days=5) + + rrule = {"FREQ": "WEEKLY", "UNTIL": last_date, "INTERVAL": row[6], "BYDAY": jour_to_byday[row[2]], "WKST": "MO"} + + new_cours = creer_evenement( + titre=row[1], + description=row[5], + localisation=localisation, + debut=first_date, + duree=duree, + rrule=rrule + ) + + listes_cours.append(new_cours) + + return listes_cours + +def get_langues(langues): + """ + Renvoie la liste de cours de langue associés sous forme de list[icalendar.Event] + """ + liste_cours = [] + langues = option_langues[langues] + + for row in np_colles[67:]: + + if row[1] in langues: + + debut = debut_semaine_to_datetime(np_colles[1][8]) + debut += heure_to_timedelta(row[3]) + debut += datetime.timedelta(days=jour_to_delta[row[2]]) + + duree = heure_to_timedelta(row[4]) + + last_date = debut_semaine_to_datetime(np_colles[1][-1]) + datetime.timedelta(days=5) + + rrule = {"FREQ": "WEEKLY", "UNTIL": last_date, "INTERVAL": row[6], "BYDAY": jour_to_byday[row[2]], "WKST": "MO"} + + new_cours = creer_evenement( + titre=row[1], + debut=debut, + duree=duree, + rrule=rrule + ) + + liste_cours.append(new_cours) + + return liste_cours + + +def get_calendar(groupe: str, langues: str): + """" + Renvoie un calendrier (icalendar.Calendar) pour un groupe avec des langues donné + """ + + new_cal_edt = open_ics("Base_Calendar.ics") + + # if split: + # new_cal_colles = open_ics("Base_Calendar.ics") + + + for event in get_langues(langues): + new_cal_edt.add_component(event) + + for event in get_cours(np_colles, groupe): + new_cal_edt.add_component(event) + + # if split: + # for event in get_colles_groupe(np_colles, groupe, langues): + # new_cal_colles.add_component(event) + # return new_cal_edt.to_ical(), new_cal_colles.to_ical() + + # else: + # for event in get_colles_groupe(np_colles, groupe, langues): + # new_cal_edt.add_component(event) + + + return new_cal_edt.to_ical() \ No newline at end of file diff --git a/colloscope/icalexport.py b/colloscope/icalexport.py index b7ebc62..ec0bd40 100644 --- a/colloscope/icalexport.py +++ b/colloscope/icalexport.py @@ -5,11 +5,24 @@ from os import path from icalendar import Calendar, Event, vCalAddress, vText from colloscope.models import * +from create_calendar import get_calendar LOCAL_TZ = "Europe/Paris" -def to_calendar(etudiant, periode): +def emailize(nom, prenom=None): + if prenom is not None: + return "{}.{}@example.com" \ + .format( + prenom.replace(" ", "_").lower(), + nom.replace(" ", "_").lower() + ) + else: + return "{}@example.com" \ + .format(nom.replace(" ", "_").lower()) + + +def to_calendar(etudiant, periode, include_EDT: bool = True): p = path.abspath('./static/Base_Calendar.ics') @@ -41,16 +54,16 @@ def to_calendar(etudiant, periode): event.add("location", f"{rotation.creneau.salle} ({rotation.creneau.periode.classe.lycee})") event.add("categories", "COLLE-" + str(rotation.creneau.matiere)) - description = "Groupes: {','.join(str(groupe) for groupe in rotation.groupes.all())}" + description = f"Groupes: {','.join(str(groupe) for groupe in rotation.groupes.all())}" event.add(description) - organizer = vCalAddress("mailto:unknown@mp2i-vms.fr") + organizer = vCalAddress(f"mailto:{emailize(rotation.creneau.colleur.nom)}") organizer.params["cn"] = vText(str(rotation.creneau.colleur)) organizer.params["role"] = vText("Colleur") event.add("organizer", organizer) for e in rotation.groupe_effectif(): - attendee = vCalAddress("mailto:unknown@mp2i-vms.fr") + attendee = vCalAddress("mailto:{emailize(e.nom, prenom=e.prenom)}") attendee.params["role"] = vText("Etudiant") attendee.params["cn"] = vText(str(e)) @@ -58,4 +71,11 @@ def to_calendar(etudiant, periode): cal.add_component(event) + if include_EDT: + #TODO: get le groupe de l'étudiant et ses langue + edt = get_calendar() + + for event in edt.walk("VEVENT"): + cal.add_component(event) + return cal diff --git a/colloscope/views.py b/colloscope/views.py index de18946..fdeac66 100644 --- a/colloscope/views.py +++ b/colloscope/views.py @@ -186,7 +186,11 @@ def icalendar(request): try: lien = LienCalendrier.objects.get(code=request.GET.get("key")) - return HttpResponse(to_calendar(lien.etudiant, lien.periode).to_ical(), content_type="text/calendar") + if not request.GET.get("edt"): + return HttpResponse(to_calendar(lien.etudiant, lien.periode, include_EDT=True).to_ical(), content_type="text/calendar") + + return HttpResponse(to_calendar(lien.etudiant, lien.periode, include_EDT=True).to_ical(), content_type="text/calendar") + except LienCalendrier.DoesNotExist: return HttpResponse("Invalid key", status=404) else: