diff --git a/app/api/assiduites.py b/app/api/assiduites.py index 2a2dd5e28e..4116a2cc0b 100644 --- a/app/api/assiduites.py +++ b/app/api/assiduites.py @@ -532,7 +532,7 @@ def assiduite_create(etudid: int = None, nip=None, ine=None): # On créé l'assiduité # 200 + obj si réussi # 404 + message d'erreur si non réussi - code, obj = create_one_assiduite(data, etud) + code, obj = _create_one(data, etud) if code == 404: errors.append({"indice": i, "message": obj}) else: @@ -590,7 +590,7 @@ def assiduites_create(): # route sans département set_sco_dept(etud.departement.acronym) - code, obj = create_one_assiduite(data, etud) + code, obj = _create_one(data, etud) if code == 404: errors.append({"indice": i, "message": obj}) else: @@ -600,14 +600,14 @@ def assiduites_create(): return {"errors": errors, "success": success} -def create_one_assiduite( +def _create_one( data: dict, etud: Identite, ) -> tuple[int, object]: """ - create_one_assiduite: création d'une assiduité à partir d'un dict + Création d'une assiduité à partir d'un dict - Cette fonction vérifie les données du dict (qui vient du JSON API ou d'ailleurs) + Cette fonction vérifie les données du dict (qui vient du JSON API) Puis crée l'assiduité si la représentation est valide. @@ -761,7 +761,7 @@ def assiduite_delete(): # Pour chaque assiduite_id on essaye de supprimer l'assiduité for i, assiduite_id in enumerate(assiduites_list): - # De la même façon que "create_one_assiduite" + # De la même façon que "_create_one" # Ici le code est soit 200 si réussi ou 404 si raté # Le message est le message d'erreur si erreur code, msg = _delete_one(assiduite_id) diff --git a/app/forms/assiduite/ajout_assiduite_etud.py b/app/forms/assiduite/ajout_assiduite_etud.py index 70a8b494ce..d4e31ddb3b 100644 --- a/app/forms/assiduite/ajout_assiduite_etud.py +++ b/app/forms/assiduite/ajout_assiduite_etud.py @@ -101,5 +101,14 @@ class AjoutAssiduiteEtudForm(FlaskForm): "maxlength": 500, }, ) + entry_date = StringField( + "Date de dépot ou saisie", + validators=[validators.Length(max=10)], + render_kw={ + "class": "datepicker", + "size": 10, + "id": "entry_date", + }, + ) submit = SubmitField("Enregistrer") cancel = SubmitField("Annuler", render_kw={"formnovalidate": True}) diff --git a/app/models/assiduites.py b/app/models/assiduites.py index 0330d6bb1b..3d4cfb1503 100644 --- a/app/models/assiduites.py +++ b/app/models/assiduites.py @@ -135,7 +135,10 @@ class Assiduite(db.Model): external_data: dict = None, notify_mail=False, ) -> "Assiduite": - """Créer une nouvelle assiduité pour l'étudiant""" + """Créer une nouvelle assiduité pour l'étudiant. + Les datetime doivent être en timzone serveur. + Raises ScoValueError en cas de conflit ou erreur. + """ if date_debut.tzinfo is None: log( f"Warning: create_assiduite: date_debut without timezone ({date_debut})" diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 8c87edf05f..67b5540499 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -75,9 +75,10 @@ MAX_TEXT_LEN = 64 * 1024 STATIC_DIR = ( os.environ.get("SCRIPT_NAME", "") + "/ScoDoc/static/links/" + sco_version.SCOVERSION ) -# La time zone du serveur: + # Attention: suppose que la timezone utilisée par postgresql soit la même ! TIME_ZONE = timezone("/".join(os.path.realpath("/etc/localtime").split("/")[-2:])) +"La timezone du serveur" # ----- CIVILITE ETUDIANTS CIVILITES = {"M": "M.", "F": "Mme", "X": ""} @@ -252,7 +253,9 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No def localize_datetime(date: datetime.datetime or str) -> datetime.datetime: - """Ajoute un timecode UTC à la date donnée.""" + """Ajoute un timecode UTC à la date donnée. + XXX semble faire autre chose... TODO fix this comment + """ if isinstance(date, str): date = is_iso_formated(date, convert=True) diff --git a/app/templates/assiduites/pages/ajout_assiduite_etud.j2 b/app/templates/assiduites/pages/ajout_assiduite_etud.j2 index ed91b4218d..dbffe8f8a0 100644 --- a/app/templates/assiduites/pages/ajout_assiduite_etud.j2 +++ b/app/templates/assiduites/pages/ajout_assiduite_etud.j2 @@ -41,6 +41,13 @@ div.radio-assi_etat input[type="radio"]:checked + label { /* Style for checked state */ font-weight: bold; } + +div.submit { + margin-top: 12px; +} +div.submit > input { + margin-right: 16px; +}
@@ -89,8 +96,12 @@ div.radio-assi_etat input[type="radio"]:checked + label { {{ form.assi_raison() }} {{ render_field_errors(form, 'assi_raison') }}
+ {# Date dépot #} + {{ form.entry_date.label }} : {{ form.entry_date }} + laisser vide pour date courante + {{ render_field_errors(form, 'entry_date') }} {# Submit #} -
+
{{ form.submit }} {{ form.cancel }}
diff --git a/app/views/assiduites.py b/app/views/assiduites.py index ba5aa61831..367f6e73de 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -32,7 +32,6 @@ from flask import abort, url_for, redirect, Response from flask_login import current_user from app import db -from app.api.assiduites import create_one_assiduite from app.comp import res_sem from app.comp.res_compat import NotesTableCompat from app.decorators import ( @@ -41,17 +40,18 @@ from app.decorators import ( ) from app.forms.assiduite.ajout_assiduite_etud import AjoutAssiduiteEtudForm from app.models import ( - FormSemestre, - Identite, - ScoDocSiteConfig, Assiduite, - Justificatif, Departement, Evaluation, + FormSemestre, + Identite, + Justificatif, + ModuleImpl, + ScoDocSiteConfig, ) from app.scodoc.codes_cursus import UE_STANDARD from app.auth.models import User -from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified +from app.models.assiduites import get_assiduites_justif import app.tables.liste_assiduites as liste_assi from app.views import assiduites_bp as bp @@ -396,6 +396,12 @@ def _record_assiduite_etud( ok = False else: moduleimpl_id = None + # La date de dépot (si viden date actuelle) + dt_entry_date = ( + datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y") + if form.entry_date.data + else None + ) if not ok: return False # Vérifie cohérence des dates/heures @@ -404,18 +410,40 @@ def _record_assiduite_etud( if dt_fin <= dt_debut: form.errors["general_errors"] = ["Erreur: dates début/fin incohérentes"] return False - data = { - "date_debut": dt_debut.isoformat(), - "date_fin": dt_fin.isoformat(), - "etat": form.assi_etat.data, - "moduleimpl_id": moduleimpl_id, - } - ok, result = create_one_assiduite(data, etud) - if ok == 200: - # assiduite_id = result["assiduite_id"] + # Ajoute time zone serveur + dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut) + dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin) + dt_entry_date_tz_server = ( + scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None + ) + external_data = None + moduleimpl: ModuleImpl | None = None + match moduleimpl_id: + case "autre": + external_data = {"module": "Autre"} + case None: + moduleimpl = None + case _: + moduleimpl = ModuleImpl.query.get(moduleimpl_id) + try: + ass = Assiduite.create_assiduite( + etud, + dt_debut_tz_server, + dt_fin_tz_server, + scu.EtatAssiduite.get(form.assi_etat.data), + description=form.assi_raison.data, + entry_date=dt_entry_date_tz_server, + external_data=external_data, + moduleimpl=moduleimpl, + notify_mail=True, + user_id=current_user.id, + ) + db.session.add(ass) + db.session.commit() return True - form.errors["general_errors"] = [f"Erreur: {result}"] - return False + except ScoValueError as exc: + form.errors["general_errors"] = [f"Erreur: {exc.args[0]}"] + return False # # Génération de la page # return HTMLBuilder(