From 5d7d967cf2838b033148c45a64e7c274103df993 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Nov 2023 17:58:11 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Extraction=20identifiant=20utilisateur=20ED?= =?UTF-8?q?T=20depuis=20la=20r=C3=A9ponse=20CAS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/cas.py | 9 +++++++++ app/auth/models.py | 13 +------------ app/forms/main/config_cas.py | 25 ++++++++++++++++++++----- app/models/config.py | 14 ++++++++++++-- app/templates/config_cas.j2 | 9 +++++---- app/views/scodoc.py | 7 +++++++ 6 files changed, 54 insertions(+), 23 deletions(-) diff --git a/app/auth/cas.py b/app/auth/cas.py index b9584f83..3b9f14e4 100644 --- a/app/auth/cas.py +++ b/app/auth/cas.py @@ -39,6 +39,15 @@ def after_cas_login(): "scodoc_cas_login_date" ] = datetime.datetime.now().isoformat() user.cas_last_login = datetime.datetime.utcnow() + if flask.session.get("CAS_EDT_ID"): + # essaie de récupérer l'edt_id s'il est présent + # cet ID peut être renvoyé par le CAS et extrait par ScoDoc + # via l'expression `cas_edt_id_from_xml_regexp` + # voir flask_cas.routing + edt_id = flask.session.get("CAS_EDT_ID") + current_app.logger.info(f"""after_cas_login: storing edt_id for { + user.user_name}: '{edt_id}'""") + user.edt_id = edt_id db.session.add(user) db.session.commit() return flask.redirect(url_for("scodoc.index")) diff --git a/app/auth/models.py b/app/auth/models.py index 899a5ab2..dcf762a2 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -185,18 +185,7 @@ class User(UserMixin, db.Model): return self._migrate_scodoc7_password(password) return False - password_ok = check_password_hash(self.password_hash, password) - if password_ok and cas_enabled and flask.session.get("CAS_EDT_ID"): - # essaie de récupérer l'edt_id s'il est présent - # cet ID peut être renvoyé par le CAS et extrait par ScoDoc - # via l'expression `cas_edt_id_from_xml_regexp` - # voir flask_cas.routing - edt_id = flask.session.get("CAS_EDT_ID") - log(f"Storing edt_id for {self.user_name}: '{edt_id}'") - self.edt_id = edt_id - db.session.add(self) - db.session.commit() - return password_ok + return check_password_hash(self.password_hash, password) def _migrate_scodoc7_password(self, password) -> bool: """After migration, rehash password.""" diff --git a/app/forms/main/config_cas.py b/app/forms/main/config_cas.py index 7e2b73c5..978b84cc 100644 --- a/app/forms/main/config_cas.py +++ b/app/forms/main/config_cas.py @@ -38,10 +38,14 @@ from app.models import ScoDocSiteConfig def check_cas_uid_from_mail_regexp(form, field): - "Vérifie la regexp fournie pur l'extraction du CAS id" + "Vérifie la regexp fournie pour l'extraction du CAS id" if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data): raise ValidationError("expression régulière invalide") +def check_cas_edt_id_from_xml_regexp(form, field): + "Vérifie la regexp fournie pour l'extraction du CAS id" + if not ScoDocSiteConfig.cas_edt_id_from_xml_regexp_is_valid(field.data): + raise ValidationError("expression régulière pour edt_id invalide") class ConfigCASForm(FlaskForm): "Formulaire paramétrage CAS" @@ -58,18 +62,18 @@ class ConfigCASForm(FlaskForm): description="""url complète. Commence en général par https://.""", ) cas_login_route = StringField( - label="Route du login CAS", + label="Optionnel: route du login CAS", description="""ajouté à l'URL du serveur: exemple /cas (si commence par /, part de la racine)""", default="/cas", ) cas_logout_route = StringField( - label="Route du logout CAS", + label="Optionnel: route du logout CAS", description="""ajouté à l'URL du serveur: exemple /cas/logout""", default="/cas/logout", ) cas_validate_route = StringField( - label="Route de validation CAS", + label="Optionnel: route de validation CAS", description="""ajouté à l'URL du serveur: exemple /cas/serviceValidate""", default="/cas/serviceValidate", ) @@ -81,7 +85,7 @@ class ConfigCASForm(FlaskForm): ) cas_uid_from_mail_regexp = StringField( - label="Expression pour extraire l'identifiant utilisateur", + label="Optionnel: expression pour extraire l'identifiant utilisateur", description="""regexp python appliquée au mail institutionnel de l'utilisateur, dont le premier groupe doit donner l'identifiant CAS. Si non fournie, le super-admin devra saisir cet identifiant pour chaque compte. @@ -92,6 +96,17 @@ class ConfigCASForm(FlaskForm): validators=[Optional(), check_cas_uid_from_mail_regexp], ) + cas_edt_id_from_xml_regexp = StringField( + label="Optionnel: expression pour extraire l'identifiant edt", + description="""regexp python appliquée à la réponse XML du serveur CAS pour + retrouver l'id de l'utilisateur sur le SI de l'institution, et notamment sur les + calendrier d'emploi du temps. Par exemple, si cet id est renvoyé dans le champ + supannEmpId, utiliser: + <cas:supannEmpId>(.*?)</cas:supannEmpId> + """, + validators=[Optional(), check_cas_edt_id_from_xml_regexp], + ) + cas_ssl_verify = BooleanField("Vérification du certificat SSL") cas_ssl_certificate_file = FileField( label="Certificat (PEM)", diff --git a/app/models/config.py b/app/models/config.py index fe76bec8..561bf6df 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -286,7 +286,7 @@ class ScoDocSiteConfig(db.Model): @classmethod def set(cls, name: str, value: str) -> bool: "Set parameter, returns True if change. Commit session." - value_str = str(value or "") + value_str = str(value or "").strip() if (cls.get(name) or "") != value_str: cfg = ScoDocSiteConfig.query.filter_by(name=name).first() if cfg is None: @@ -429,7 +429,17 @@ class ScoDocSiteConfig(db.Model): return False # and returns at least one group on a simple cannonical address match = pattern.search("emmanuel@exemple.fr") - return len(match.groups()) > 0 + return match is not None and len(match.groups()) > 0 + + @classmethod + def cas_edt_id_from_xml_regexp_is_valid(cls, exp: str) -> bool: + "True si l'expression régulière semble valide" + # check that it compiles + try: + _ = re.compile(exp) + except re.error: + return False + return True @classmethod def assi_get_rounded_time(cls, label: str, default: str) -> float: diff --git a/app/templates/config_cas.j2 b/app/templates/config_cas.j2 index ca500b1c..05fb2158 100644 --- a/app/templates/config_cas.j2 +++ b/app/templates/config_cas.j2 @@ -24,6 +24,7 @@ {{ wtf.form_field(form.cas_validate_route) }} {{ wtf.form_field(form.cas_attribute_id) }} {{ wtf.form_field(form.cas_uid_from_mail_regexp) }} + {{ wtf.form_field(form.cas_edt_id_from_xml_regexp) }}
{{ wtf.form_field(form.cas_ssl_verify) }} {{ wtf.form_field(form.cas_ssl_certificate_file) }} @@ -40,8 +41,8 @@ {{ wtf.form_field(form.cancel) }}
- ℹ️ Note: si le CAS est forcé, le super-admin et les utilisateurs autorisés - à "se connecter via ScoDoc" pourront toujours se + ℹ️ Note: si le CAS est forcé, le super-admin et les utilisateurs autorisés + à "se connecter via ScoDoc" pourront toujours se connecter via l'adresse spéciale {{url_for("auth.login_scodoc", _external=True)}}
@@ -50,6 +51,6 @@ - -{% endblock %} \ No newline at end of file + +{% endblock %} diff --git a/app/views/scodoc.py b/app/views/scodoc.py index 46dd8d8f..c2ee067d 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -273,6 +273,10 @@ def config_cas(): "cas_uid_from_mail_regexp", form.data["cas_uid_from_mail_regexp"] ): flash("Expression extraction identifiant CAS enregistrée") + if ScoDocSiteConfig.set( + "cas_edt_id_from_xml_regexp", form.data["cas_edt_id_from_xml_regexp"] + ): + flash("Expression extraction identifiant edt enregistrée") if ScoDocSiteConfig.set("cas_ssl_verify", form.data["cas_ssl_verify"]): flash("Vérification SSL modifiée") if form.cas_ssl_certificate_file.data: @@ -300,6 +304,9 @@ def config_cas(): form.cas_uid_from_mail_regexp.data = ScoDocSiteConfig.get( "cas_uid_from_mail_regexp" ) + form.cas_edt_id_from_xml_regexp.data = ScoDocSiteConfig.get( + "cas_edt_id_from_xml_regexp" + ) form.cas_ssl_verify.data = ScoDocSiteConfig.get("cas_ssl_verify") return render_template( "config_cas.j2", From bb331e31c135be5d8048a22ab1936c685126c7de Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Wed, 8 Nov 2023 23:19:58 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Am=C3=A9liore=20form=20config=20assiduite?= =?UTF-8?q?=20+=20edt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/forms/main/config_assiduites.py | 44 +++++++++++++-- app/forms/main/config_cas.py | 2 + app/forms/main/config_personalized_links.py | 6 +- app/models/moduleimpls.py | 3 - app/scodoc/sco_edit_module.py | 11 ++-- app/scodoc/sco_formsemestre.py | 3 +- app/scodoc/sco_formsemestre_edit.py | 55 ++++++++++++------- app/scodoc/sco_groups.py | 4 +- app/static/js/assiduites.js | 18 +++--- .../assiduites/pages/config_assiduites.j2 | 44 +++++++++------ app/templates/configuration.j2 | 4 +- app/views/scodoc.py | 25 ++++----- 12 files changed, 138 insertions(+), 81 deletions(-) diff --git a/app/forms/main/config_assiduites.py b/app/forms/main/config_assiduites.py index ba7198b8..da62b8b4 100644 --- a/app/forms/main/config_assiduites.py +++ b/app/forms/main/config_assiduites.py @@ -28,12 +28,14 @@ """ Formulaire configuration Module Assiduités """ +import datetime from flask_wtf import FlaskForm -from wtforms import SubmitField, DecimalField +from wtforms import DecimalField, SubmitField, ValidationError from wtforms.fields.simple import StringField +from wtforms.validators import Optional + from wtforms.widgets import TimeInput -import datetime class TimeField(StringField): @@ -72,9 +74,28 @@ class TimeField(StringField): else: raise ValueError self.data = datetime.time(hour, minutes, seconds) - except ValueError: + except ValueError as exc: self.data = None - raise ValueError(self.gettext("Not a valid time string")) + raise ValueError(self.gettext("Not a valid time string")) from exc + + +def check_tick_time(form, field): + """Le tick_time doit être entre 0 et 60 minutes""" + if field.data < 1 or field.data > 59: + raise ValidationError("Valeur de granularité invalide (entre 1 et 59)") + + +def check_ics_path(form, field): + """Vérifie que le chemin est bien un chemin absolu + et qu'il contient edt_id + """ + data = field.data.strip() + if not data: + return + if not data.startswith("/"): + raise ValidationError("Le chemin vers les ics doit commencer par /") + if not "{edt_id}" in data: + raise ValidationError("Le chemin vers les ics doit utiliser {edt_id}") class ConfigAssiduitesForm(FlaskForm): @@ -84,7 +105,20 @@ class ConfigAssiduitesForm(FlaskForm): lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)") afternoon_time = TimeField("Fin de la journée") - tick_time = DecimalField("Granularité de la Time Line (temps en minutes)", places=0) + tick_time = DecimalField( + "Granularité de la timeline (temps en minutes)", + places=0, + validators=[check_tick_time], + ) + + edt_ics_path = StringField( + label="Chemin vers les ics", + description="""Chemin absolu unix sur le serveur vers le fichier ics donnant l'emploi + du temps d'un semestre. La balise {edt_id} sera remplacée par l'edt_id du + semestre (par défaut, son code étape Apogée). + Si ce champ n'est pas renseigné, les emplois du temps ne seront pas utilisés.""", + validators=[Optional(), check_ics_path], + ) submit = SubmitField("Valider") cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) diff --git a/app/forms/main/config_cas.py b/app/forms/main/config_cas.py index 978b84cc..3a31b47f 100644 --- a/app/forms/main/config_cas.py +++ b/app/forms/main/config_cas.py @@ -42,11 +42,13 @@ def check_cas_uid_from_mail_regexp(form, field): if not ScoDocSiteConfig.cas_uid_from_mail_regexp_is_valid(field.data): raise ValidationError("expression régulière invalide") + def check_cas_edt_id_from_xml_regexp(form, field): "Vérifie la regexp fournie pour l'extraction du CAS id" if not ScoDocSiteConfig.cas_edt_id_from_xml_regexp_is_valid(field.data): raise ValidationError("expression régulière pour edt_id invalide") + class ConfigCASForm(FlaskForm): "Formulaire paramétrage CAS" cas_enable = BooleanField("Activer le CAS") diff --git a/app/forms/main/config_personalized_links.py b/app/forms/main/config_personalized_links.py index 1aed3130..b2293a2c 100644 --- a/app/forms/main/config_personalized_links.py +++ b/app/forms/main/config_personalized_links.py @@ -29,7 +29,7 @@ def PersonalizedLinksForm() -> _PersonalizedLinksForm: F, f"link_{idx}", StringField( - f"Titre", + "Titre", validators=[ validators.Optional(), validators.Length(min=1, max=80), @@ -42,7 +42,7 @@ def PersonalizedLinksForm() -> _PersonalizedLinksForm: F, f"link_url_{idx}", StringField( - f"URL", + "URL", description="adresse, incluant le http.", validators=[ validators.Optional(), @@ -56,7 +56,7 @@ def PersonalizedLinksForm() -> _PersonalizedLinksForm: F, f"link_with_args_{idx}", BooleanField( - f"ajouter arguments", + "ajouter arguments", description="query string avec ids", ), ) diff --git a/app/models/moduleimpls.py b/app/models/moduleimpls.py index 3ccff4cf..75672c1b 100644 --- a/app/models/moduleimpls.py +++ b/app/models/moduleimpls.py @@ -42,9 +42,6 @@ class ModuleImpl(db.Model): viewonly=True, ) - def __init__(self, **kwargs): - super(ModuleImpl, self).__init__(**kwargs) - def __repr__(self): return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>" diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py index ea0ca9fb..4f5b9235 100644 --- a/app/scodoc/sco_edit_module.py +++ b/app/scodoc/sco_edit_module.py @@ -74,7 +74,8 @@ _moduleEditor = ndb.EditableTable( "semestre_id", "numero", "code_apogee", - "module_type" + "module_type", + "edt_id", #'ects' ), sortkey="numero, code, titre", @@ -171,7 +172,7 @@ def do_module_delete(oid): d'en créer une nouvelle version pour la modifier sans affecter les semestres déjà en place.

- reprendre """ raise ScoGenError(err_page) @@ -645,7 +646,7 @@ def module_edit( "title": "Code Apogée", "size": 25, "explanation": """(optionnel) code élément pédagogique Apogée ou liste de codes ELP - séparés par des virgules (ce code est propre à chaque établissement, se rapprocher + séparés par des virgules (ce code est propre à chaque établissement, se rapprocher du référent Apogée). """, "validator": lambda val, _: len(val) < APO_CODE_STR_LEN, @@ -682,7 +683,7 @@ def module_edit( ] + ["-1"], "explanation": """Parcours dans lesquels est utilisé ce module.
- Attention: si le module ne doit pas avoir les mêmes coefficients suivant le parcours, + Attention: si le module ne doit pas avoir les mêmes coefficients suivant le parcours, il faut en créer plusieurs versions, car dans ScoDoc chaque module a ses coefficients.""", }, ) @@ -746,7 +747,7 @@ def module_edit( "input_type": "separator", "title": f"""{scu.EMO_WARNING } Pas de parcours: - associer un référentiel de compétence """, diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py index 8c77ed6a..d01c7af8 100644 --- a/app/scodoc/sco_formsemestre.py +++ b/app/scodoc/sco_formsemestre.py @@ -68,6 +68,7 @@ _formsemestreEditor = ndb.EditableTable( "ens_can_edit_eval", "elt_sem_apo", "elt_annee_apo", + "edt_id", ), filter_dept=True, sortkey="date_debut", @@ -571,7 +572,7 @@ def view_formsemestre_by_etape(etape_apo=None, fmt="html"): ), html_title=html_title, html_next_section="""
- Etape: + Etape:
""", ) tab.base_url = "%s?etape_apo=%s" % (request.base_url, etape_apo or "") diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index 1aedb43e..4f5ae61d 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -40,6 +40,7 @@ from app.models import ( ModuleImpl, Evaluation, UniteEns, + ScoDocSiteConfig, ScolarFormSemestreValidation, ScolarAutorisationInscription, ApcValidationAnnee, @@ -321,7 +322,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N "size": 40, "title": "Nom de ce semestre", "explanation": f"""n'indiquez pas les dates, ni le semestre, ni la modalité dans - le titre: ils seront automatiquement ajoutés """, "allow_null": False, @@ -445,13 +446,25 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N }, ) ) + if ScoDocSiteConfig.get("edt_ics_path"): + modform.append( + ( + "edt_id", + { + "size": 32, + "title": "Identifiant EDT", + "explanation": "optionnel, identifiant sur le logiciel emploi du temps (par défaut, utilise la première étape Apogée).", + "allow_null": True, + }, + ) + ) if edit: formtit = f""" -

Modifier les coefficients des UE capitalisées

-

Sélectionner les modules, leurs responsables et les étudiants +

Sélectionner les modules, leurs responsables et les étudiants à inscrire:

""" else: @@ -510,7 +523,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N { "input_type": "boolcheckbox", "title": "", - "explanation": """Autoriser tous les enseignants associés + "explanation": """Autoriser tous les enseignants associés à un module à y créer des évaluations""", }, ), @@ -585,8 +598,8 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N { "input_type": "separator", "title": f"""{scu.EMO_WARNING } - Pas de parcours: - vérifier la formation """, @@ -784,7 +797,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N if tf[0] == 0 or msg: return f"""

Formation {formation.titre} ({formation.acronyme}), version { formation.version}, code {formation.formation_code} @@ -969,11 +982,11 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N - {"

Modification effectuée

" if ok + {"

Modification effectuée

" if ok else "

Modules non modifiés

" } retour au tableau de bord """ @@ -1309,11 +1322,11 @@ def formsemestre_delete(formsemestre_id): html_sco_header.html_sem_header("Suppression du semestre"), """
Attention !

A n'utiliser qu'en cas d'erreur lors de la saisie d'une formation. Normalement, -un semestre ne doit jamais être supprimé +un semestre ne doit jamais être supprimé (on perd la mémoire des notes et de tous les événements liés à ce semestre !).

-

Tous les modules de ce semestre seront supprimés. +

Tous les modules de ce semestre seront supprimés. Ceci n'est possible que si :

    @@ -1497,24 +1510,24 @@ def do_formsemestre_delete(formsemestre_id): req = "DELETE FROM sco_prefs WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Suppression des groupes et partitions - req = """DELETE FROM group_membership - WHERE group_id IN + req = """DELETE FROM group_membership + WHERE group_id IN (SELECT gm.group_id FROM group_membership gm, partition p, group_descr gd - WHERE gm.group_id = gd.id AND gd.partition_id = p.id + WHERE gm.group_id = gd.id AND gd.partition_id = p.id AND p.formsemestre_id=%(formsemestre_id)s) """ cursor.execute(req, {"formsemestre_id": formsemestre_id}) - req = """DELETE FROM group_descr - WHERE id IN - (SELECT gd.id FROM group_descr gd, partition p - WHERE gd.partition_id = p.id + req = """DELETE FROM group_descr + WHERE id IN + (SELECT gd.id FROM group_descr gd, partition p + WHERE gd.partition_id = p.id AND p.formsemestre_id=%(formsemestre_id)s) """ cursor.execute(req, {"formsemestre_id": formsemestre_id}) req = "DELETE FROM partition WHERE formsemestre_id=%(formsemestre_id)s" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Responsables - req = """DELETE FROM notes_formsemestre_responsables + req = """DELETE FROM notes_formsemestre_responsables WHERE formsemestre_id=%(formsemestre_id)s""" cursor.execute(req, {"formsemestre_id": formsemestre_id}) # --- Etapes @@ -1606,7 +1619,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): Indiquez "auto" (ou laisser vide) pour que ScoDoc calcule automatiquement le coefficient, ou bien entrez une valeur (nombre réel).

    -

    Dans le doute, si le mode auto n'est pas applicable et que tous les étudiants sont inscrits aux mêmes modules de ce semestre, prenez comme coefficient la somme indiquée. +

    Dans le doute, si le mode auto n'est pas applicable et que tous les étudiants sont inscrits aux mêmes modules de ce semestre, prenez comme coefficient la somme indiquée. Sinon, référez vous au programme pédagogique. Les lignes en rouge sont à changer.

    @@ -1734,7 +1747,7 @@ def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): return f"""{html_sco_header.html_sem_header("Coefficients des UE du semestre")} {" ".join(message)} -

    Revenir au tableau de bord

    diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index 46b650b7..6e212375 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -79,7 +79,9 @@ partitionEditor = ndb.EditableTable( ) groupEditor = ndb.EditableTable( - "group_descr", "group_id", ("group_id", "partition_id", "group_name", "numero") + "group_descr", + "group_id", + ("group_id", "partition_id", "group_name", "numero", "edt_id"), ) group_list = groupEditor.list diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 90ed21d6..aecc4669 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -1052,7 +1052,7 @@ function editAssiduite(assiduite_id, etat, assi) { } /** - * Récupération des assiduités conflictuelles avec la période de la time line + * Récupération des assiduités conflictuelles avec la période de la timeline * @param {String | Number} etudid identifiant de l'étudiant * @returns {Array[Assiduité]} un tableau d'assiduité */ @@ -1328,17 +1328,17 @@ function generateEtudRow( const HTML = `
    - +
    ${index}
    @@ -1347,12 +1347,12 @@ function generateEtudRow(
    - + ${assi} - +
    - - + +
    `; return HTML; diff --git a/app/templates/assiduites/pages/config_assiduites.j2 b/app/templates/assiduites/pages/config_assiduites.j2 index 05c6a439..eb6bec56 100644 --- a/app/templates/assiduites/pages/config_assiduites.j2 +++ b/app/templates/assiduites/pages/config_assiduites.j2 @@ -13,26 +13,36 @@ affectent notamment les comptages d'absences de tous les bulletins des
    -
    -
    +
    +
    +
    + {{ form.hidden_tag() }} + {{ wtf.form_errors(form, hiddens="only") }} - - {{ form.hidden_tag() }} - {{ wtf.form_errors(form, hiddens="only") }} - - {{ wtf.form_field(form.morning_time) }} - {{ wtf.form_field(form.lunch_time) }} - {{ wtf.form_field(form.afternoon_time) }} - {{ wtf.form_field(form.tick_time) }} -
    - {{ wtf.form_field(form.submit) }} - {{ wtf.form_field(form.cancel) }} -
    - + {{ wtf.form_field(form.morning_time) }} + {{ wtf.form_field(form.lunch_time) }} + {{ wtf.form_field(form.afternoon_time) }} + {{ wtf.form_field(form.tick_time) }} +
    -
    +
    +

    Emplois du temps

    +
    ScoDoc peut récupérer les emplois du temps de chaque session.
    +
    +
    + {{ wtf.form_field(form.edt_ics_path) }} +
    + +
    + {{ wtf.form_field(form.submit) }} + {{ wtf.form_field(form.cancel) }} +
    +
    +
    + -{% endblock %} \ No newline at end of file + +{% endblock %} diff --git a/app/templates/configuration.j2 b/app/templates/configuration.j2 index 01afbc7a..7c06908a 100644 --- a/app/templates/configuration.j2 +++ b/app/templates/configuration.j2 @@ -73,8 +73,8 @@ Heure: {{ time.strftime("%d/%m/%Y %H:%M") }}

    -

    Assiduité

    -

    Configuration du suivi de l'assiduité +

    Assiduité et emplois du temps

    +

    Configuration du suivi de l'assiduité et accès aux emplois du temps

    diff --git a/app/views/scodoc.py b/app/views/scodoc.py index c2ee067d..4ecd9f60 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -323,6 +323,7 @@ def config_assiduites(): form = ConfigAssiduitesForm() if request.method == "POST" and form.cancel.data: # cancel button return redirect(url_for("scodoc.index")) + 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") @@ -330,18 +331,13 @@ def config_assiduites(): flash("Heure de midi enregistrée") if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]): flash("Heure de fin de la journée enregistrée") - if ( - form.data["tick_time"] > 0 - and form.data["tick_time"] < 60 - and 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") - else: - flash("Erreur : Granularité invalide ou identique") - + if ScoDocSiteConfig.set("edt_ics_path", form.data["edt_ics_path"]): + flash("Chemin vers les calendriers ics enregistré") return redirect(url_for("scodoc.configuration")) - elif request.method == "GET": + if request.method == "GET": form.morning_time.data = ScoDocSiteConfig.get( "assi_morning_time", datetime.time(8, 0, 0) ) @@ -356,12 +352,13 @@ 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") - return render_template( - "assiduites/pages/config_assiduites.j2", - form=form, - title="Configuration du module Assiduité", - ) + return render_template( + "assiduites/pages/config_assiduites.j2", + form=form, + title="Configuration du module Assiduité", + ) @bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])