forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -29,6 +29,7 @@
|
||||
Formulaire configuration Module Assiduités
|
||||
"""
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import DecimalField, SubmitField, ValidationError
|
||||
@ -98,6 +99,23 @@ def check_ics_path(form, field):
|
||||
raise ValidationError("Le chemin vers les ics doit utiliser {edt_id}")
|
||||
|
||||
|
||||
def check_ics_field(form, field):
|
||||
"""Vérifie que c'est un nom de champ crédible: un mot alphanumérique"""
|
||||
if not re.match(r"^[a-zA-Z\-_0-9]+$", field.data):
|
||||
raise ValidationError("nom de champ ics invalide")
|
||||
|
||||
|
||||
def check_ics_regexp(form, field):
|
||||
"""Vérifie que field est une expresssion régulière"""
|
||||
value = field.data.strip()
|
||||
# check that it compiles
|
||||
try:
|
||||
_ = re.compile(value)
|
||||
except re.error as exc:
|
||||
raise ValidationError("expression invalide") from exc
|
||||
return True
|
||||
|
||||
|
||||
class ConfigAssiduitesForm(FlaskForm):
|
||||
"Formulaire paramétrage Module Assiduité"
|
||||
|
||||
@ -120,5 +138,45 @@ class ConfigAssiduitesForm(FlaskForm):
|
||||
validators=[Optional(), check_ics_path],
|
||||
)
|
||||
|
||||
edt_ics_title_field = StringField(
|
||||
label="Champs contenant le titre",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
edt_ics_title_regexp = StringField(
|
||||
label="Extraction du titre",
|
||||
description=r"""expression régulière python dont le premier groupe doit
|
||||
sera le titre de l'évènement affcihé dans le calendrier ScoDoc.
|
||||
Exemple: <tt>Matière : \w+ - ([\w\.\s']+)</tt>
|
||||
""",
|
||||
validators=[Optional(), check_ics_regexp],
|
||||
)
|
||||
edt_ics_group_field = StringField(
|
||||
label="Champs contenant le groupe",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
edt_ics_group_regexp = StringField(
|
||||
label="Extraction du groupe",
|
||||
description=r"""expression régulière python dont le premier groupe doit
|
||||
correspondre à l'identifiant de groupe de l'emploi du temps.
|
||||
Exemple: <tt>.*- ([\w\s]+)$</tt>
|
||||
""",
|
||||
validators=[Optional(), check_ics_regexp],
|
||||
)
|
||||
edt_ics_mod_field = StringField(
|
||||
label="Champs contenant le module",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
edt_ics_mod_regexp = StringField(
|
||||
label="Extraction du module",
|
||||
description=r"""expression régulière python dont le premier groupe doit
|
||||
correspondre à l'identifiant (code) du module de l'emploi du temps.
|
||||
Exemple: <tt>Matière : ([A-Z][A-Z0-9]+)</tt>
|
||||
""",
|
||||
validators=[Optional(), check_ics_regexp],
|
||||
)
|
||||
|
||||
submit = SubmitField("Valider")
|
||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||
|
@ -36,6 +36,7 @@ import icalendar
|
||||
from flask import flash, g, url_for
|
||||
from app import log
|
||||
from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
@ -67,6 +68,7 @@ def formsemestre_load_calendar(
|
||||
return calendar
|
||||
|
||||
|
||||
# --- Couleurs des évènements emploi du temps
|
||||
_COLOR_PALETTE = [
|
||||
"#ff6961",
|
||||
"#ffb480",
|
||||
@ -77,60 +79,46 @@ _COLOR_PALETTE = [
|
||||
"#9d94ff",
|
||||
"#c780e8",
|
||||
]
|
||||
_EVENT_DEFAULT_COLOR = "rgb(214, 233, 248)"
|
||||
|
||||
|
||||
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/<int:formsemestre_id>/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"
|
||||
)
|
||||
events_scodoc = _load_and_convert_ics(formsemestre)
|
||||
# Génération des événements pour le calendrier html
|
||||
events_cal = []
|
||||
for event in events_scodoc:
|
||||
group: GroupDescr | bool = event["group"]
|
||||
if group is False:
|
||||
group_disp = f"""<div class="group-edt">
|
||||
<span title="extraction emploi du temps non configurée">
|
||||
{scu.EMO_WARNING} non configuré</span>
|
||||
</div>"""
|
||||
else:
|
||||
group_disp = (
|
||||
f"""<div class="group-name">{group.get_nom_with_part(default="promo")}</div>"""
|
||||
if group
|
||||
else f"""<div class="group-edt">{edt_group}
|
||||
else f"""<div class="group-edt">{event['edt_group']}
|
||||
<span title="vérifier noms de groupe ou configuration extraction edt">
|
||||
{scu.EMO_WARNING} non reconnu</span>
|
||||
</div>"""
|
||||
)
|
||||
# --- ModuleImpl
|
||||
edt_module = extract_event_module(event)
|
||||
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
|
||||
modimpl: ModuleImpl | bool = event["modimpl"]
|
||||
if modimpl is False:
|
||||
mod_disp = f"""<div class="module-edt" title="extraction emploi du temps non configurée">
|
||||
{scu.EMO_WARNING} non configuré
|
||||
</div>"""
|
||||
else:
|
||||
mod_disp = (
|
||||
f"""<div class="module-edt mod-name" title="{modimpl.module.abbrev or ""}">{
|
||||
modimpl.module.code}</div>"""
|
||||
if modimpl
|
||||
else f"""<div class="module-edt mod-etd" title="vérifier code edt module ?">{
|
||||
scu.EMO_WARNING} {edt_module}</div>"""
|
||||
else f"""<div class="module-edt mod-etd" title="code module non trouvé dans ScoDoc.
|
||||
Vérifier configuration.">{
|
||||
scu.EMO_WARNING} {event['edt_module']}</div>"""
|
||||
)
|
||||
# --- Lien saisie abs
|
||||
link_abs = (
|
||||
@ -138,9 +126,11 @@ def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]:
|
||||
url_for("assiduites.signal_assiduites_group",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=formsemestre.id,
|
||||
moduleimpl_id=modimpl.id,
|
||||
jour = event.decoded("dtstart").isoformat(),
|
||||
group_ids=group.id,
|
||||
heure_deb=event["heure_deb"],
|
||||
heure_fin=event["heure_fin"],
|
||||
moduleimpl_id=modimpl.id,
|
||||
jour = event["jour"],
|
||||
)}">absences</a>
|
||||
</div>"""
|
||||
if modimpl and group
|
||||
@ -149,69 +139,188 @@ def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]:
|
||||
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,
|
||||
"title": event["title"] + group_disp + mod_disp + link_abs,
|
||||
"start": event["start"],
|
||||
"end": event["end"],
|
||||
"backgroundColor": event["group_bg_color"],
|
||||
# Infos brutes pour usage API éventuel
|
||||
"group_id": group.id if group else None,
|
||||
"group_edt_id": edt_group,
|
||||
"group_edt_id": event["edt_group"],
|
||||
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||
}
|
||||
events_dict.append(d)
|
||||
events_cal.append(d)
|
||||
|
||||
return events_dict
|
||||
return events_cal
|
||||
|
||||
|
||||
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"):
|
||||
def _load_and_convert_ics(formsemestre: FormSemestre) -> list[dict]:
|
||||
"chargement fichier, filtrage et extraction des identifiants."
|
||||
# Chargement du calendier ics
|
||||
calendar = formsemestre_load_calendar(formsemestre)
|
||||
if not calendar:
|
||||
return []
|
||||
# --- Paramètres d'extraction
|
||||
edt_ics_title_field = ScoDocSiteConfig.get("edt_ics_title_field")
|
||||
edt_ics_title_regexp = ScoDocSiteConfig.get("edt_ics_title_regexp")
|
||||
try:
|
||||
edt_ics_title_pattern = (
|
||||
re.compile(edt_ics_title_regexp) if edt_ics_title_regexp else None
|
||||
)
|
||||
except re.error as exc:
|
||||
raise ScoValueError(
|
||||
"expression d'extraction du titre depuis l'emploi du temps invalide"
|
||||
) from exc
|
||||
edt_ics_group_field = ScoDocSiteConfig.get("edt_ics_group_field")
|
||||
edt_ics_group_regexp = ScoDocSiteConfig.get("edt_ics_group_regexp")
|
||||
try:
|
||||
edt_ics_group_pattern = (
|
||||
re.compile(edt_ics_group_regexp) if edt_ics_group_regexp else None
|
||||
)
|
||||
except re.error as exc:
|
||||
raise ScoValueError(
|
||||
"expression d'extraction du groupe depuis l'emploi du temps invalide"
|
||||
) from exc
|
||||
edt_ics_mod_field = ScoDocSiteConfig.get("edt_ics_mod_field")
|
||||
edt_ics_mod_regexp = ScoDocSiteConfig.get("edt_ics_mod_regexp")
|
||||
try:
|
||||
edt_ics_mod_pattern = (
|
||||
re.compile(edt_ics_mod_regexp) if edt_ics_mod_regexp else None
|
||||
)
|
||||
except re.error as exc:
|
||||
raise ScoValueError(
|
||||
"expression d'extraction du module depuis l'emploi du temps invalide"
|
||||
) from exc
|
||||
# --- 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)
|
||||
# ---
|
||||
events = [e for e in calendar.walk() if e.name == "VEVENT"]
|
||||
events_sco = []
|
||||
for event in events:
|
||||
if "DESCRIPTION" in event:
|
||||
# --- Titre de l'évènement
|
||||
title = (
|
||||
extract_event_data(event, edt_ics_title_field, edt_ics_title_pattern)
|
||||
if edt_ics_title_pattern
|
||||
else "non configuré"
|
||||
)
|
||||
# --- Group
|
||||
if edt_ics_group_pattern:
|
||||
edt_group = extract_event_data(
|
||||
event, edt_ics_group_field, edt_ics_group_pattern
|
||||
)
|
||||
# si pas de groupe dans l'event, oi si groupe non reconnu, prend toute la promo ("tous")
|
||||
group: GroupDescr = (
|
||||
edt2group.get(edt_group, default_group)
|
||||
if edt_group
|
||||
else default_group
|
||||
)
|
||||
group_bg_color = (
|
||||
group_colors.get(edt_group, _EVENT_DEFAULT_COLOR)
|
||||
if group
|
||||
else "lightgrey"
|
||||
)
|
||||
else:
|
||||
edt_group = ""
|
||||
group = False
|
||||
group_bg_color = _EVENT_DEFAULT_COLOR
|
||||
|
||||
# --- ModuleImpl
|
||||
if edt_ics_mod_pattern:
|
||||
edt_module = extract_event_data(
|
||||
event, edt_ics_mod_field, edt_ics_mod_pattern
|
||||
)
|
||||
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
|
||||
else:
|
||||
modimpl = False
|
||||
edt_module = ""
|
||||
# --- TODO: enseignant
|
||||
#
|
||||
events_sco.append(
|
||||
{
|
||||
"title": title,
|
||||
"edt_group": edt_group, # id group edt non traduit
|
||||
"group": group, # False si extracteur non configuré
|
||||
"group_bg_color": group_bg_color, # associée au groupe
|
||||
"modimpl": modimpl, # False si extracteur non configuré
|
||||
"edt_module": edt_module, # id module edt non traduit
|
||||
"heure_deb": event.decoded("dtstart").strftime("%H:%M"),
|
||||
"heure_fin": event.decoded("dtend").strftime("%H:%M"),
|
||||
"jour": event.decoded("dtstart").isoformat(),
|
||||
"start": event.decoded("dtstart").isoformat(),
|
||||
"end": event.decoded("dtend").isoformat(),
|
||||
}
|
||||
)
|
||||
return events_sco
|
||||
|
||||
|
||||
def extract_event_data(
|
||||
event: icalendar.cal.Event, ics_field: str, pattern: re.Pattern
|
||||
) -> str:
|
||||
"""Extrait la chaine (id) de l'évènement."""
|
||||
if not event.has_key(ics_field):
|
||||
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)
|
||||
data = event.decoded(ics_field).decode("utf-8") # assume ics in utf8
|
||||
m = pattern.search(data)
|
||||
if m and len(m.groups()) > 0:
|
||||
return m.group(1)
|
||||
# fallback: full description
|
||||
return description
|
||||
# fallback: ics field, complete
|
||||
return data
|
||||
|
||||
|
||||
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_title(event: icalendar.cal.Event) -> str:
|
||||
# """Extrait le titre à afficher dans nos calendriers.
|
||||
# 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_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 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."""
|
||||
# # 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(
|
||||
|
@ -1,6 +1,19 @@
|
||||
{% extends "base.j2" %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<style>
|
||||
div.config-section {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
margin-right: -15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
|
||||
@ -33,6 +46,25 @@ affectent notamment les comptages d'absences de tous les bulletins des
|
||||
{{ wtf.form_field(form.edt_ics_path) }}
|
||||
</div>
|
||||
|
||||
<div class="config-section">Extraction des identifiants depuis les calendriers</div>
|
||||
<div class="help">
|
||||
Indiquer ici comment récupérer les informations (titre, groupe, module)
|
||||
dans les calendriers publiés par votre logiciel d'emploi du temps.
|
||||
</div>
|
||||
<div class="config-edt">
|
||||
{{ wtf.form_field(form.edt_ics_title_field) }}
|
||||
{{ wtf.form_field(form.edt_ics_title_regexp) }}
|
||||
</div>
|
||||
<div class="config-edt">
|
||||
{{ wtf.form_field(form.edt_ics_group_field) }}
|
||||
{{ wtf.form_field(form.edt_ics_group_regexp) }}
|
||||
</div>
|
||||
<div class="config-edt">
|
||||
{{ wtf.form_field(form.edt_ics_mod_field) }}
|
||||
{{ wtf.form_field(form.edt_ics_mod_regexp) }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
{{ wtf.form_field(form.submit) }}
|
||||
{{ wtf.form_field(form.cancel) }}
|
||||
@ -41,8 +73,4 @@ affectent notamment les comptages d'absences de tous les bulletins des
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
@ -51,6 +51,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return `<strong>${start}</strong> <span>${event.title}</span>`;
|
||||
},
|
||||
},
|
||||
timezone: {
|
||||
zones: [
|
||||
{
|
||||
timezoneName: 'CET', // TODO récupérer timezone serveur
|
||||
},
|
||||
],
|
||||
},
|
||||
usageStatistics: false,
|
||||
week: {
|
||||
dayNames: [ "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
|
||||
|
@ -324,6 +324,16 @@ def config_assiduites():
|
||||
if request.method == "POST" and form.cancel.data: # cancel button
|
||||
return redirect(url_for("scodoc.index"))
|
||||
|
||||
edt_options = (
|
||||
("edt_ics_path", "Chemin vers les calendriers ics"),
|
||||
("edt_ics_title_field", "Champ contenant titre"),
|
||||
("edt_ics_title_regexp", "Expression extraction titre"),
|
||||
("edt_ics_group_field", "Champ contenant groupe"),
|
||||
("edt_ics_group_regexp", "Expression extraction groupe"),
|
||||
("edt_ics_mod_field", "Champ contenant module"),
|
||||
("edt_ics_mod_regexp", "Expression extraction module"),
|
||||
)
|
||||
|
||||
if form.validate_on_submit():
|
||||
if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]):
|
||||
flash("Heure du début de la journée enregistrée")
|
||||
@ -333,8 +343,11 @@ def config_assiduites():
|
||||
flash("Heure de fin de la journée enregistrée")
|
||||
if ScoDocSiteConfig.set("assi_tick_time", float(form.data["tick_time"])):
|
||||
flash("Granularité de la timeline enregistrée")
|
||||
if ScoDocSiteConfig.set("edt_ics_path", form.data["edt_ics_path"]):
|
||||
flash("Chemin vers les calendriers ics enregistré")
|
||||
# --- Calendriers emploi du temps
|
||||
for opt_name, message in edt_options:
|
||||
if ScoDocSiteConfig.set(opt_name, form.data[opt_name]):
|
||||
flash(f"{message} enregistré")
|
||||
|
||||
return redirect(url_for("scodoc.configuration"))
|
||||
|
||||
if request.method == "GET":
|
||||
@ -352,7 +365,9 @@ def config_assiduites():
|
||||
except ValueError:
|
||||
form.tick_time.data = 15.0
|
||||
ScoDocSiteConfig.set("assi_tick_time", 15.0)
|
||||
form.edt_ics_path.data = ScoDocSiteConfig.get("edt_ics_path")
|
||||
# --- Emplois du temps
|
||||
for opt_name, _ in edt_options:
|
||||
getattr(form, opt_name).data = ScoDocSiteConfig.get(opt_name)
|
||||
|
||||
return render_template(
|
||||
"assiduites/pages/config_assiduites.j2",
|
||||
|
Loading…
Reference in New Issue
Block a user