forked from ScoDoc/ScoDoc
Merge branch 'edt' of https://scodoc.org/git/viennet/ScoDoc
This commit is contained in:
commit
bd860295ba
@ -192,15 +192,16 @@ class ConfigAssiduitesForm(FlaskForm):
|
|||||||
validators=[Optional(), check_ics_regexp],
|
validators=[Optional(), check_ics_regexp],
|
||||||
)
|
)
|
||||||
edt_ics_uid_field = StringField(
|
edt_ics_uid_field = StringField(
|
||||||
label="Champ contenant l'enseignant",
|
label="Champ contenant les enseignants",
|
||||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||||
validators=[Optional(), check_ics_field],
|
validators=[Optional(), check_ics_field],
|
||||||
)
|
)
|
||||||
edt_ics_uid_regexp = StringField(
|
edt_ics_uid_regexp = StringField(
|
||||||
label="Extraction de l'enseignant",
|
label="Extraction des enseignants",
|
||||||
description=r"""expression régulière python dont le premier groupe doit
|
description=r"""expression régulière python permettant d'extraire les
|
||||||
correspondre à l'identifiant (edt_id) de l'enseignant associé à l'évènement.
|
identifiants des enseignants associés à l'évènement.
|
||||||
Exemple: <tt>Enseignant : ([0-9]+)</tt>
|
(contrairement aux autres champs, il peut y avoir plusieurs enseignants par évènement.)
|
||||||
|
Exemple: <tt>[0-9]+</tt>
|
||||||
""",
|
""",
|
||||||
validators=[Optional(), check_ics_regexp],
|
validators=[Optional(), check_ics_regexp],
|
||||||
)
|
)
|
||||||
|
@ -280,7 +280,10 @@ class FormSemestre(db.Model):
|
|||||||
raise ScoValueError("Le semestre n'a pas de groupe par défaut")
|
raise ScoValueError("Le semestre n'a pas de groupe par défaut")
|
||||||
|
|
||||||
def get_edt_ids(self) -> list[str]:
|
def get_edt_ids(self) -> list[str]:
|
||||||
"l'ids pour l'emploi du temps: à défaut, les codes étape Apogée"
|
"""Les ids pour l'emploi du temps: à défaut, les codes étape Apogée.
|
||||||
|
Les edt_id de formsemestres ne sont pas normalisés afin de contrôler
|
||||||
|
précisément l'accès au fichier ics.
|
||||||
|
"""
|
||||||
return (
|
return (
|
||||||
scu.split_id(self.edt_id)
|
scu.split_id(self.edt_id)
|
||||||
or [e.etape_apo.strip() for e in self.etapes if e.etape_apo]
|
or [e.etape_apo.strip() for e in self.etapes if e.etape_apo]
|
||||||
|
@ -251,8 +251,11 @@ class GroupDescr(ScoDocModel):
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def get_edt_ids(self) -> list[str]:
|
def get_edt_ids(self) -> list[str]:
|
||||||
"les ids pour l'emploi du temps: à défaut, le nom scodoc du groupe"
|
"les ids normalisés pour l'emploi du temps: à défaut, le nom scodoc du groupe"
|
||||||
return scu.split_id(self.edt_id) or [self.group_name] or []
|
return [
|
||||||
|
scu.normalize_edt_id(x)
|
||||||
|
for x in scu.split_id(self.edt_id) or [self.group_name] or []
|
||||||
|
]
|
||||||
|
|
||||||
def get_nb_inscrits(self) -> int:
|
def get_nb_inscrits(self) -> int:
|
||||||
"""Nombre inscrits à ce group et au formsemestre.
|
"""Nombre inscrits à ce group et au formsemestre.
|
||||||
|
@ -67,11 +67,10 @@ class ModuleImpl(db.Model):
|
|||||||
|
|
||||||
def get_edt_ids(self) -> list[str]:
|
def get_edt_ids(self) -> list[str]:
|
||||||
"les ids pour l'emploi du temps: à défaut, les codes Apogée"
|
"les ids pour l'emploi du temps: à défaut, les codes Apogée"
|
||||||
return (
|
return [
|
||||||
scu.split_id(self.edt_id)
|
scu.normalize_edt_id(x)
|
||||||
or scu.split_id(self.code_apogee)
|
for x in scu.split_id(self.edt_id) or scu.split_id(self.code_apogee)
|
||||||
or self.module.get_edt_ids()
|
] or self.module.get_edt_ids()
|
||||||
)
|
|
||||||
|
|
||||||
def get_evaluations_poids(self) -> pd.DataFrame:
|
def get_evaluations_poids(self) -> pd.DataFrame:
|
||||||
"""Les poids des évaluations vers les UE (accès via cache)"""
|
"""Les poids des évaluations vers les UE (accès via cache)"""
|
||||||
|
@ -292,7 +292,10 @@ class Module(db.Model):
|
|||||||
|
|
||||||
def get_edt_ids(self) -> list[str]:
|
def get_edt_ids(self) -> list[str]:
|
||||||
"les ids pour l'emploi du temps: à défaut, le 1er code Apogée"
|
"les ids pour l'emploi du temps: à défaut, le 1er code Apogée"
|
||||||
return scu.split_id(self.edt_id) or scu.split_id(self.code_apogee) or []
|
return [
|
||||||
|
scu.normalize_edt_id(x)
|
||||||
|
for x in scu.split_id(self.edt_id) or scu.split_id(self.code_apogee) or []
|
||||||
|
]
|
||||||
|
|
||||||
def get_parcours(self) -> list[ApcParcours]:
|
def get_parcours(self) -> list[ApcParcours]:
|
||||||
"""Les parcours utilisant ce module.
|
"""Les parcours utilisant ce module.
|
||||||
|
@ -132,7 +132,8 @@ def load_calendar(
|
|||||||
) from exc
|
) from exc
|
||||||
except FileNotFoundError as exc:
|
except FileNotFoundError as exc:
|
||||||
log(
|
log(
|
||||||
f"formsemestre_load_calendar: ics not found for {formsemestre or ''}\npath='{ics_filename}'"
|
f"""formsemestre_load_calendar: ics not found for {
|
||||||
|
formsemestre or ''}\npath='{ics_filename}'"""
|
||||||
)
|
)
|
||||||
raise ScoValueError(
|
raise ScoValueError(
|
||||||
f"Fichier ics introuvable (filename={ics_filename})"
|
f"Fichier ics introuvable (filename={ics_filename})"
|
||||||
@ -229,7 +230,8 @@ def formsemestre_edt_dict(
|
|||||||
scu.EMO_WARNING} {event['edt_module']}</span>"""
|
scu.EMO_WARNING} {event['edt_module']}</span>"""
|
||||||
bubble = "code module non trouvé dans ScoDoc. Vérifier configuration."
|
bubble = "code module non trouvé dans ScoDoc. Vérifier configuration."
|
||||||
case _: # module EDT bien retrouvé dans ScoDoc
|
case _: # module EDT bien retrouvé dans ScoDoc
|
||||||
bubble = f"{modimpl.module.abbrev or modimpl.module.titre or ''} ({event['edt_module']})"
|
bubble = f"""{modimpl.module.abbrev or modimpl.module.titre or ''
|
||||||
|
} ({event['edt_module']})"""
|
||||||
mod_disp = (
|
mod_disp = (
|
||||||
f"""<span class="mod-name mod-code">{modimpl.module.code}</span>"""
|
f"""<span class="mod-name mod-code">{modimpl.module.code}</span>"""
|
||||||
)
|
)
|
||||||
@ -249,20 +251,24 @@ def formsemestre_edt_dict(
|
|||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
ens_user_name = event["ens"].user_name if event["ens"] else None
|
ens_nomprenoms = (
|
||||||
ens_nomprenom = event["ens"].get_nomprenom() if event["ens"] else None
|
"(" + ", ".join([u.get_nomprenom() for u in event["users"]]) + ")"
|
||||||
|
if event["users"]
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
ens_user_names = (
|
||||||
|
",".join([u.user_name for u in event["users"]]) if event["users"] else ""
|
||||||
|
)
|
||||||
d = {
|
d = {
|
||||||
# Champs utilisés par tui.calendar
|
# Champs utilisés par tui.calendar
|
||||||
"calendarId": "cal1",
|
"calendarId": "cal1",
|
||||||
"title": f"""{title} {group_disp} {
|
"title": f"""{title} {group_disp} {ens_nomprenoms} {link_abs}""",
|
||||||
'('+ens_nomprenom+')' if ens_nomprenom else ''
|
|
||||||
} {link_abs}""",
|
|
||||||
"start": event["start"],
|
"start": event["start"],
|
||||||
"end": event["end"],
|
"end": event["end"],
|
||||||
"backgroundColor": event["group_bg_color"],
|
"backgroundColor": event["group_bg_color"],
|
||||||
# Infos brutes pour usage API éventuel
|
# Infos brutes pour usage API éventuel
|
||||||
"ens_edt": event["edt_ens"],
|
"edt_ens_ids": event["edt_ens_ids"],
|
||||||
"ens_user_name": ens_user_name,
|
"ens_user_names": ens_user_names,
|
||||||
"group_id": group.id if group else None,
|
"group_id": group.id if group else None,
|
||||||
"group_edt_id": event["edt_group"],
|
"group_edt_id": event["edt_group"],
|
||||||
"moduleimpl_id": modimpl.id if modimpl else None,
|
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||||
@ -343,7 +349,7 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
||||||
for i, group_name in enumerate(edt2group)
|
for i, group_name in enumerate(edt2group)
|
||||||
}
|
}
|
||||||
edt_groups_ids = set() # les ids de groupes tels que dans l'ics
|
edt_groups_ids = set() # les ids de groupes normalisés tels que dans l'ics
|
||||||
default_group = formsemestre.get_default_group()
|
default_group = formsemestre.get_default_group()
|
||||||
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
||||||
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
||||||
@ -354,7 +360,7 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
if "DESCRIPTION" in event:
|
if "DESCRIPTION" in event:
|
||||||
# --- Titre de l'évènement
|
# --- Titre de l'évènement
|
||||||
title_edt = (
|
title_edt = (
|
||||||
extract_event_data(event, edt_ics_title_field, edt_ics_title_pattern)
|
extract_event_edt_id(event, edt_ics_title_field, edt_ics_title_pattern)
|
||||||
if edt_ics_title_pattern
|
if edt_ics_title_pattern
|
||||||
else "non configuré"
|
else "non configuré"
|
||||||
)
|
)
|
||||||
@ -362,7 +368,7 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
title = title_edt
|
title = title_edt
|
||||||
# --- Group
|
# --- Group
|
||||||
if edt_ics_group_pattern:
|
if edt_ics_group_pattern:
|
||||||
edt_group = extract_event_data(
|
edt_group = extract_event_edt_id(
|
||||||
event, edt_ics_group_field, edt_ics_group_pattern
|
event, edt_ics_group_field, edt_ics_group_pattern
|
||||||
)
|
)
|
||||||
edt_groups_ids.add(edt_group)
|
edt_groups_ids.add(edt_group)
|
||||||
@ -385,7 +391,7 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
|
|
||||||
# --- ModuleImpl
|
# --- ModuleImpl
|
||||||
if edt_ics_mod_pattern:
|
if edt_ics_mod_pattern:
|
||||||
edt_module = extract_event_data(
|
edt_module = extract_event_edt_id(
|
||||||
event, edt_ics_mod_field, edt_ics_mod_pattern
|
event, edt_ics_mod_field, edt_ics_mod_pattern
|
||||||
)
|
)
|
||||||
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
|
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
|
||||||
@ -394,19 +400,22 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
else:
|
else:
|
||||||
modimpl = False
|
modimpl = False
|
||||||
edt_module = ""
|
edt_module = ""
|
||||||
# --- Enseignant
|
# --- Enseignants
|
||||||
|
users: list[User] = []
|
||||||
if edt_ics_uid_pattern:
|
if edt_ics_uid_pattern:
|
||||||
edt_ens = extract_event_data(
|
ens_edt_ids = extract_event_edt_ids(
|
||||||
event, edt_ics_uid_field, edt_ics_uid_pattern
|
event, edt_ics_uid_field, edt_ics_uid_pattern
|
||||||
)
|
)
|
||||||
if edt_ens in edt2user:
|
for ens_edt_id in ens_edt_ids:
|
||||||
ens = edt2user[edt_ens]
|
if ens_edt_id in edt2user:
|
||||||
|
ens = edt2user[ens_edt_id]
|
||||||
else:
|
else:
|
||||||
ens = User.query.filter_by(edt_id=edt_ens).first()
|
ens = User.query.filter_by(edt_id=ens_edt_id).first()
|
||||||
edt2user[edt_ens] = ens
|
edt2user[ens_edt_id] = ens
|
||||||
|
if ens:
|
||||||
|
users.append(ens)
|
||||||
else:
|
else:
|
||||||
ens = None
|
ens_edt_ids = []
|
||||||
edt_ens = ""
|
|
||||||
#
|
#
|
||||||
events_sco.append(
|
events_sco.append(
|
||||||
{
|
{
|
||||||
@ -418,8 +427,8 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
"modimpl": modimpl, # False si extracteur non configuré
|
"modimpl": modimpl, # False si extracteur non configuré
|
||||||
"edt_module": edt_module, # id module edt non traduit
|
"edt_module": edt_module, # id module edt non traduit
|
||||||
# Enseignant
|
# Enseignant
|
||||||
"edt_ens": edt_ens, # id ens edt, non traduit
|
"edt_ens_ids": ens_edt_ids, # ids ens edt, normalisés mais non traduits
|
||||||
"ens": ens,
|
"users": users,
|
||||||
# heures pour saisie abs: en heure LOCALE DU SERVEUR
|
# heures pour saisie abs: en heure LOCALE DU SERVEUR
|
||||||
"heure_deb": event.decoded("dtstart")
|
"heure_deb": event.decoded("dtstart")
|
||||||
.replace(tzinfo=timezone.utc)
|
.replace(tzinfo=timezone.utc)
|
||||||
@ -437,27 +446,49 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
|||||||
return events_sco, sorted(edt_groups_ids)
|
return events_sco, sorted(edt_groups_ids)
|
||||||
|
|
||||||
|
|
||||||
def extract_event_data(
|
def extract_event_edt_id(
|
||||||
event: icalendar.cal.Event,
|
event: icalendar.cal.Event,
|
||||||
ics_field: str,
|
ics_field: str,
|
||||||
pattern: re.Pattern,
|
pattern: re.Pattern,
|
||||||
none_if_no_match=False,
|
none_if_no_match=False,
|
||||||
) -> str:
|
) -> str | None:
|
||||||
"""Extrait la chaine (id) de l'évènement."""
|
"""Extrait la chaine (id) de l'évènement et la normalise.
|
||||||
|
Si l'event n'a pas le champ: "-"
|
||||||
|
Si pas de match: None
|
||||||
|
"""
|
||||||
if not event.has_key(ics_field):
|
if not event.has_key(ics_field):
|
||||||
return "-"
|
return "-"
|
||||||
data = event.decoded(ics_field).decode("utf-8") # assume ics in utf8
|
data = event.decoded(ics_field).decode("utf-8") # assume ics in utf8
|
||||||
m = pattern.search(data)
|
m = pattern.search(data)
|
||||||
if m and len(m.groups()) > 0:
|
if m and len(m.groups()) > 0:
|
||||||
return m.group(1)
|
return scu.normalize_edt_id(m.group(1))
|
||||||
# fallback: if not none_if_no_match, ics field complete
|
# fallback: if not none_if_no_match, ics field complete
|
||||||
return None if none_if_no_match else data
|
return None if none_if_no_match else data
|
||||||
|
|
||||||
|
|
||||||
|
def extract_event_edt_ids(
|
||||||
|
event: icalendar.cal.Event,
|
||||||
|
ics_field: str,
|
||||||
|
pattern: re.Pattern,
|
||||||
|
) -> list[str] | None:
|
||||||
|
"""Extrait les edt_id de l'évènement et les normalise.
|
||||||
|
Si l'event n'a pas le champ: None
|
||||||
|
Si pas de match: liste vide
|
||||||
|
Utilisé pour les enseignants uniquement.
|
||||||
|
"""
|
||||||
|
if not event.has_key(ics_field):
|
||||||
|
return
|
||||||
|
data = event.decoded(ics_field).decode("utf-8") # assume ics in utf8
|
||||||
|
matches = pattern.findall(data)
|
||||||
|
# nota: pattern may have zero or one group, so the result
|
||||||
|
# is a list of strings, not a list of matches
|
||||||
|
return [scu.normalize_edt_id(m) for m in matches if m]
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_retreive_modimpls_from_edt_id(
|
def formsemestre_retreive_modimpls_from_edt_id(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
) -> dict[str, ModuleImpl]:
|
) -> dict[str, ModuleImpl]:
|
||||||
"""Construit un dict donnant le moduleimpl de chaque edt_id"""
|
"""Construit un dict donnant le moduleimpl de chaque edt_id (normalisé)"""
|
||||||
edt2modimpl = {}
|
edt2modimpl = {}
|
||||||
for modimpl in formsemestre.modimpls:
|
for modimpl in formsemestre.modimpls:
|
||||||
for edt_id in modimpl.get_edt_ids():
|
for edt_id in modimpl.get_edt_ids():
|
||||||
@ -469,10 +500,13 @@ def formsemestre_retreive_modimpls_from_edt_id(
|
|||||||
def formsemestre_retreive_groups_from_edt_id(
|
def formsemestre_retreive_groups_from_edt_id(
|
||||||
formsemestre: FormSemestre,
|
formsemestre: FormSemestre,
|
||||||
) -> dict[str, GroupDescr]:
|
) -> dict[str, GroupDescr]:
|
||||||
"""Construit un dict donnant le groupe de chaque edt_id"""
|
"""Construit un dict donnant le groupe de chaque edt_id
|
||||||
|
La clé edt_id est sans accents, lowercase.
|
||||||
|
"""
|
||||||
edt2group = {}
|
edt2group = {}
|
||||||
|
group: GroupDescr
|
||||||
for partition in formsemestre.partitions:
|
for partition in formsemestre.partitions:
|
||||||
for g in partition.groups:
|
for group in partition.groups:
|
||||||
for edt_id in g.get_edt_ids():
|
for edt_id in group.get_edt_ids():
|
||||||
edt2group[edt_id] = g
|
edt2group[edt_id] = group
|
||||||
return edt2group
|
return edt2group
|
||||||
|
@ -882,6 +882,18 @@ def suppress_accents(s):
|
|||||||
return s # may be int
|
return s # may be int
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_edt_id(edt_id: str) -> str:
|
||||||
|
"""Normalize les identifiants edt pour faciliter la correspondance
|
||||||
|
entre les identifiants ScoDoc et ceux dans l'ics:
|
||||||
|
Passe tout en majuscules sans accents ni espaces.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
None
|
||||||
|
if edt_id is None
|
||||||
|
else suppress_accents(edt_id or "").upper().replace(" ", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PurgeChars:
|
class PurgeChars:
|
||||||
"""delete all chars except those belonging to the specified string"""
|
"""delete all chars except those belonging to the specified string"""
|
||||||
|
|
||||||
|
@ -1,8 +1,67 @@
|
|||||||
#show_modules_titles_form {
|
#calendar_control_form {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Style for the dropdown button */
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropbtn {
|
||||||
|
background-color: rgb(233,233,233);
|
||||||
|
color: black;
|
||||||
|
padding: 2px 32px 2px 4px;
|
||||||
|
margin-left: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 5px; /* Rounded corners */
|
||||||
|
cursor: pointer;
|
||||||
|
/* Add arrow to the button */
|
||||||
|
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2212%22%20height%3D%227%22%20viewBox%3D%220%200%2012%207%22%20fill%3D%22none%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cpath%20d%3D%22M1%201L6%206L11%201%22%20stroke%3D%22black%22%20stroke-width%3D%222%22/%3E%3C/svg%3E');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 10px center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown content (hidden by default) */
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
min-width: 210px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 5px; /* Rounded corners */
|
||||||
|
overflow: hidden; /* Ensures rounded corners for dropdown items */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for the dropdown items */
|
||||||
|
.dropdown-content ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content ul li {
|
||||||
|
color: black;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content ul li a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content ul li label {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.toastui-calendar-template-time {
|
.toastui-calendar-template-time {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
@ -16,11 +16,29 @@
|
|||||||
|
|
||||||
{{ form_groups_choice|safe }}
|
{{ form_groups_choice|safe }}
|
||||||
|
|
||||||
<form id="show_modules_titles_form" method="GET">
|
<div class="dropdown">
|
||||||
<input type="checkbox" name="show_modules_titles" {{
|
<button class="dropbtn">Semaine</button>
|
||||||
'checked' if show_modules_titles else ''}}
|
<div class="dropdown-content">
|
||||||
onchange="this.form.submit()"/> noms complets des modules</input>
|
<ul>
|
||||||
</form>
|
<li><a href="#" data-view="day">Jour</a></li>
|
||||||
|
<li><a href="#" data-view="week">Semaine</a></li>
|
||||||
|
<li><a href="#" data-view="month">Mois</a></li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="showModulesTitles"
|
||||||
|
{{ "checked" if show_modules_titles else "" }}
|
||||||
|
>
|
||||||
|
noms des modules
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="calendar_control_form" method="GET">
|
||||||
|
<input type="hidden" name="current_date" value="{{current_date}}">
|
||||||
|
<input type="hidden" name="show_modules_titles" value="{{ 1 if show_modules_titles else 0 }}">
|
||||||
|
<input type="submit" value="Apply Changes" hidden>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span id="menu-navi">
|
<span id="menu-navi">
|
||||||
@ -35,7 +53,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span id="renderRange" class="render-range"></span>
|
<span id="renderRange" class="render-range"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="calendar" style="height: calc(100vh - 180px);"></div>
|
<div id="formsemestre-calendar" style="height: calc(100vh - 180px);"></div>
|
||||||
|
|
||||||
<div class="help">
|
<div class="help">
|
||||||
<ul>
|
<ul>
|
||||||
@ -73,10 +91,23 @@ function getDataAction(target) {
|
|||||||
return target.dataset ? target.dataset.action : target.getAttribute('data-action');
|
return target.dataset ? target.dataset.action : target.getAttribute('data-action');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNextDayISODate(isoDate) {
|
||||||
|
// Parse the ISO date string into a Date object
|
||||||
|
const date = new Date(isoDate);
|
||||||
|
|
||||||
|
// Add one day
|
||||||
|
date.setDate(date.getDate() + 1);
|
||||||
|
|
||||||
|
// Convert back to ISO date string (YYYY-MM-DD)
|
||||||
|
return date.toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var calendar;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.getElementById('menu-navi').addEventListener('click', onClickNavi);
|
document.getElementById('menu-navi').addEventListener('click', onClickNavi);
|
||||||
const Calendar = tui.Calendar;
|
const Calendar = tui.Calendar;
|
||||||
const container = document.getElementById('calendar');
|
const container = document.getElementById('formsemestre-calendar');
|
||||||
const options = {
|
const options = {
|
||||||
defaultView: 'week',
|
defaultView: 'week',
|
||||||
calendars: [
|
calendars: [
|
||||||
@ -118,7 +149,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const calendar = new Calendar(container, options);
|
calendar = new Calendar(container, options);
|
||||||
|
|
||||||
fetch(`${SCO_URL}/../api/formsemestre/{{formsemestre.id}}/edt?{{groups_query_args|safe}}&show_modules_titles={{show_modules_titles}}`)
|
fetch(`${SCO_URL}/../api/formsemestre/{{formsemestre.id}}/edt?{{groups_query_args|safe}}&show_modules_titles={{show_modules_titles}}`)
|
||||||
.then(r=>{return r.json()})
|
.then(r=>{return r.json()})
|
||||||
@ -131,6 +162,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
{% if current_date %}
|
||||||
|
// we need to add one day because our weeks are starting on Monday
|
||||||
|
calendar.setDate( getNextDayISODate("{{current_date}}") );
|
||||||
|
{% endif %}
|
||||||
|
changeCalendarDate();
|
||||||
|
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
let year = date.getFullYear();
|
let year = date.getFullYear();
|
||||||
let month = (date.getMonth() + 1).toString().padStart(2, '0'); // Months are zero-indexed in JavaScript
|
let month = (date.getMonth() + 1).toString().padStart(2, '0'); // Months are zero-indexed in JavaScript
|
||||||
@ -145,10 +182,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
var html = [];
|
var html = [];
|
||||||
if (viewName === 'day') {
|
if (viewName === 'day') {
|
||||||
html.push(currentCalendarDate('YYYY.MM.DD'));
|
html.push(calendar.getDate().toDate().toLocaleString('fr-Fr', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'}));
|
||||||
} else if (viewName === 'month' &&
|
} else if (viewName === 'month' &&
|
||||||
(!options.month.visibleWeeksCount || options.month.visibleWeeksCount > 4)) {
|
(!options.month.visibleWeeksCount || options.month.visibleWeeksCount > 4)) {
|
||||||
html.push(currentCalendarDate('YYYY.MM'));
|
html.push(
|
||||||
|
calendar.getDate().toDate().toLocaleString('fr-Fr', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
html.push(formatDate(calendar.getDateRangeStart()));
|
html.push(formatDate(calendar.getDateRangeStart()));
|
||||||
html.push(' - ');
|
html.push(' - ');
|
||||||
@ -173,9 +217,65 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setRenderRangeText();
|
|
||||||
// setSchedules();
|
// setSchedules();
|
||||||
|
changeCalendarDate();
|
||||||
}
|
}
|
||||||
|
// Update current URL when date change (newDate=ISO string)
|
||||||
|
function updateCurrentDateInUrl(newDate) {
|
||||||
|
// Parse the current URL
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
|
||||||
|
// Access and modify the search parameters
|
||||||
|
const searchParams = currentUrl.searchParams;
|
||||||
|
searchParams.set('current_date', newDate); // Set the new date
|
||||||
|
|
||||||
|
// Create the new URL
|
||||||
|
const newUrl = `${currentUrl.origin}${currentUrl.pathname}?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
// Update the URL without reloading the page
|
||||||
|
window.history.pushState({ path: newUrl }, '', newUrl);
|
||||||
|
}
|
||||||
|
// Update "current" date (URL and title)
|
||||||
|
function changeCalendarDate() {
|
||||||
|
setRenderRangeText();
|
||||||
|
// current calendar date, ISO, without time
|
||||||
|
const iso_date = calendar.getDateRangeStart().toDate().toISOString().split('T')[0];
|
||||||
|
updateCurrentDateInUrl(iso_date);
|
||||||
|
calendar_control_form.current_date = iso_date;
|
||||||
|
}
|
||||||
|
// View menu
|
||||||
|
const dropdown = document.querySelector('.dropdown');
|
||||||
|
const dropbtn = document.querySelector('.dropbtn');
|
||||||
|
const dropdownContent = document.querySelector('.dropdown-content');
|
||||||
|
|
||||||
|
dropbtn.addEventListener('click', function(event) {
|
||||||
|
dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block';
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.dropdown-content a').forEach(item => {
|
||||||
|
item.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selectedText = this.textContent;
|
||||||
|
const selectedView = this.getAttribute('data-view');
|
||||||
|
calendar.changeView(selectedView); // Change the calendar view
|
||||||
|
dropbtn.textContent = selectedText; // Update the button text
|
||||||
|
dropdownContent.style.display = 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const showModulesTitlesCheckbox = document.getElementById('showModulesTitles');
|
||||||
|
showModulesTitlesCheckbox.addEventListener('change', function() {
|
||||||
|
calendar_control_form.show_modules_titles.value = (calendar_control_form.show_modules_titles.value=="1") ? "0" : "1";
|
||||||
|
calendar_control_form.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close dropdown when clicking outside
|
||||||
|
window.addEventListener('click', function() {
|
||||||
|
if (dropdownContent.style.display === 'block') {
|
||||||
|
dropdownContent.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -163,6 +163,7 @@ def formsemestre_edit_modimpls_codes(formsemestre_id: int):
|
|||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def formsemestre_edt(formsemestre_id: int):
|
def formsemestre_edt(formsemestre_id: int):
|
||||||
"""Expérimental: affiche emploi du temps du semestre"""
|
"""Expérimental: affiche emploi du temps du semestre"""
|
||||||
|
current_date = request.args.get("current_date")
|
||||||
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
show_modules_titles = scu.to_bool(request.args.get("show_modules_titles", False))
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
cfg = ScoDocSiteConfig.query.filter_by(name="assi_morning_time").first()
|
cfg = ScoDocSiteConfig.query.filter_by(name="assi_morning_time").first()
|
||||||
@ -177,6 +178,7 @@ def formsemestre_edt(formsemestre_id: int):
|
|||||||
)
|
)
|
||||||
return render_template(
|
return render_template(
|
||||||
"formsemestre/edt.j2",
|
"formsemestre/edt.j2",
|
||||||
|
current_date=current_date,
|
||||||
formsemestre=formsemestre,
|
formsemestre=formsemestre,
|
||||||
hour_start=hour_start,
|
hour_start=hour_start,
|
||||||
hour_end=hour_end,
|
hour_end=hour_end,
|
||||||
|
@ -86,17 +86,23 @@ def generate_ens_calendars():
|
|||||||
nb_events += len(events)
|
nb_events += len(events)
|
||||||
ens: User | None = None
|
ens: User | None = None
|
||||||
for event in events:
|
for event in events:
|
||||||
edt_ens = sco_edt_cal.extract_event_data(
|
ens_edt_ids = sco_edt_cal.extract_event_edt_ids(
|
||||||
event, edt_ics_uid_field, edt_ics_uid_pattern, none_if_no_match=True
|
event, edt_ics_uid_field, edt_ics_uid_pattern
|
||||||
)
|
)
|
||||||
if edt_ens in edt2user:
|
users = []
|
||||||
ens = edt2user[edt_ens]
|
for ens_edt_id in ens_edt_ids:
|
||||||
|
if ens_edt_id in edt2user:
|
||||||
|
ens = edt2user[ens_edt_id]
|
||||||
else:
|
else:
|
||||||
ens = User.query.filter_by(edt_id=edt_ens).first()
|
ens = User.query.filter_by(edt_id=ens_edt_id).first()
|
||||||
edt2user[edt_ens] = ens
|
edt2user[ens_edt_id] = ens
|
||||||
if ens: # si l'utilisateur est reconnu
|
if ens:
|
||||||
event.add("X-ScoDoc-user", ens.user_name)
|
users.append(ens)
|
||||||
edt_by_uid[edt_ens].add_component(event)
|
edt_by_uid[ens_edt_id].add_component(event)
|
||||||
|
|
||||||
|
if users: # si un utilisateur est reconnu dans l'event
|
||||||
|
event.add("X-ScoDoc-users", ",".join([u.user_name for u in users]))
|
||||||
|
|
||||||
_write_user_calendars(edt_by_uid)
|
_write_user_calendars(edt_by_uid)
|
||||||
log(
|
log(
|
||||||
f"""generate_ens_calendars: done in {(time.time()-t0):g}s, processed {
|
f"""generate_ens_calendars: done in {(time.time()-t0):g}s, processed {
|
||||||
|
Loading…
Reference in New Issue
Block a user