forked from ScoDoc/ScoDoc
EDT: extraction et affichage de l'enseignant
This commit is contained in:
parent
4cb7479b6f
commit
999757dd77
@ -139,7 +139,7 @@ class ConfigAssiduitesForm(FlaskForm):
|
||||
)
|
||||
|
||||
edt_ics_title_field = StringField(
|
||||
label="Champs contenant le titre",
|
||||
label="Champ contenant le titre",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
@ -152,7 +152,7 @@ class ConfigAssiduitesForm(FlaskForm):
|
||||
validators=[Optional(), check_ics_regexp],
|
||||
)
|
||||
edt_ics_group_field = StringField(
|
||||
label="Champs contenant le groupe",
|
||||
label="Champ contenant le groupe",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
@ -165,7 +165,7 @@ class ConfigAssiduitesForm(FlaskForm):
|
||||
validators=[Optional(), check_ics_regexp],
|
||||
)
|
||||
edt_ics_mod_field = StringField(
|
||||
label="Champs contenant le module",
|
||||
label="Champ contenant le module",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
@ -177,6 +177,18 @@ class ConfigAssiduitesForm(FlaskForm):
|
||||
""",
|
||||
validators=[Optional(), check_ics_regexp],
|
||||
)
|
||||
|
||||
edt_ics_uid_field = StringField(
|
||||
label="Champ contenant l'enseignant",
|
||||
description="""champ de l'évènement calendrier: DESCRIPTION, SUMMARY, ...""",
|
||||
validators=[Optional(), check_ics_field],
|
||||
)
|
||||
edt_ics_uid_regexp = StringField(
|
||||
label="Extraction de l'enseignant",
|
||||
description=r"""expression régulière python dont le premier groupe doit
|
||||
correspondre à l'identifiant (edt_id) de l'enseignant associé à l'évènement.
|
||||
Exemple: <tt>Enseignant : ([0-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 g, url_for
|
||||
from app import log
|
||||
from app.auth.models import User
|
||||
from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig
|
||||
from app.scodoc.sco_exceptions import ScoValueError
|
||||
import app.scodoc.sco_utils as scu
|
||||
@ -56,8 +57,11 @@ def formsemestre_load_calendar(
|
||||
Raises ScoValueError if not configured or not available or invalid format.
|
||||
"""
|
||||
edt_ids = []
|
||||
if edt_id is None and formsemestre:
|
||||
edt_ids = formsemestre.get_edt_ids()
|
||||
if edt_id is None:
|
||||
if formsemestre:
|
||||
edt_ids = formsemestre.get_edt_ids()
|
||||
else:
|
||||
edt_ids = [edt_id]
|
||||
if not edt_ids:
|
||||
raise ScoValueError(
|
||||
"accès aux emplois du temps non configuré pour ce semestre (pas d'edt_id)"
|
||||
@ -132,8 +136,10 @@ def formsemestre_edt_dict(
|
||||
except ScoValueError as exc:
|
||||
return exc.args[0]
|
||||
# Génération des événements pour le calendrier html
|
||||
promo_icon = f"""<img height="24px" src="{scu.STATIC_DIR}/icons/promo.svg"
|
||||
promo_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/promo.svg"
|
||||
title="promotion complète" alt="promotion"/>"""
|
||||
abs_icon = f"""<img height="28px" src="{scu.STATIC_DIR}/icons/absences.svg"
|
||||
title="saisir absences" alt="saisir absences"/>"""
|
||||
events_cal = []
|
||||
for event in events_scodoc:
|
||||
group: GroupDescr | bool = event["group"]
|
||||
@ -191,19 +197,26 @@ def formsemestre_edt_dict(
|
||||
# --- Lien saisie abs
|
||||
link_abs = (
|
||||
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
|
||||
url_abs}">absences</a>
|
||||
url_abs}">{abs_icon}</a>
|
||||
</div>"""
|
||||
if url_abs
|
||||
else ""
|
||||
)
|
||||
|
||||
ens_user_name = event["ens"].user_name if event["ens"] else None
|
||||
ens_nomprenom = event["ens"].get_nomprenom() if event["ens"] else None
|
||||
d = {
|
||||
# Champs utilisés par tui.calendar
|
||||
"calendarId": "cal1",
|
||||
"title": f"""{title} {group_disp} {link_abs}""",
|
||||
"title": f"""{title} {group_disp} {
|
||||
'('+ens_nomprenom+')' if ens_nomprenom else ''
|
||||
} {link_abs}""",
|
||||
"start": event["start"],
|
||||
"end": event["end"],
|
||||
"backgroundColor": event["group_bg_color"],
|
||||
# Infos brutes pour usage API éventuel
|
||||
"ens_edt": event["edt_ens"],
|
||||
"ens_user_name": ens_user_name,
|
||||
"group_id": group.id if group else None,
|
||||
"group_edt_id": event["edt_group"],
|
||||
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||
@ -257,6 +270,16 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
||||
raise ScoValueError(
|
||||
"expression d'extraction du module depuis l'emploi du temps invalide"
|
||||
) from exc
|
||||
edt_ics_uid_field = ScoDocSiteConfig.get("edt_ics_uid_field")
|
||||
edt_ics_uid_regexp = ScoDocSiteConfig.get("edt_ics_uid_regexp")
|
||||
try:
|
||||
edt_ics_uid_pattern = (
|
||||
re.compile(edt_ics_uid_regexp) if edt_ics_uid_regexp else None
|
||||
)
|
||||
except re.error as exc:
|
||||
raise ScoValueError(
|
||||
"expression d'extraction de l'enseignant 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 = {
|
||||
@ -266,6 +289,7 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
||||
edt_groups_ids = set() # les ids de groupes tels que dans l'ics
|
||||
default_group = formsemestre.get_default_group()
|
||||
edt2modimpl = formsemestre_retreive_modimpls_from_edt_id(formsemestre)
|
||||
edt2user: dict[str, User | None] = {} # construit au fur et à mesure (cache)
|
||||
# ---
|
||||
events = [e for e in calendar.walk() if e.name == "VEVENT"]
|
||||
events_sco = []
|
||||
@ -313,7 +337,19 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
||||
else:
|
||||
modimpl = False
|
||||
edt_module = ""
|
||||
# --- TODO: enseignant
|
||||
# --- Enseignant
|
||||
if edt_ics_uid_pattern:
|
||||
edt_ens = extract_event_data(
|
||||
event, edt_ics_uid_field, edt_ics_uid_pattern
|
||||
)
|
||||
if edt_ens in edt2user:
|
||||
ens = edt2user[edt_ens]
|
||||
else:
|
||||
ens = User.query.filter_by(edt_id=edt_ens).first()
|
||||
edt2user[edt_ens] = ens
|
||||
else:
|
||||
ens = None
|
||||
edt_ens = ""
|
||||
#
|
||||
events_sco.append(
|
||||
{
|
||||
@ -324,6 +360,9 @@ def load_and_convert_ics(formsemestre: FormSemestre) -> tuple[list[dict], list[s
|
||||
"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
|
||||
# Enseignant
|
||||
"edt_ens": edt_ens, # id ens edt, non traduit
|
||||
"ens": ens,
|
||||
# heures pour saisie abs: en heure LOCALE DU SERVEUR
|
||||
"heure_deb": event.decoded("dtstart")
|
||||
.replace(tzinfo=timezone.utc)
|
||||
|
1
app/static/icons/absences.svg
Normal file
1
app/static/icons/absences.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.8 KiB |
@ -96,7 +96,7 @@ affectent notamment les comptages d'absences de tous les bulletins des
|
||||
<button id="test_load_ics" type="button" onclick="load_ics_sample()" disabled>
|
||||
Essayer de charger l'ics</button>
|
||||
<div id="raw-ics-sample-zone">
|
||||
<div>Voici un évènement chargé au milieu de ce calendrier.
|
||||
<div>Voici un évènement chargé, pris au hasard au milieu de ce calendrier.
|
||||
Utilisez cet exemple pour configurer les expressions d'extraction
|
||||
en bas de ce formulaire.
|
||||
</div>
|
||||
@ -121,7 +121,10 @@ affectent notamment les comptages d'absences de tous les bulletins des
|
||||
{{ wtf.form_field(form.edt_ics_mod_field) }}
|
||||
{{ wtf.form_field(form.edt_ics_mod_regexp) }}
|
||||
</div>
|
||||
|
||||
<div class="config-edt">
|
||||
{{ wtf.form_field(form.edt_ics_uid_field) }}
|
||||
{{ wtf.form_field(form.edt_ics_uid_regexp) }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{{ wtf.form_field(form.submit) }}
|
||||
|
@ -93,6 +93,6 @@
|
||||
<script src="{{scu.STATIC_DIR}}/js/scodoc.js"></script>
|
||||
<script>
|
||||
const SCO_URL = "{{ url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)[:-11] }}";
|
||||
const SCO_TIMEZONE = {{ scu.TIME_ZONE }}
|
||||
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
|
||||
</script>
|
||||
{% endblock %}
|
@ -4,6 +4,9 @@
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<style>
|
||||
.form-titre {
|
||||
font-weight: bold;
|
||||
}
|
||||
span.mod-label {
|
||||
display: inline-block;
|
||||
min-width: 300px;
|
||||
@ -68,13 +71,17 @@ Pour les modifier, aller dans l'édition de la formation.
|
||||
<form id="mf" class="form form-horizontal" method="post" enctype="multipart/form-data" role="form">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ wtf.form_errors(form, hiddens="only") }}
|
||||
<div class="form-group">
|
||||
<div class="form-group form-titre">
|
||||
<span class="mod-label">
|
||||
<label>Module</label>
|
||||
<span class="code-apo-module">Code Apo. Module</span>
|
||||
<span class="field-apo">Code(s) Apogée</span>
|
||||
<span class="field-edt">Code EDT</span>
|
||||
<span class="code-apo-module" title="codes dans la formation">Code Apo. Module</span>
|
||||
</span>
|
||||
<span class="field-apo"
|
||||
title="codes spécifiques à ce semestre (si différents de ceux de la formation)">
|
||||
Code(s) Apogée</span>
|
||||
<span class="field-edt"
|
||||
title="identifiant dans l'emploi du temps (si différent du code Apogée)">
|
||||
Code EDT</span>
|
||||
</div>
|
||||
{% for modimpl in formsemestre.modimpls_sorted %}
|
||||
{{ render_text_field(form["modimpl_apo_" ~ modimpl.id], form["modimpl_edt_" ~ modimpl.id], modimpl.module.get_codes_apogee()) }}
|
||||
|
@ -47,6 +47,23 @@ table#edt2group tbody tr.active-row {
|
||||
si vous voyez ici de nombreuses lignes, il est possible que l'expression régulière
|
||||
d'extraction soit incorrecte (voir configuration globale) ou bien que votre logiciel d'emploi du temps génère de nombreux évènements non associés à un groupe donné.
|
||||
</div>
|
||||
|
||||
{% if ScoDocSiteConfig.get("edt_ics_group_field") %}
|
||||
<div>Les groupes sont extrait du champs <b>{{ScoDocSiteConfig.get("edt_ics_group_field")}}</b>
|
||||
à l'aide de l'expression régulière: <tt>{{ScoDocSiteConfig.get("edt_ics_group_regexp")}}</tt>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="fontred">extraction non configuré</div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.has_permission(sco.Permission.ScoSuperAdmin) %}
|
||||
<div>Pour changer ce réglage, <a href="{{url_for('scodoc.config_assiduites')}}"
|
||||
class="stdlink">voir la page de configuration</a>.</div>
|
||||
{% else %}
|
||||
<div class="fontred">au besoin, l'administrateur peut changer ces réglages dans
|
||||
le panneau de configuration générale.</div>
|
||||
{% endif %}
|
||||
|
||||
<div>Voici ce qui a été extrait de l'emploi du temps par l'expression régulière configurée:
|
||||
</div>
|
||||
<ul>
|
||||
|
@ -209,4 +209,6 @@ def formsemestre_edt_help_config(formsemestre_id: int):
|
||||
edt_groups_ids=edt_groups_ids,
|
||||
events_sco=events_sco,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
ScoDocSiteConfig=ScoDocSiteConfig,
|
||||
title="Aide configuration EDT",
|
||||
)
|
||||
|
@ -52,7 +52,7 @@ from PIL import Image as PILImage
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
|
||||
|
||||
from app import db
|
||||
from app import db, log
|
||||
from app.auth.models import User, Role
|
||||
from app.auth.cas import set_cas_configuration
|
||||
from app.decorators import (
|
||||
@ -332,6 +332,8 @@ def config_assiduites():
|
||||
("edt_ics_group_regexp", "Expression extraction groupe"),
|
||||
("edt_ics_mod_field", "Champ contenant module"),
|
||||
("edt_ics_mod_regexp", "Expression extraction module"),
|
||||
("edt_ics_uid_field", "Champ contenant l'enseignant"),
|
||||
("edt_ics_uid_regexp", "Expression extraction de l'enseignant"),
|
||||
)
|
||||
|
||||
if form.validate_on_submit():
|
||||
@ -380,16 +382,20 @@ def config_assiduites():
|
||||
@admin_required
|
||||
def ics_raw_sample(edt_id: str):
|
||||
"Renvoie un extrait de l'ics brut, pour aider à configurer les extractions"
|
||||
log(f"ics_raw_sample/{edt_id}")
|
||||
try:
|
||||
raw_ics, _ = sco_edt_cal.formsemestre_load_calendar(edt_id=edt_id)
|
||||
except ScoValueError as exc:
|
||||
log(f"ics_raw_sample: formsemestre_load_calendar({edt_id}) failed")
|
||||
return exc.args[0]
|
||||
try:
|
||||
ics = raw_ics.decode(scu.SCO_ENCODING)
|
||||
except SyntaxError:
|
||||
log(f"ics_raw_sample: raw_ics.decode failed")
|
||||
return f"Erreur lors de la conversion vers {scu.SCO_ENCODING}"
|
||||
evs = ics.split("BEGIN:VEVENT")
|
||||
if len(evs) < 1:
|
||||
log(f"ics_raw_sample: empty calendar")
|
||||
return "pas d'évènements VEVENT détectés dans ce fichier"
|
||||
return "BEGIN:VEVENT" + evs[len(evs) // 2]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user