diff --git a/app/api/jury.py b/app/api/jury.py index 7de6cdef08..6103d11649 100644 --- a/app/api/jury.py +++ b/app/api/jury.py @@ -10,7 +10,7 @@ from flask import g, url_for from flask_json import as_json -from flask_login import login_required +from flask_login import current_user, login_required import app from app import db, log @@ -29,6 +29,7 @@ from app.models import ( ) from app.scodoc import sco_cache from app.scodoc.sco_permissions import Permission +from app.scodoc.sco_utils import json_error @bp.route("/formsemestre//decisions_jury") @@ -73,7 +74,7 @@ def _news_delete_jury_etud(etud: Identite): ) @login_required @scodoc -@permission_required(Permission.ScoEtudInscrit) +@permission_required(Permission.ScoView) @as_json def validation_ue_delete(etudid: int, validation_id: int): "Efface cette validation" @@ -90,7 +91,7 @@ def validation_ue_delete(etudid: int, validation_id: int): ) @login_required @scodoc -@permission_required(Permission.ScoEtudInscrit) +@permission_required(Permission.ScoView) @as_json def validation_formsemestre_delete(etudid: int, validation_id: int): "Efface cette validation" @@ -106,6 +107,24 @@ def _validation_ue_delete(etudid: int, validation_id: int): validation = ScolarFormSemestreValidation.query.filter_by( id=validation_id, etudid=etudid ).first_or_404() + # Vérification de la permission: + # A le droit de supprimer cette validation: le chef de dept ou quelqu'un ayant + # le droit de saisir des décisions de jury dans le formsemestre concerné s'il y en a un + # (c'est le cas pour les validations de jury, mais pas pour les "antérieures" non + # rattachées à un formsemestre) + if not g.scodoc_dept: # accès API + if not current_user.has_permission(Permission.ScoEtudInscrit): + return json_error(403, "validation_delete: non autorise") + else: + if validation.formsemestre: + if ( + validation.formsemestre.dept_id != g.scodoc_dept_id + ) or not validation.formsemestre.can_edit_jury(): + return json_error(403, "validation_delete: non autorise") + elif not current_user.has_permission(Permission.ScoEtudInscrit): + # Validation non rattachée à un semestre: on doit être chef + return json_error(403, "validation_delete: non autorise") + log(f"validation_ue_delete: etuid={etudid} {validation}") db.session.delete(validation) sco_cache.invalidate_formsemestre_etud(etud) diff --git a/app/models/validations.py b/app/models/validations.py index 7686d78976..10791758a0 100644 --- a/app/models/validations.py +++ b/app/models/validations.py @@ -8,6 +8,8 @@ from app import log from app.models import SHORT_STR_LEN from app.models import CODE_STR_LEN from app.models.events import Scolog +from app.scodoc import sco_cache +from app.scodoc import sco_utils as scu class ScolarFormSemestreValidation(db.Model): @@ -70,6 +72,14 @@ class ScolarFormSemestreValidation(db.Model): return f"""décision sur semestre {self.formsemestre.titre_mois()} du { self.event_date.strftime("%d/%m/%Y")}""" + def delete(self): + "Efface cette validation" + log(f"{self.__class__.__name__}.delete({self})") + etud = self.etud + db.session.delete(self) + db.session.commit() + sco_cache.invalidate_formsemestre_etud(etud) + def to_dict(self) -> dict: "as a dict" d = dict(self.__dict__) @@ -79,15 +89,22 @@ class ScolarFormSemestreValidation(db.Model): def html(self, detail=False) -> str: "Affichage html" if self.ue_id is not None: - return f"""Validation de l'UE {self.ue.acronyme} + moyenne = ( + f", moyenne {scu.fmt_note(self.moy_ue)}/20 " + if self.moy_ue is not None + else "" + ) + return f"""Validation + {'externe' if self.is_external else ""} + de l'UE {self.ue.acronyme} {('parcours ' + ", ".join([p.code for p in self.ue.parcours])) + "" if self.ue.parcours else ""} de {self.ue.formation.acronyme} {("émise par " + self.formsemestre.html_link_status()) - if self.formsemestre else ""} - : {self.code} + if self.formsemestre else "externe/antérieure"} + : {self.code}{moyenne} le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")} """ else: diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py index c827207b55..d8f62b983e 100644 --- a/app/scodoc/sco_bulletins.py +++ b/app/scodoc/sco_bulletins.py @@ -1238,7 +1238,7 @@ def make_menu_autres_operations( "enabled": current_user.has_permission(Permission.ScoImplement), }, { - "title": "Enregistrer une validation d'UE antérieure", + "title": "Gérer les validations d'UEs antérieures", "endpoint": "notes.formsemestre_validate_previous_ue", "args": { "formsemestre_id": formsemestre.id, diff --git a/app/scodoc/sco_cursus_dut.py b/app/scodoc/sco_cursus_dut.py index 3cf2897fa3..9eab3da6b7 100644 --- a/app/scodoc/sco_cursus_dut.py +++ b/app/scodoc/sco_cursus_dut.py @@ -972,7 +972,7 @@ def do_formsemestre_validate_ue( moy_ue = ue_status["moy"] if ue_status else "" args["moy_ue"] = moy_ue log("formsemestre_validate_ue: create %s" % args) - if code != None: + if code is not None: scolar_formsemestre_validation_create(cnx, args) else: log("formsemestre_validate_ue: code is None, not recording validation") diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index fbe8e2be96..90f01f04ee 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -502,7 +502,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No else: clone_form = "" bonus_div = """
""" - ue_div = """
""" + ue_div = """
""" return ( "\n".join(H) + tf[1] @@ -1375,13 +1375,12 @@ def _ue_table_modules( return "\n".join(H) -def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None): +def ue_sharing_code(ue_code: str = "", ue_id: int = None, hide_ue_id: int = None): """HTML list of UE sharing this code Either ue_code or ue_id may be specified. hide_ue_id spécifie un id à retirer de la liste. """ - ue_code = str(ue_code) - if ue_id: + if ue_id is not None: ue = UniteEns.query.get_or_404(ue_id) if not ue_code: ue_code = ue.ue_code @@ -1400,29 +1399,36 @@ def ue_sharing_code(ue_code=None, ue_id=None, hide_ue_id=None): .filter_by(dept_id=g.scodoc_dept_id) ) - if hide_ue_id: # enlève l'ue de depart + if hide_ue_id is not None: # enlève l'ue de depart q_ues = q_ues.filter(UniteEns.id != hide_ue_id) ues = q_ues.all() + msg = " dans les formations du département " if not ues: - if ue_id: - return ( - f"""Seule UE avec code {ue_code or '-'}""" - ) + if ue_id is not None: + return f"""Seule UE avec code { + ue_code if ue_code is not None else '-'}{msg}""" else: - return f"""Aucune UE avec code {ue_code or '-'}""" + return f"""Aucune UE avec code { + ue_code if ue_code is not None else '-'}{msg}""" H = [] if ue_id: H.append( - f"""Autres UE avec le code {ue_code or '-'}:""" + f"""Pour information, autres UEs avec le code { + ue_code if ue_code is not None else '-'}{msg}:""" ) else: - H.append(f"""UE avec le code {ue_code or '-'}:""") + H.append( + f"""UE avec le code { + ue_code if ue_code is not None else '-'}{msg}:""" + ) H.append("