From db44e8e5f4f2c7b8e7b44b9e52b104b8393689cd Mon Sep 17 00:00:00 2001
From: Iziram <matthias.hartmann@iziram.fr>
Date: Mon, 3 Jun 2024 15:50:35 +0200
Subject: [PATCH] =?UTF-8?q?Assiduit=C3=A9=20:=20fusion=20tableau=5Faction?=
=?UTF-8?q?=20d=C3=A9tails/modification=20closes=20#918=20#768?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/forms/assiduite/ajout_assiduite_etud.py | 38 +---
app/forms/assiduite/edit_assiduite_etud.py | 58 ++++++
app/models/assiduites.py | 18 ++
app/static/js/assiduites.js | 13 +-
app/tables/liste_assiduites.py | 23 +--
.../pages/ajout_justificatif_etud.j2 | 49 +++++-
.../assiduites/pages/calendrier_assi_etud.j2 | 2 +-
.../assiduites/pages/edit_assiduite_etud.j2 | 165 ++++++++++++++++++
app/views/assiduites.py | 158 ++++++++++++++---
9 files changed, 434 insertions(+), 90 deletions(-)
create mode 100644 app/forms/assiduite/edit_assiduite_etud.py
create mode 100644 app/templates/assiduites/pages/edit_assiduite_etud.j2
diff --git a/app/forms/assiduite/ajout_assiduite_etud.py b/app/forms/assiduite/ajout_assiduite_etud.py
index 8c3423ac..302f7377 100644
--- a/app/forms/assiduite/ajout_assiduite_etud.py
+++ b/app/forms/assiduite/ajout_assiduite_etud.py
@@ -62,6 +62,11 @@ class AjoutAssiOrJustForm(FlaskForm):
if field:
field.errors.append(err_msg)
+ def disable_all(self):
+ "Disable all fields"
+ for field in self:
+ field.render_kw = {"disabled": True}
+
date_debut = StringField(
"Date de début",
validators=[validators.Length(max=10)],
@@ -175,36 +180,3 @@ class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
validators=[DataRequired(message="This field is required.")],
)
fichiers = MultipleFileField(label="Ajouter des fichiers")
-
-
-class ChoixDateForm(FlaskForm):
- """
- Formulaire de choix de date
- (utilisé par la page de choix de date
- si la date courante n'est pas dans le semestre)
- """
-
- def __init__(self, *args, **kwargs):
- "Init form, adding a filed for our error messages"
- super().__init__(*args, **kwargs)
- self.ok = True
- self.error_messages: list[str] = [] # used to report our errors
-
- def set_error(self, err_msg, field=None):
- "Set error message both in form and field"
- self.ok = False
- self.error_messages.append(err_msg)
- if field:
- field.errors.append(err_msg)
-
- date = StringField(
- "Date",
- validators=[validators.Length(max=10)],
- render_kw={
- "class": "datepicker",
- "size": 10,
- "id": "date",
- },
- )
- submit = SubmitField("Enregistrer")
- cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
diff --git a/app/forms/assiduite/edit_assiduite_etud.py b/app/forms/assiduite/edit_assiduite_etud.py
new file mode 100644
index 00000000..ca284c01
--- /dev/null
+++ b/app/forms/assiduite/edit_assiduite_etud.py
@@ -0,0 +1,58 @@
+""" """
+
+from flask_wtf import FlaskForm
+from wtforms import SelectField, RadioField, TextAreaField, validators, SubmitField
+from app.scodoc.sco_utils import EtatAssiduite
+
+
+class EditAssiForm(FlaskForm):
+ """
+ Formulaire de modification d'une assiduité
+ """
+
+ def __init__(self, *args, **kwargs):
+ "Init form, adding a filed for our error messages"
+ super().__init__(*args, **kwargs)
+ self.ok = True
+ self.error_messages: list[str] = [] # used to report our errors
+
+ def set_error(self, err_msg, field=None):
+ "Set error message both in form and field"
+ self.ok = False
+ self.error_messages.append(err_msg)
+ if field:
+ field.errors.append(err_msg)
+
+ def disable_all(self):
+ "Disable all fields"
+ for field in self:
+ field.render_kw = {"disabled": True}
+
+ assi_etat = RadioField(
+ "État:",
+ choices=[
+ (EtatAssiduite.ABSENT.value, EtatAssiduite.ABSENT.version_lisible()),
+ (EtatAssiduite.RETARD.value, EtatAssiduite.RETARD.version_lisible()),
+ (EtatAssiduite.PRESENT.value, EtatAssiduite.PRESENT.version_lisible()),
+ ],
+ default="absent",
+ validators=[
+ validators.DataRequired("spécifiez le type d'évènement à signaler"),
+ ],
+ )
+ modimpl = SelectField(
+ "Module",
+ choices={}, # will be populated dynamically
+ )
+ description = TextAreaField(
+ "Description",
+ render_kw={
+ "id": "description",
+ "cols": 75,
+ "rows": 4,
+ "maxlength": 500,
+ },
+ )
+
+ submit = SubmitField("Enregistrer")
+ cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
diff --git a/app/models/assiduites.py b/app/models/assiduites.py
index 241e44aa..b741005e 100644
--- a/app/models/assiduites.py
+++ b/app/models/assiduites.py
@@ -360,6 +360,16 @@ class Assiduite(ScoDocModel):
return "Module non spécifié" if traduire else None
+ def get_moduleimpl_id(self) -> int | str | None:
+ """
+ Retourne le ModuleImpl associé à l'assiduité
+ """
+ if self.moduleimpl_id is not None:
+ return self.moduleimpl_id
+ if self.external_data is not None and "module" in self.external_data:
+ return self.external_data["module"]
+ return None
+
def get_saisie(self) -> str:
"""
retourne le texte "saisie le <date> par <User>"
@@ -395,6 +405,14 @@ class Assiduite(ScoDocModel):
if force:
raise ScoValueError("Module non renseigné")
+ @classmethod
+ def get_assiduite(cls, assiduite_id: int) -> "Assiduite":
+ """Assiduité ou 404, cherche uniquement dans le département courant"""
+ query = Assiduite.query.filter_by(id=assiduite_id)
+ if g.scodoc_dept:
+ query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
+ return query.first_or_404()
+
class Justificatif(ScoDocModel):
"""
diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js
index e796923f..91c651e3 100644
--- a/app/static/js/assiduites.js
+++ b/app/static/js/assiduites.js
@@ -870,21 +870,12 @@ function setupAssiduiteBubble(el, assiduite) {
const infos = document.createElement("a");
infos.className = "";
infos.textContent = `ℹ️`;
- infos.title = "Cliquez pour plus d'informations";
+ infos.title = "Détails / Modifier";
infos.target = "_blank";
- infos.href = `tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assiduite.assiduite_id}`;
-
- // Ajout d'un lien pour modifier l'assiduité
- const modifs = document.createElement("a");
- modifs.className = "";
- modifs.textContent = `📝`;
- modifs.title = "Cliquez pour modifier l'assiduité";
- modifs.target = "_blank";
- modifs.href = `tableau_assiduite_actions?type=assiduite&action=modifier&obj_id=${assiduite.assiduite_id}`;
+ infos.href = `edit_assiduite_etud/${assiduite.assiduite_id}`;
const actionsDiv = document.createElement("div");
actionsDiv.className = "assiduite-actions";
- actionsDiv.appendChild(modifs);
actionsDiv.appendChild(infos);
bubble.appendChild(actionsDiv);
diff --git a/app/tables/liste_assiduites.py b/app/tables/liste_assiduites.py
index c0c7dbbb..7b0204b9 100644
--- a/app/tables/liste_assiduites.py
+++ b/app/tables/liste_assiduites.py
@@ -600,33 +600,22 @@ class RowAssiJusti(tb.Row):
url: str
html: list[str] = []
- # Détails
- url = url_for(
- "assiduites.tableau_assiduite_actions",
- type=self.ligne["type"],
- action="details",
- obj_id=self.ligne["obj_id"],
- scodoc_dept=g.scodoc_dept,
- )
- html.append(f'<a title="Détails" href="{url}">ℹ️</a>')
-
- # Modifier
if self.ligne["type"] == "justificatif":
+ # Détails/Modifier assiduité
url = url_for(
"assiduites.edit_justificatif_etud",
justif_id=self.ligne["obj_id"],
scodoc_dept=g.scodoc_dept,
- back_url=request.url,
)
+ html.append(f'<a title="Détails/Modifier" href="{url}">ℹ️</a>')
else:
+ # Détails/Modifier assiduité
url = url_for(
- "assiduites.tableau_assiduite_actions",
- type=self.ligne["type"],
- action="modifier",
- obj_id=self.ligne["obj_id"],
+ "assiduites.edit_assiduite_etud",
+ assiduite_id=self.ligne["obj_id"],
scodoc_dept=g.scodoc_dept,
)
- html.append(f'<a title="Modifier" href="{url}">📝</a>')
+ html.append(f'<a title="Détails/Modifier" href="{url}">ℹ️</a>')
# Supprimer
url = url_for(
diff --git a/app/templates/assiduites/pages/ajout_justificatif_etud.j2 b/app/templates/assiduites/pages/ajout_justificatif_etud.j2
index 221f3b49..a6527203 100644
--- a/app/templates/assiduites/pages/ajout_justificatif_etud.j2
+++ b/app/templates/assiduites/pages/ajout_justificatif_etud.j2
@@ -44,11 +44,33 @@ div.submit > input {
</style>
<div class="tab-content">
<h2>{{title|safe}}</h2>
-
+ {% if readonly %}
+ <h3 class="rouge">Vous n'avez pas la permission de modifier ce justificatif</h3>
+ {% endif %}
{% if justif %}
+ <div class="informations">
+
<div class="info-saisie">
- Saisie par {{justif.user.get_prenomnom() if justif.user else "inconnu"}}
- le {{justif.entry_date.strftime(scu.DATEATIME_FMT) if justif.entry_date else "?"}}
+ <span>Saisie par {{justif.saisie_par}} le {{justif.entry_date}}</span>
+ </div>
+
+ <div class="info-row">
+ <span class="info-label">Assiduités concernées: </span>
+ {% if justif.justification.assiduites %}
+ <ul>
+ {% for assi in justif.justification.assiduites %}
+ <li><a href="{{url_for('assiduites.edit_assiduite_etud',
+ assiduite_id=assi.assiduite_id, scodoc_dept=g.scodoc_dept)
+ }}" target="_blank">{{assi.etat}} du {{assi.date_debut}} au
+ {{assi.date_fin}}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <span class="text">Aucune</span>
+ {% endif %}
+ </div>
+
</div>
{% endif %}
@@ -110,7 +132,9 @@ div.submit > input {
{% for filename in filenames %}
<li><span data-justif_id="{{justif.id}}" class="suppr_fichier_just"
>{{scu.icontag("delete_img", alt="supprimer", title="Supprimer")|safe}}</span>
- {{filename}}</li>
+ <a href="{{url_for('apiweb.justif_export',justif_id=justif.justif_id,
+ filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
+ </li>
{% endfor %}
</ul>
{% endif %}
@@ -126,11 +150,28 @@ div.submit > input {
<span class="help" style="margin-left: 12px;">laisser vide pour date courante</span>
{{ render_field_errors(form, 'entry_date') }}
+ {% if readonly == False %}
{# Submit #}
<div class="submit">
{{ form.submit }} {{ form.cancel }}
</div>
+ <div class="info-row">
+ <a
+ style="color:red;"
+ href="{{url_for(
+ 'assiduites.tableau_assiduite_actions',
+ type='justificatif',
+ action='supprimer',
+ obj_id=justif.justif_id,
+ scodoc_dept=g.scodoc_dept,
+ )}}"
+ >Supprimer le justificatif</a>
+ </div>
+ {% endif %}
+
+
+
</fieldset>
</form>
</section>
diff --git a/app/templates/assiduites/pages/calendrier_assi_etud.j2 b/app/templates/assiduites/pages/calendrier_assi_etud.j2
index d7822294..b4fa3854 100644
--- a/app/templates/assiduites/pages/calendrier_assi_etud.j2
+++ b/app/templates/assiduites/pages/calendrier_assi_etud.j2
@@ -242,7 +242,7 @@ Calendrier de l'assiduité
document.querySelectorAll('[assi_id]').forEach((el, i) => {
el.addEventListener('click', () => {
const assi_id = el.getAttribute('assi_id');
- window.open(`${SCO_URL}Assiduites/tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assi_id}`);
+ window.open(`${SCO_URL}Assiduites/edit_assiduite_etud/${assi_id}`);
})
});
</script>
diff --git a/app/templates/assiduites/pages/edit_assiduite_etud.j2 b/app/templates/assiduites/pages/edit_assiduite_etud.j2
new file mode 100644
index 00000000..8b0930e8
--- /dev/null
+++ b/app/templates/assiduites/pages/edit_assiduite_etud.j2
@@ -0,0 +1,165 @@
+{# Ajout d'une "assiduité" sur un étudiant #}
+
+{% extends "sco_page.j2" %}
+{% import 'wtf.j2' as wtf %}
+
+
+{% block styles %}
+{{super()}}
+<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
+
+<style>
+ .info-row {
+ margin-top: 12px;
+ }
+
+ .info-label {
+ font-weight: bold;
+ }
+ #assi_etat{
+ list-style: none;
+ }
+
+ .info-etat {
+ font-size: 110%;
+ font-weight: bold;
+ background-color: rgb(253, 234, 210);
+ border: 1px solid grey;
+ border-radius: 4px;
+ padding: 4px;
+ }
+
+ .info-saisie {
+ margin-top: 12px;
+ margin-bottom: 12px;
+ font-style: italic;
+ }
+</style>
+{% endblock %}
+
+{% block app_content %}
+<div class="tab-content">
+ <h2>Détails Assiduité concernant {{etud.html_link_fiche()|safe}}</h2>
+
+ <div id="informations">
+ <div class="info-saisie">
+ <span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
+ </div>
+ <div class="info-row">
+ <span class="info-label">Période :</span> du <b>{{objet.date_debut}}</b> au <b>{{objet.date_fin}}</b>
+ </div>
+ <div class="info-row">
+ <span class="info-label">Module :</span> {{objet.module}}
+ </div>
+ <div class="info-row">
+ <span class="info-label">État de l'assiduité :</span><span class="info-etat">{{objet.etat}}</span>
+ </div>
+ <div class="info-row">
+ <span class="info-label">Description:</span>
+ {% if objet.description != "" and objet.description is not None %}
+ <span class="text">{{objet.description}}</span>
+ {% else %}
+ <span class="text fontred">Pas de description</span>
+ {% endif %}
+ </span>
+ </div>
+ {# Affichage des justificatifs si assiduité justifiée #}
+ {% if objet.etat != "Présence" %}
+ <div class="info-row">
+ <span class="info-label">Justifiée: </span>
+ {% if objet.justification.est_just %}
+ <span class="text">Oui</span>
+ {% else %}
+ <span class="text fontred">Non</span>
+ {% if not objet.justification.justificatifs %}
+ <a
+ href="{{url_for(
+ 'assiduites.tableau_assiduite_actions',
+ type='assiduite',
+ action='justifier',
+ obj_id=objet.assiduite_id,
+ scodoc_dept=g.scodoc_dept,
+ )}}"
+ >Justifier l'assiduité</a>
+ {% endif %}
+ {% endif %}
+ </div>
+ <div class="info-row">
+ {% if not objet.justification.justificatifs %}
+ <span class="text info-label">Pas de justificatif associé</span>
+ {% else %}
+ <span class="text info-label">Justificatifs associés:</span>
+ <ul>
+ {% for justi in objet.justification.justificatifs %}
+ <li>
+ <a href="{{url_for('assiduites.edit_justificatif_etud',
+ justif_id=justi.justif_id,scodoc_dept=g.scodoc_dept)}}"
+ target="_blank" rel="noopener noreferrer" style="{{'color:red;' if justi.etat != 'Valide'}}">Justificatif {{justi.etat}} du {{justi.date_debut}} au
+ {{justi.date_fin}}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </div>
+ {% endif %}
+ </div>
+
+ {% if readonly != True %}
+ <h2 style="margin-top: 24px;">Modification de l'assiduité</h2>
+ {% for err_msg in form.error_messages %}
+ <div class="wtf-error-messages">
+ {{ err_msg }}
+ </div>
+ {% endfor %}
+
+ <form id="edit-assiduite-form" method="post">
+ {{ form.hidden_tag() }}
+ {# Type d'évènement #}
+ <div class="radio-assi_etat">
+ {{ form.assi_etat.label }}
+ {{ form.assi_etat() }}
+ </div>
+ {# Menu module #}
+ <div class="select-module">
+ {{ form.modimpl.label }} :
+ {{ form.modimpl }}
+ {{ render_field_errors(form, 'modimpl') }}
+ </div>
+ {# Description #}
+ <div>
+ <div>{{ form.description.label }}</div>
+ {{ form.description() }}
+ {{ render_field_errors(form, 'description') }}
+ </div>
+ {# Submit #}
+ <div class="submit info-row">
+ {{ form.submit }} {{ form.cancel }}
+ </div>
+
+
+ </form>
+ <div class="info-row">
+ <a
+ style="color:red;"
+ href="{{url_for(
+ 'assiduites.tableau_assiduite_actions',
+ type='assiduite',
+ action='supprimer',
+ obj_id=objet.assiduite_id,
+ scodoc_dept=g.scodoc_dept,
+ )}}"
+ >Supprimer l'assiduité</a>
+ </div>
+ {% else %}
+ <h3 class="rouge">Vous n'avez pas la permission de modifier cette assiduité</h3>
+ {% endif %}
+
+</div>
+
+{% endblock app_content %}
+
+{% block scripts %}
+{{ super() }}
+<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
+{% include "sco_timepicker.j2" %}
+{% endblock scripts %}
\ No newline at end of file
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index e71a3246..b98d61ec 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -36,6 +36,7 @@ from flask_login import current_user
from flask_sqlalchemy.query import Query
from markupsafe import Markup
+from werkzeug.exceptions import HTTPException
from app import db, log
from app.comp import res_sem
@@ -48,8 +49,8 @@ from app.forms.assiduite.ajout_assiduite_etud import (
AjoutAssiOrJustForm,
AjoutAssiduiteEtudForm,
AjoutJustificatifEtudForm,
- ChoixDateForm,
)
+from app.forms.assiduite.edit_assiduite_etud import EditAssiForm
from app.models import (
Assiduite,
Departement,
@@ -538,10 +539,8 @@ def _record_assiduite_etud(
assi: Assiduite = conflits.first()
lien: str = url_for(
- "assiduites.tableau_assiduite_actions",
- type="assiduite",
- action="details",
- obj_id=assi.assiduite_id,
+ "assiduites.edit_assiduite_etud",
+ assiuite_id=assi.assiduite_id,
scodoc_dept=g.scodoc_dept,
)
@@ -612,7 +611,7 @@ def bilan_etud():
@bp.route("/edit_justificatif_etud/<int:justif_id>", methods=["GET", "POST"])
@scodoc
-@permission_required(Permission.AbsChange)
+@permission_required(Permission.ScoView)
def edit_justificatif_etud(justif_id: int):
"""
Edition d'un justificatif.
@@ -624,8 +623,19 @@ def edit_justificatif_etud(justif_id: int):
Returns:
str: l'html généré
"""
- justif = Justificatif.get_justificatif(justif_id)
+ try:
+ justif = Justificatif.get_justificatif(justif_id)
+ except HTTPException:
+ flash("Justificatif invalide")
+ return redirect(url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept))
+
+ readonly = not current_user.has_permission(Permission.AbsChange)
+
form = AjoutJustificatifEtudForm(obj=justif)
+
+ if readonly:
+ form.disable_all()
+
# Set the default value for the etat field
if request.method == "GET":
form.date_debut.data = justif.date_debut.strftime(scu.DATE_FMT)
@@ -652,7 +662,9 @@ def edit_justificatif_etud(justif_id: int):
)
if form.validate_on_submit():
- if form.cancel.data: # cancel button
+ if form.cancel.data or not current_user.has_permission(
+ Permission.AbsChange
+ ): # cancel button
return redirect(redirect_url)
if _record_justificatif_etud(justif.etudiant, form, justif):
return redirect(redirect_url)
@@ -667,12 +679,13 @@ def edit_justificatif_etud(justif_id: int):
etud=justif.etudiant,
filenames=filenames,
form=form,
- justif=justif,
+ justif=_preparer_objet("justificatif", justif),
nb_files=nb_files,
title=f"Modification justificatif absence de {justif.etudiant.html_link_fiche()}",
redirect_url=redirect_url,
sco=ScoData(justif.etudiant),
scu=scu,
+ readonly=not current_user.has_permission(Permission.AbsChange),
)
@@ -1672,20 +1685,18 @@ def _preparer_objet(
# Gestion justification
- if not objet.est_just:
- objet_prepare["justification"] = {"est_just": False}
- else:
- objet_prepare["justification"] = {"est_just": True, "justificatifs": []}
+ objet_prepare["justification"] = {
+ "est_just": objet.est_just,
+ "justificatifs": [],
+ }
- if not sans_gros_objet:
- justificatifs: list[int] = get_assiduites_justif(
- objet.assiduite_id, False
+ if not sans_gros_objet:
+ justificatifs: list[int] = get_assiduites_justif(objet.assiduite_id, False)
+ for justi_id in justificatifs:
+ justi: Justificatif = Justificatif.query.get(justi_id)
+ objet_prepare["justification"]["justificatifs"].append(
+ _preparer_objet("justificatif", justi, sans_gros_objet=True)
)
- for justi_id in justificatifs:
- justi: Justificatif = Justificatif.query.get(justi_id)
- objet_prepare["justification"]["justificatifs"].append(
- _preparer_objet("justificatif", justi, sans_gros_objet=True)
- )
else: # objet == "justificatif"
justif: Justificatif = objet
@@ -1698,9 +1709,8 @@ def _preparer_objet(
objet_prepare["justification"] = {"assiduites": [], "fichiers": {}}
if not sans_gros_objet:
- assiduites: list[int] = scass.justifies(justif)
- for assi_id in assiduites:
- assi: Assiduite = Assiduite.query.get(assi_id)
+ assiduites: list[Assiduite] = justif.get_assiduites()
+ for assi in assiduites:
objet_prepare["justification"]["assiduites"].append(
_preparer_objet("assiduite", assi, sans_gros_objet=True)
)
@@ -2152,6 +2162,106 @@ def signal_assiduites_hebdo():
)
+@bp.route("edit_assiduite_etud/<int:assiduite_id>", methods=["GET", "POST"])
+@scodoc
+@permission_required(Permission.ScoView)
+def edit_assiduite_etud(assiduite_id: int):
+ """
+ Page affichant les détails d'une assiduité
+ Si le current_user alors la page propose un formulaire de modification
+ """
+ try:
+ assi: Assiduite = Assiduite.get_assiduite(assiduite_id=assiduite_id)
+ except HTTPException:
+ flash("Assiduité invalide")
+ return redirect(url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept))
+
+ etud: Identite = assi.etudiant
+ formsemestre: FormSemestre = assi.get_formsemestre()
+
+ readonly: bool = not current_user.has_permission(Permission.AbsChange)
+
+ form: EditAssiForm = EditAssiForm(request.form)
+ if readonly:
+ form.disable_all()
+
+ # peuplement moduleimpl_select
+ modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
+ choices: OrderedDict = OrderedDict()
+ choices[""] = [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
+
+ # indique le nom du semestre dans le menu (optgroup)
+ group_name: str = formsemestre.titre_annee()
+ choices[group_name] = [
+ (m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
+ for m in modimpls_by_formsemestre[formsemestre.id]
+ if m.module.ue.type == UE_STANDARD
+ ]
+
+ choices.move_to_end("", last=False)
+ form.modimpl.choices = choices
+
+ # Vérification formulaire
+ if form.validate_on_submit():
+ if form.cancel.data: # cancel button
+ return redirect(request.referrer)
+
+ # vérification des valeurs
+
+ # Gestion de l'état
+ etat = form.assi_etat.data
+ try:
+ etat = int(etat)
+ etat = scu.EtatAssiduite.inverse().get(etat, None)
+ except ValueError:
+ etat = None
+
+ if etat is None:
+ form.error_messages.append("État invalide")
+ form.ok = False
+
+ description = form.description.data or ""
+ description = description.strip()
+ moduleimpl_id = form.modimpl.data or -1
+ if isinstance(moduleimpl_id, int):
+ try:
+ ModuleImpl.get_moduleimpl(moduleimpl_id)
+ except ValueError:
+ form.error_messages.append("Module invalide")
+ moduleimpl_id = -1
+ form.ok = False
+
+ if form.ok:
+ assi.etat = etat
+ assi.description = description
+ if moduleimpl_id != -1:
+ assi.set_moduleimpl(moduleimpl_id)
+
+ db.session.add(assi)
+ db.session.commit()
+
+ scass.simple_invalidate_cache(assi.to_dict(format_api=True), assi.etudid)
+
+ flash("enregistré")
+ return redirect(request.referrer)
+
+ # Remplissage du formulaire
+ form.assi_etat.data = str(assi.etat)
+ form.description.data = assi.description
+ moduleimpl_id: int | str | None = assi.get_moduleimpl_id() or ""
+ form.modimpl.data = str(moduleimpl_id)
+
+ return render_template(
+ "assiduites/pages/edit_assiduite_etud.j2",
+ etud=etud,
+ sco=ScoData(etud, formsemestre=formsemestre),
+ form=form,
+ readonly=readonly,
+ objet=_preparer_objet("assiduite", assi),
+ title=f"Assiduité {etud.nom_short}",
+ )
+
+
def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
"""Génère la liste des assiduités d'un étudiant pour le bulletin mail"""
--
GitLab