forked from ScoDoc/ScoDoc
merge
This commit is contained in:
commit
d5c15cc5c5
@ -4,7 +4,7 @@ Formulaire configuration des codes Apo et EDT des modimps d'un formsemestre
|
|||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import validators
|
from wtforms import validators
|
||||||
from wtforms.fields.simple import BooleanField, StringField, SubmitField
|
from wtforms.fields.simple import StringField, SubmitField
|
||||||
|
|
||||||
from app.models import FormSemestre, ModuleImpl
|
from app.models import FormSemestre, ModuleImpl
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
Formulaire configuration Module Assiduités
|
Formulaire configuration Module Assiduités
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import DecimalField, SubmitField, ValidationError
|
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}")
|
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):
|
class ConfigAssiduitesForm(FlaskForm):
|
||||||
"Formulaire paramétrage Module Assiduité"
|
"Formulaire paramétrage Module Assiduité"
|
||||||
|
|
||||||
@ -120,5 +138,45 @@ class ConfigAssiduitesForm(FlaskForm):
|
|||||||
validators=[Optional(), check_ics_path],
|
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")
|
submit = SubmitField("Valider")
|
||||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||||
|
@ -631,7 +631,7 @@ class FormSemestre(db.Model):
|
|||||||
|
|
||||||
def can_change_groups(self, user: User = None) -> bool:
|
def can_change_groups(self, user: User = None) -> bool:
|
||||||
"""Vrai si l'utilisateur (par def. current) peut changer les groupes dans
|
"""Vrai si l'utilisateur (par def. current) peut changer les groupes dans
|
||||||
ce semestre: vérifie permission et verrouillage.
|
ce semestre: vérifie permission et verrouillage (mais pas si la partition est éditable).
|
||||||
"""
|
"""
|
||||||
if not self.etat:
|
if not self.etat:
|
||||||
return False # semestre verrouillé
|
return False # semestre verrouillé
|
||||||
|
@ -282,18 +282,18 @@ class GroupDescr(db.Model):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_name(
|
def set_name(self, group_name: str, dest_url: str = None):
|
||||||
self, group_name: str, edt_id: str | bool = False, dest_url: str = None
|
|
||||||
):
|
|
||||||
"""Set group name, and optionally edt_id.
|
"""Set group name, and optionally edt_id.
|
||||||
Check permission and invalidate caches. Commit session.
|
Check permission (partition must be groups_editable)
|
||||||
|
and invalidate caches. Commit session.
|
||||||
dest_url is used for error messages.
|
dest_url is used for error messages.
|
||||||
"""
|
"""
|
||||||
if not self.partition.formsemestre.can_change_groups():
|
if not self.partition.formsemestre.can_change_groups():
|
||||||
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
if self.group_name is None:
|
if self.group_name is None:
|
||||||
raise ValueError("can't set a name to default group")
|
raise ValueError("can't set a name to default group")
|
||||||
|
if not self.partition.groups_editable:
|
||||||
|
raise AccessDenied("Partition non éditable")
|
||||||
if group_name:
|
if group_name:
|
||||||
group_name = group_name.strip()
|
group_name = group_name.strip()
|
||||||
if not group_name:
|
if not group_name:
|
||||||
@ -306,16 +306,22 @@ class GroupDescr(db.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.group_name = group_name
|
self.group_name = group_name
|
||||||
if edt_id is not False:
|
|
||||||
if isinstance(edt_id, str):
|
|
||||||
edt_id = edt_id.strip() or None
|
|
||||||
self.edt_id = edt_id
|
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
sco_cache.invalidate_formsemestre(
|
sco_cache.invalidate_formsemestre(
|
||||||
formsemestre_id=self.partition.formsemestre_id
|
formsemestre_id=self.partition.formsemestre_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_edt_id(self, edt_id: str):
|
||||||
|
"Set edt_id. Check permission. Commit session."
|
||||||
|
if not self.partition.formsemestre.can_change_groups():
|
||||||
|
raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !")
|
||||||
|
if isinstance(edt_id, str):
|
||||||
|
edt_id = edt_id.strip() or None
|
||||||
|
self.edt_id = edt_id
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
def remove_etud(self, etud: "Identite"):
|
def remove_etud(self, etud: "Identite"):
|
||||||
"Enlève l'étudiant de ce groupe s'il en fait partie (ne fait rien sinon)"
|
"Enlève l'étudiant de ce groupe s'il en fait partie (ne fait rien sinon)"
|
||||||
if etud in self.etuds:
|
if etud in self.etuds:
|
||||||
|
@ -36,6 +36,7 @@ import icalendar
|
|||||||
from flask import flash, g, url_for
|
from flask import flash, g, url_for
|
||||||
from app import log
|
from app import log
|
||||||
from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig
|
from app.models import FormSemestre, GroupDescr, ModuleImpl, ScoDocSiteConfig
|
||||||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
|
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ def formsemestre_load_calendar(
|
|||||||
return calendar
|
return calendar
|
||||||
|
|
||||||
|
|
||||||
|
# --- Couleurs des évènements emploi du temps
|
||||||
_COLOR_PALETTE = [
|
_COLOR_PALETTE = [
|
||||||
"#ff6961",
|
"#ff6961",
|
||||||
"#ffb480",
|
"#ffb480",
|
||||||
@ -77,15 +79,118 @@ _COLOR_PALETTE = [
|
|||||||
"#9d94ff",
|
"#9d94ff",
|
||||||
"#c780e8",
|
"#c780e8",
|
||||||
]
|
]
|
||||||
|
_EVENT_DEFAULT_COLOR = "rgb(214, 233, 248)"
|
||||||
|
|
||||||
|
|
||||||
def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]:
|
def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]:
|
||||||
"""EDT complet du semestre, comme une liste de dict serialisable en json.
|
"""EDT complet du semestre, comme une liste de dict serialisable en json.
|
||||||
Fonction appellée par l'API /formsemestre/<int:formsemestre_id>/edt
|
Fonction appellée par l'API /formsemestre/<int:formsemestre_id>/edt
|
||||||
TODO: spécifier intervalle de dates start et end
|
TODO: spécifier intervalle de dates start et end
|
||||||
TODO: cacher ?
|
|
||||||
"""
|
"""
|
||||||
# Correspondances id edt -> id scodoc pour groupes, modules et enseignants
|
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">{event['edt_group']}
|
||||||
|
<span title="vérifier noms de groupe ou configuration extraction edt">
|
||||||
|
{scu.EMO_WARNING} non reconnu</span>
|
||||||
|
</div>"""
|
||||||
|
)
|
||||||
|
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="code module non trouvé dans ScoDoc.
|
||||||
|
Vérifier configuration.">{
|
||||||
|
scu.EMO_WARNING} {event['edt_module']}</div>"""
|
||||||
|
)
|
||||||
|
# --- Lien saisie abs
|
||||||
|
link_abs = (
|
||||||
|
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
|
||||||
|
url_for("assiduites.signal_assiduites_group",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
formsemestre_id=formsemestre.id,
|
||||||
|
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
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
d = {
|
||||||
|
# Champs utilisés par tui.calendar
|
||||||
|
"calendarId": "cal1",
|
||||||
|
"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": event["edt_group"],
|
||||||
|
"moduleimpl_id": modimpl.id if modimpl else None,
|
||||||
|
}
|
||||||
|
events_cal.append(d)
|
||||||
|
|
||||||
|
return events_cal
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
edt2group = formsemestre_retreive_groups_from_edt_id(formsemestre)
|
||||||
group_colors = {
|
group_colors = {
|
||||||
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
group_name: _COLOR_PALETTE[i % (len(_COLOR_PALETTE) - 1) + 1]
|
||||||
@ -93,45 +198,65 @@ def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
}
|
}
|
||||||
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)
|
||||||
|
# ---
|
||||||
# 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 = [e for e in calendar.walk() if e.name == "VEVENT"]
|
||||||
events_dict = []
|
events_sco = []
|
||||||
for event in events:
|
for event in events:
|
||||||
if "DESCRIPTION" in event:
|
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
|
# --- Group
|
||||||
edt_group = extract_event_group(event)
|
if edt_ics_group_pattern:
|
||||||
# si pas de groupe dans l'event, prend toute la promo ("tous")
|
edt_group = extract_event_data(
|
||||||
group: GroupDescr = (
|
event, edt_ics_group_field, edt_ics_group_pattern
|
||||||
edt2group.get(edt_group, None) if edt_group else default_group
|
)
|
||||||
)
|
# si pas de groupe dans l'event, oi si groupe non reconnu, prend toute la promo ("tous")
|
||||||
background_color = (
|
group: GroupDescr = (
|
||||||
group_colors.get(edt_group, "rgb(214, 233, 248)")
|
edt2group.get(edt_group, default_group)
|
||||||
if group
|
if edt_group
|
||||||
else "lightgrey"
|
else default_group
|
||||||
)
|
)
|
||||||
group_disp = (
|
group_bg_color = (
|
||||||
f"""<div class="group-name">{group.get_nom_with_part(default="promo")}</div>"""
|
group_colors.get(edt_group, _EVENT_DEFAULT_COLOR)
|
||||||
if group
|
if group
|
||||||
else f"""<div class="group-edt">{edt_group}
|
else "lightgrey"
|
||||||
<span title="vérifier noms de groupe ou configuration extraction edt">
|
)
|
||||||
{scu.EMO_WARNING} non reconnu</span>
|
else:
|
||||||
</div>"""
|
edt_group = ""
|
||||||
)
|
group = False
|
||||||
|
group_bg_color = _EVENT_DEFAULT_COLOR
|
||||||
|
|
||||||
# --- ModuleImpl
|
# --- ModuleImpl
|
||||||
edt_module = extract_event_module(event)
|
if edt_ics_mod_pattern:
|
||||||
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
|
edt_module = extract_event_data(
|
||||||
mod_disp = (
|
event, edt_ics_mod_field, edt_ics_mod_pattern
|
||||||
f"""<div class="module-edt mod-name" title="{modimpl.module.abbrev or ""}">{
|
)
|
||||||
modimpl.module.code}</div>"""
|
modimpl: ModuleImpl = edt2modimpl.get(edt_module, None)
|
||||||
if modimpl
|
else:
|
||||||
else f"""<div class="module-edt mod-etd" title="vérifier code edt module ?">{
|
modimpl = False
|
||||||
scu.EMO_WARNING} {edt_module}</div>"""
|
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(),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
<<<<<<< HEAD
|
||||||
# --- Lien saisie abs
|
# --- Lien saisie abs
|
||||||
link_abs = (
|
link_abs = (
|
||||||
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
|
f"""<div class="module-edt link-abs"><a class="stdlink" href="{
|
||||||
@ -161,57 +286,73 @@ def formsemestre_edt_dict(formsemestre: FormSemestre) -> list[dict]:
|
|||||||
events_dict.append(d)
|
events_dict.append(d)
|
||||||
|
|
||||||
return events_dict
|
return events_dict
|
||||||
|
=======
|
||||||
|
return events_sco
|
||||||
|
>>>>>>> edt
|
||||||
|
|
||||||
|
|
||||||
def extract_event_title(event: icalendar.cal.Event) -> str:
|
def extract_event_data(
|
||||||
"""Extrait le titre à afficher dans nos calendriers (si on ne retrouve pas le module ScoDoc)
|
event: icalendar.cal.Event, ics_field: str, pattern: re.Pattern
|
||||||
En effet, le titre présent dans l'ics emploi du temps est souvent complexe et peu parlant.
|
) -> str:
|
||||||
Par exemple, à l'USPN, Hyperplanning nous donne:
|
"""Extrait la chaine (id) de l'évènement."""
|
||||||
'Matière : VRETR113 - Mathematiques du sig (VRETR113\nEnseignant : 1234 - M. DUPONT PIERRE\nTD : TDB\nSalle : L112 (IUTV) - L112\n'
|
if not event.has_key(ics_field):
|
||||||
"""
|
|
||||||
# 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 "-"
|
return "-"
|
||||||
description = event.decoded("DESCRIPTION").decode("utf-8") # assume ics in utf8
|
data = event.decoded(ics_field).decode("utf-8") # assume ics in utf8
|
||||||
# ici on prend le nom du module
|
m = pattern.search(data)
|
||||||
m = re.search(r"Matière : \w+ - ([\w\.\s']+)", description)
|
|
||||||
if m and len(m.groups()) > 0:
|
if m and len(m.groups()) > 0:
|
||||||
return m.group(1)
|
return m.group(1)
|
||||||
# fallback: full description
|
# fallback: ics field, complete
|
||||||
return description
|
return data
|
||||||
|
|
||||||
|
|
||||||
def extract_event_module(event: icalendar.cal.Event) -> str:
|
# def extract_event_title(event: icalendar.cal.Event) -> str:
|
||||||
"""Extrait le code module de l'emplois du temps.
|
# """Extrait le titre à afficher dans nos calendriers.
|
||||||
Chaine vide si ne le trouve pas.
|
# 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 le code 'VRETR113' dans DESCRIPTION
|
# 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'
|
# '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 ?
|
# # 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"):
|
# if not event.has_key("DESCRIPTION"):
|
||||||
return "-"
|
# return "-"
|
||||||
description = event.decoded("DESCRIPTION").decode("utf-8") # assume ics in utf8
|
# description = event.decoded("DESCRIPTION").decode("utf-8") # assume ics in utf8
|
||||||
# extraction du code:
|
# # ici on prend le nom du module
|
||||||
m = re.search(r"Matière : ([A-Z][A-Z0-9]+)", description)
|
# m = re.search(r"Matière : \w+ - ([\w\.\s']+)", description)
|
||||||
if m and len(m.groups()) > 0:
|
# if m and len(m.groups()) > 0:
|
||||||
return m.group(1)
|
# return m.group(1)
|
||||||
return ""
|
# # fallback: full description
|
||||||
|
# return description
|
||||||
|
|
||||||
|
|
||||||
def extract_event_group(event: icalendar.cal.Event) -> str:
|
# def extract_event_module(event: icalendar.cal.Event) -> str:
|
||||||
"""Extrait le nom du groupe (TD, ...). "" si pas de match."""
|
# """Extrait le code module de l'emplois du temps.
|
||||||
# TODO: fonction ajustée à l'USPN, devra être paramétrable d'une façon ou d'une autre: regexp ?
|
# Chaine vide si ne le trouve pas.
|
||||||
# Utilise ici le SUMMARY
|
# Par exemple, à l'USPN, Hyperplanning nous donne le code 'VRETR113' dans DESCRIPTION
|
||||||
# qui est de la forme
|
# 'Matière : VRETR113 - Mathematiques du sig (VRETR113\nEnseignant : 1234 - M. DUPONT PIERRE\nTD : TDB\nSalle : L112 (IUTV) - L112\n'
|
||||||
# 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"):
|
# # TODO: fonction ajustée à l'USPN, devra être paramétrable d'une façon ou d'une autre: regexp ?
|
||||||
return "-"
|
# if not event.has_key("DESCRIPTION"):
|
||||||
summary = event.decoded("SUMMARY").decode("utf-8") # assume ics in utf8
|
# return "-"
|
||||||
# extraction du code:
|
# description = event.decoded("DESCRIPTION").decode("utf-8") # assume ics in utf8
|
||||||
m = re.search(r".*- ([\w\s]+)$", summary)
|
# # extraction du code:
|
||||||
if m and len(m.groups()) > 0:
|
# m = re.search(r"Matière : ([A-Z][A-Z0-9]+)", description)
|
||||||
return m.group(1).strip()
|
# if m and len(m.groups()) > 0:
|
||||||
return ""
|
# 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(
|
def formsemestre_retreive_modimpls_from_edt_id(
|
||||||
|
@ -214,6 +214,9 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
)
|
)
|
||||||
for index, resp in enumerate(formsemestre.responsables):
|
for index, resp in enumerate(formsemestre.responsables):
|
||||||
initvalues[resp_fields[index]] = uid2display.get(resp.id)
|
initvalues[resp_fields[index]] = uid2display.get(resp.id)
|
||||||
|
group_tous = formsemestre.get_default_group()
|
||||||
|
if group_tous:
|
||||||
|
initvalues["edt_promo_id"] = group_tous.edt_id or ""
|
||||||
|
|
||||||
# Liste des ID de semestres
|
# Liste des ID de semestres
|
||||||
if formation.type_parcours is not None:
|
if formation.type_parcours is not None:
|
||||||
@ -458,6 +461,18 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
modform.append(
|
||||||
|
(
|
||||||
|
"edt_promo_id",
|
||||||
|
{
|
||||||
|
"size": 32,
|
||||||
|
"title": "Identifiant EDT promo",
|
||||||
|
"explanation": """optionnel, identifiant du groupe "tous"
|
||||||
|
(promotion complète) dans l'emploi du temps.""",
|
||||||
|
"allow_null": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
if edit:
|
if edit:
|
||||||
formtit = f"""
|
formtit = f"""
|
||||||
<p><a class="stdlink" href="{url_for("notes.formsemestre_edit_uecoefs",
|
<p><a class="stdlink" href="{url_for("notes.formsemestre_edit_uecoefs",
|
||||||
@ -970,8 +985,15 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
|
|||||||
db.session.get(ApcParcours, int(parcour_id_str))
|
db.session.get(ApcParcours, int(parcour_id_str))
|
||||||
for parcour_id_str in tf[2]["parcours"]
|
for parcour_id_str in tf[2]["parcours"]
|
||||||
]
|
]
|
||||||
|
# --- Id edt du groupe par défault
|
||||||
|
group_tous = formsemestre.get_default_group()
|
||||||
|
if group_tous:
|
||||||
|
group_tous.edt_id = tf[2]["edt_promo_id"]
|
||||||
|
db.session.add(group_tous)
|
||||||
|
|
||||||
db.session.add(formsemestre)
|
db.session.add(formsemestre)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# --- Crée ou met à jour les groupes de parcours BUT
|
# --- Crée ou met à jour les groupes de parcours BUT
|
||||||
formsemestre.setup_parcours_groups()
|
formsemestre.setup_parcours_groups()
|
||||||
# peut être nécessaire dans certains cas:
|
# peut être nécessaire dans certains cas:
|
||||||
|
@ -86,7 +86,10 @@ def group_rename(group_id):
|
|||||||
"size": 12,
|
"size": 12,
|
||||||
"allow_null": False,
|
"allow_null": False,
|
||||||
"validator": lambda val, _: len(val) < GROUPNAME_STR_LEN,
|
"validator": lambda val, _: len(val) < GROUPNAME_STR_LEN,
|
||||||
"explanation": "doit être unique dans cette partition",
|
"explanation": "doit être unique dans cette partition"
|
||||||
|
if group.partition.groups_editable
|
||||||
|
else "groupes non modifiables dans cette partition",
|
||||||
|
"enabled": group.partition.groups_editable,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@ -97,7 +100,7 @@ def group_rename(group_id):
|
|||||||
"size": 12,
|
"size": 12,
|
||||||
"allow_null": True,
|
"allow_null": True,
|
||||||
"explanation": """optionnel : identifiant du groupe dans le logiciel
|
"explanation": """optionnel : identifiant du groupe dans le logiciel
|
||||||
d'emploi du temps, pour le cas où les noms de gropupes ne seraient pas
|
d'emploi du temps, pour le cas où les noms de groupes ne seraient pas
|
||||||
les mêmes dans ScoDoc et dans l'emploi du temps.""",
|
les mêmes dans ScoDoc et dans l'emploi du temps.""",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -123,6 +126,9 @@ def group_rename(group_id):
|
|||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
else:
|
else:
|
||||||
# form submission
|
# form submission
|
||||||
group.set_name(tf[2]["group_name"], edt_id=tf[2]["edt_id"], dest_url=dest_url)
|
# Si la partition n'est pas editable, on ne peut changer que l'edt_id
|
||||||
|
group.set_edt_id(tf[2]["edt_id"])
|
||||||
|
if group.partition.groups_editable:
|
||||||
|
group.set_name(tf[2]["group_name"], dest_url=dest_url)
|
||||||
flash("groupe modifié")
|
flash("groupe modifié")
|
||||||
return flask.redirect(dest_url)
|
return flask.redirect(dest_url)
|
||||||
|
@ -14,3 +14,14 @@
|
|||||||
color: red;
|
color: red;
|
||||||
background-color: yellow;
|
background-color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toastui-calendar-timegrid {
|
||||||
|
height: 100% !important;
|
||||||
|
min-height: auto !important;
|
||||||
|
}
|
||||||
|
.toastui-calendar-time{
|
||||||
|
height: calc(100% - 44px) !important;
|
||||||
|
}
|
||||||
|
.toastui-calendar-week-view-day-names, .toastui-calendar-time {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
@ -91,6 +91,10 @@ body:not(.editionActivated) .editing {
|
|||||||
.nonEditable .editing {
|
.nonEditable .editing {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.nonEditable .editing.rename {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.editionActivated #zoneChoix,
|
.editionActivated #zoneChoix,
|
||||||
.editionActivated #zoneGroupes {
|
.editionActivated #zoneGroupes {
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
{% extends "base.j2" %}
|
{% extends "base.j2" %}
|
||||||
{% import 'bootstrap/wtf.html' as wtf %}
|
{% 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 %}
|
{% block app_content %}
|
||||||
<div class="row">
|
<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) }}
|
{{ wtf.form_field(form.edt_ics_path) }}
|
||||||
</div>
|
</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">
|
<div class="form-group">
|
||||||
{{ wtf.form_field(form.submit) }}
|
{{ wtf.form_field(form.submit) }}
|
||||||
{{ wtf.form_field(form.cancel) }}
|
{{ wtf.form_field(form.cancel) }}
|
||||||
@ -41,8 +73,4 @@ affectent notamment les comptages d'absences de tous les bulletins des
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -131,12 +131,12 @@
|
|||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"mouseup",
|
"mouseup",
|
||||||
mouseUp,
|
mouseUp,
|
||||||
{once:true}
|
{ once: true }
|
||||||
);
|
);
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"touchend",
|
"touchend",
|
||||||
mouseUp,
|
mouseUp,
|
||||||
{once:true}
|
{ once: true }
|
||||||
|
|
||||||
);
|
);
|
||||||
} else if (event.target === periodTimeLine) {
|
} else if (event.target === periodTimeLine) {
|
||||||
@ -217,6 +217,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setPeriodValues(deb, fin) {
|
function setPeriodValues(deb, fin) {
|
||||||
|
if (fin < deb) {
|
||||||
|
throw new RangeError(`le paramètre 'deb' doit être inférieur au paramètre 'fin' ([${deb};${fin}])`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deb < 0 || fin < 0) {
|
||||||
|
throw new RangeError(`Les paramètres doivent être des entiers positifis ([${deb};${fin}])`)
|
||||||
|
}
|
||||||
|
|
||||||
deb = snapToQuarter(deb);
|
deb = snapToQuarter(deb);
|
||||||
fin = snapToQuarter(fin);
|
fin = snapToQuarter(fin);
|
||||||
let leftPercentage = (deb - t_start) / (t_end - t_start) * 100;
|
let leftPercentage = (deb - t_start) / (t_end - t_start) * 100;
|
||||||
@ -231,13 +239,13 @@
|
|||||||
|
|
||||||
function snapHandlesToQuarters() {
|
function snapHandlesToQuarters() {
|
||||||
const periodValues = getPeriodValues();
|
const periodValues = getPeriodValues();
|
||||||
let lef = Math.min(computePercentage(periodValues[0], t_start), computePercentage(t_end, tick_delay));
|
let lef = Math.min(computePercentage(Math.abs(periodValues[0]), t_start), computePercentage(Math.abs(t_end), tick_delay));
|
||||||
if (lef < 0) {
|
if (lef < 0) {
|
||||||
lef = 0;
|
lef = 0;
|
||||||
}
|
}
|
||||||
const left = `${lef}%`;
|
const left = `${lef}%`;
|
||||||
|
|
||||||
let wid = Math.max(computePercentage(periodValues[1], periodValues[0]), computePercentage(tick_delay, 0));
|
let wid = Math.max(computePercentage(Math.abs(periodValues[1]), Math.abs(periodValues[0])), computePercentage(tick_delay, 0));
|
||||||
if (wid > 100) {
|
if (wid > 100) {
|
||||||
wid = 100;
|
wid = 100;
|
||||||
}
|
}
|
||||||
@ -251,10 +259,23 @@
|
|||||||
function computePercentage(a, b) {
|
function computePercentage(a, b) {
|
||||||
return ((a - b) / (t_end - t_start)) * 100;
|
return ((a - b) / (t_end - t_start)) * 100;
|
||||||
}
|
}
|
||||||
|
function fromTime(time, separator = ":") {
|
||||||
|
const [hours, minutes] = time.split(separator).map((el) => Number(el))
|
||||||
|
return hours + minutes / 60
|
||||||
|
}
|
||||||
|
|
||||||
createTicks();
|
createTicks();
|
||||||
setPeriodValues(t_start, t_start + period_default);
|
setPeriodValues(t_start, t_start + period_default);
|
||||||
|
|
||||||
|
{% if heures %}
|
||||||
|
let [heure_deb, heure_fin] = [{{ heures | safe }}]
|
||||||
|
if (heure_deb != '' && heure_fin != '') {
|
||||||
|
heure_deb = fromTime(heure_deb);
|
||||||
|
heure_fin = fromTime(heure_fin);
|
||||||
|
setPeriodValues(heure_deb, heure_fin)
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.timeline-container {
|
.timeline-container {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<h2>Expérimental: emploi du temps</h2>
|
<h2>Expérimental: emploi du temps</h2>
|
||||||
|
|
||||||
<div id="calendar" style="height: 900px;"></div>
|
<div id="calendar" style="height: calc(100vh - 180px);"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock app_content %}
|
{% endblock app_content %}
|
||||||
@ -51,12 +51,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return `<strong>${start}</strong> <span>${event.title}</span>`;
|
return `<strong>${start}</strong> <span>${event.title}</span>`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
timezone: {
|
||||||
|
zones: [
|
||||||
|
{
|
||||||
|
timezoneName: 'CET', // TODO récupérer timezone serveur
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
usageStatistics: false,
|
usageStatistics: false,
|
||||||
week: {
|
week: {
|
||||||
dayNames: [ "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
|
dayNames: [ "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
|
||||||
eventView: ['time'],
|
eventView: ['time'],
|
||||||
hourStart: 7, // TODO préférence
|
hourStart: {{ hour_start }},
|
||||||
hourEnd:24, // TODO préférence
|
hourEnd: {{ hour_end }},
|
||||||
showNowIndicator: true,
|
showNowIndicator: true,
|
||||||
startDayOfWeek: 1,
|
startDayOfWeek: 1,
|
||||||
taskView: false,
|
taskView: false,
|
||||||
|
@ -251,7 +251,7 @@
|
|||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<span class="editing move">||</span>
|
<span class="editing move">||</span>
|
||||||
<span>${groupe.group_name} ${edt_id_str}</span>
|
<span>${groupe.group_name} ${edt_id_str}</span>
|
||||||
<span class="editing"><a href="/ScoDoc/{{formsemestre.departement.acronym}}/Scolarite/group_rename?group_id=${groupe.id}">✏️</a></span>
|
<span class="editing rename"><a href="/ScoDoc/{{formsemestre.departement.acronym}}/Scolarite/group_rename?group_id=${groupe.id}">✏️</a></span>
|
||||||
<span class="editing suppr">❌</span>`;
|
<span class="editing suppr">❌</span>`;
|
||||||
|
|
||||||
div.addEventListener("click", filtre);
|
div.addEventListener("click", filtre);
|
||||||
|
@ -262,6 +262,10 @@ def signal_assiduites_etud():
|
|||||||
|
|
||||||
# Récupération de la date (par défaut la date du jour)
|
# Récupération de la date (par défaut la date du jour)
|
||||||
date = request.args.get("date", datetime.date.today().isoformat())
|
date = request.args.get("date", datetime.date.today().isoformat())
|
||||||
|
heures: list[str] = [
|
||||||
|
request.args.get("heure_deb", ""),
|
||||||
|
request.args.get("heure_fin", ""),
|
||||||
|
]
|
||||||
|
|
||||||
# gestion évaluations (Appel à la page depuis les évaluations)
|
# gestion évaluations (Appel à la page depuis les évaluations)
|
||||||
|
|
||||||
@ -320,7 +324,7 @@ def signal_assiduites_etud():
|
|||||||
date=date,
|
date=date,
|
||||||
morning=morning,
|
morning=morning,
|
||||||
lunch=lunch,
|
lunch=lunch,
|
||||||
timeline=_timeline(),
|
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
||||||
afternoon=afternoon,
|
afternoon=afternoon,
|
||||||
nonworkdays=_non_work_days(),
|
nonworkdays=_non_work_days(),
|
||||||
forcer_module=sco_preferences.get_preference(
|
forcer_module=sco_preferences.get_preference(
|
||||||
@ -576,6 +580,10 @@ def signal_assiduites_group():
|
|||||||
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
formsemestre_id: int = request.args.get("formsemestre_id", -1)
|
||||||
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
moduleimpl_id: int = request.args.get("moduleimpl_id")
|
||||||
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
date: str = request.args.get("jour", datetime.date.today().isoformat())
|
||||||
|
heures: list[str] = [
|
||||||
|
request.args.get("heure_deb", ""),
|
||||||
|
request.args.get("heure_fin", ""),
|
||||||
|
]
|
||||||
group_ids: list[int] = request.args.get("group_ids", None)
|
group_ids: list[int] = request.args.get("group_ids", None)
|
||||||
if group_ids is None:
|
if group_ids is None:
|
||||||
group_ids = []
|
group_ids = []
|
||||||
@ -701,7 +709,7 @@ def signal_assiduites_group():
|
|||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
grp=sco_groups_view.menu_groups_choice(groups_infos),
|
grp=sco_groups_view.menu_groups_choice(groups_infos),
|
||||||
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
|
moduleimpl_select=_module_selector(formsemestre, moduleimpl_id),
|
||||||
timeline=_timeline(),
|
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
||||||
nonworkdays=_non_work_days(),
|
nonworkdays=_non_work_days(),
|
||||||
formsemestre_date_debut=str(formsemestre.date_debut),
|
formsemestre_date_debut=str(formsemestre.date_debut),
|
||||||
formsemestre_date_fin=str(formsemestre.date_fin),
|
formsemestre_date_fin=str(formsemestre.date_fin),
|
||||||
@ -1259,12 +1267,12 @@ def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
|||||||
assiduites, metric=metrique, filtered={"split": True}
|
assiduites, metric=metrique, filtered={"split": True}
|
||||||
)
|
)
|
||||||
|
|
||||||
# On sépare :
|
# On sépare :
|
||||||
# - abs_j = absences justifiées
|
# - abs_j = absences justifiées
|
||||||
# - abs_nj = absences non justifiées
|
# - abs_nj = absences non justifiées
|
||||||
# - retards = les retards
|
# - retards = les retards
|
||||||
# - justifs = les justificatifs
|
# - justifs = les justificatifs
|
||||||
|
|
||||||
abs_j: list[str] = [
|
abs_j: list[str] = [
|
||||||
{"date": _get_date_str(assi.date_debut, assi.date_fin)}
|
{"date": _get_date_str(assi.date_debut, assi.date_fin)}
|
||||||
for assi in assiduites
|
for assi in assiduites
|
||||||
@ -1463,7 +1471,7 @@ def _dynamic_module_selector() -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _timeline(formsemestre_id: int = None) -> str:
|
def _timeline(formsemestre_id: int = None, heures=None) -> str:
|
||||||
"""
|
"""
|
||||||
_timeline retourne l'html de la timeline
|
_timeline retourne l'html de la timeline
|
||||||
|
|
||||||
@ -1483,6 +1491,7 @@ def _timeline(formsemestre_id: int = None) -> str:
|
|||||||
periode_defaut=sco_preferences.get_preference(
|
periode_defaut=sco_preferences.get_preference(
|
||||||
"periode_defaut", formsemestre_id
|
"periode_defaut", formsemestre_id
|
||||||
),
|
),
|
||||||
|
heures=heures,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ from app.decorators import (
|
|||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
from app.forms.formsemestre import change_formation, edit_modimpls_codes_apo
|
from app.forms.formsemestre import change_formation, edit_modimpls_codes_apo
|
||||||
from app.models import Formation, FormSemestre
|
from app.models import Formation, FormSemestre, ScoDocSiteConfig
|
||||||
from app.scodoc import sco_formations, sco_formation_versions
|
from app.scodoc import sco_formations, sco_formation_versions
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.views import notes_bp as bp
|
from app.views import notes_bp as bp
|
||||||
@ -158,8 +158,14 @@ def formsemestre_edit_modimpls_codes(formsemestre_id: int):
|
|||||||
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"""
|
||||||
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
cfg = ScoDocSiteConfig.query.filter_by(name="assi_morning_time").first()
|
||||||
|
hour_start = cfg.value.split(":")[0].lstrip(" 0") if cfg else "7"
|
||||||
|
cfg = ScoDocSiteConfig.query.filter_by(name="assi_afternoon_time").first()
|
||||||
|
hour_end = cfg.value.split(":")[0].lstrip(" 0") if cfg else "18"
|
||||||
return render_template(
|
return render_template(
|
||||||
"formsemestre/edt.j2",
|
"formsemestre/edt.j2",
|
||||||
formsemestre=formsemestre,
|
formsemestre=formsemestre,
|
||||||
|
hour_start=hour_start,
|
||||||
|
hour_end=hour_end,
|
||||||
sco=ScoData(formsemestre=formsemestre),
|
sco=ScoData(formsemestre=formsemestre),
|
||||||
)
|
)
|
||||||
|
@ -324,6 +324,16 @@ def config_assiduites():
|
|||||||
if request.method == "POST" and form.cancel.data: # cancel button
|
if request.method == "POST" and form.cancel.data: # cancel button
|
||||||
return redirect(url_for("scodoc.index"))
|
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 form.validate_on_submit():
|
||||||
if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]):
|
if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]):
|
||||||
flash("Heure du début de la journée enregistrée")
|
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")
|
flash("Heure de fin de la journée enregistrée")
|
||||||
if ScoDocSiteConfig.set("assi_tick_time", float(form.data["tick_time"])):
|
if ScoDocSiteConfig.set("assi_tick_time", float(form.data["tick_time"])):
|
||||||
flash("Granularité de la timeline enregistrée")
|
flash("Granularité de la timeline enregistrée")
|
||||||
if ScoDocSiteConfig.set("edt_ics_path", form.data["edt_ics_path"]):
|
# --- Calendriers emploi du temps
|
||||||
flash("Chemin vers les calendriers ics enregistré")
|
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"))
|
return redirect(url_for("scodoc.configuration"))
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
@ -352,7 +365,9 @@ def config_assiduites():
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
form.tick_time.data = 15.0
|
form.tick_time.data = 15.0
|
||||||
ScoDocSiteConfig.set("assi_tick_time", 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(
|
return render_template(
|
||||||
"assiduites/pages/config_assiduites.j2",
|
"assiduites/pages/config_assiduites.j2",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.52"
|
SCOVERSION = "9.6.53"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user