diff --git a/app/models/ues.py b/app/models/ues.py index 5c458620b..5ff4258b1 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -184,11 +184,8 @@ class UniteEns(models.ScoDocModel): """ return 1 if self.semestre_idx is None else (self.semestre_idx - 1) // 2 + 1 - def is_locked(self): - """True if UE should not be modified - (contains modules used in a locked formsemestre) - """ - # XXX todo : à ré-écrire avec SQLAlchemy + def is_locked(self) -> tuple[bool, str]: + """True if UE should not be modified""" from app.scodoc import sco_edit_ue return sco_edit_ue.ue_is_locked(self.id) diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py index 805fa1fb5..8943b2342 100644 --- a/app/scodoc/sco_edit_apc.py +++ b/app/scodoc/sco_edit_apc.py @@ -215,9 +215,11 @@ def html_ue_infos(ue): and ue.modules.count() == 0 and ue.matieres.count() == 0 ) + titre = f"UE {ue.acronyme} {ue.titre or ''}" return render_template( "pn/ue_infos.j2", - titre=f"UE {ue.acronyme} {ue.titre or ''}", + title=titre, # titre html de la page + titre=titre, # dans la page ue=ue, formsemestres=formsemestres, nb_etuds_valid_ue=nb_etuds_valid_ue, diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py index 40989539e..5dc218e9c 100644 --- a/app/scodoc/sco_edit_ue.py +++ b/app/scodoc/sco_edit_ue.py @@ -44,6 +44,8 @@ from app.models import ( FormSemestreUEComputationExpr, FormSemestreUECoef, Matiere, + Module, + ModuleImpl, UniteEns, ) from app.models import ApcValidationRCUE, ScolarFormSemestreValidation, ScolarEvent @@ -59,7 +61,6 @@ from app.scodoc.sco_exceptions import ( ScoNonEmptyFormationObject, ) -from app.scodoc import html_sco_header from app.scodoc import codes_cursus from app.scodoc import sco_edit_apc from app.scodoc import sco_edit_matiere @@ -1066,10 +1067,12 @@ du programme" (menu "Semestre") si vous avez un semestre en cours); warn, _ = sco_formsemestre_validation.check_formation_ues(formation) H.append(warn) + titre = f"Programme {formation.acronyme} v{formation.version}" return render_template( "sco_page_dept.j2", content="".join(H), - page_title=f"Formation {formation.acronyme} v{formation.version}", + title=titre, + page_title=titre, cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/ue_table.css"], javascripts=[ "libjs/jinplace-1.2.1.min.js", @@ -1200,7 +1203,8 @@ def _ue_table_ues( }">transformer en UE ordinaire """ ) H.append("") - ue_editable = editable and not ue_is_locked(ue["ue_id"]) + ue_locked, ue_locked_reason = ue_is_locked(ue["ue_id"]) + ue_editable = editable and not ue_locked if ue_editable: H.append( f"""modifier""" ) else: - H.append('[verrouillé]') + H.append( + f'[verrouillée: {ue_locked_reason}]' + ) H.append( _ue_table_matieres( parcours, @@ -1500,8 +1506,10 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False): # check ue_id = args["ue_id"] ue = ue_list({"ue_id": ue_id})[0] - if (not bypass_lock) and ue_is_locked(ue["ue_id"]): - raise ScoLockedFormError() + if not bypass_lock: + ue_locked, ue_locked_reason = ue_is_locked(ue["ue_id"]) + if ue_locked: + raise ScoLockedFormError(msg=f"UE verrouillée: {ue_locked_reason}") # check: acronyme unique dans cette formation if "acronyme" in args: new_acro = args["acronyme"] @@ -1525,20 +1533,38 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False): formation.invalidate_module_coefs() -def ue_is_locked(ue_id): - """True if UE should not be modified - (contains modules used in a locked formsemestre) +def ue_is_locked(ue_id: int) -> tuple[bool, str]: + """True if UE should not be modified: + utilisée dans un formsemestre verrouillé ou validations de jury de cette UE. + Renvoie aussi une explication. """ - r = ndb.SimpleDictFetch( - """SELECT ue.id - FROM notes_ue ue, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi - WHERE ue.id = mod.ue_id - AND mi.module_id = mod.id AND mi.formsemestre_id = sem.id - AND ue.id = %(ue_id)s AND sem.etat = false - """, - {"ue_id": ue_id}, - ) - return len(r) > 0 + # before 9.7.23: contains modules used in a locked formsemestre + # starting from 9.7.23: + existence de validations de jury de cette UE + ue = UniteEns.query.get(ue_id) + if not ue: + return True, "inexistante" + if ue.formation.is_apc(): + # en APC, interdit toute modification d'UE si utilisée dans un semestre verrouillé + if False in [formsemestre.etat for formsemestre in ue.formation.formsemestres]: + return True, "utilisée dans un semestre verrouillé" + else: + # en classique: interdit si contient des modules utilisés dans des semestres verrouillés + # en effet, dans certaines (très anciennes) formations, une UE peut avoir des modules de + # différents semestre + if ( + Module.query.filter(Module.ue_id == ue_id) + .join(Module.modimpls) + .join(ModuleImpl.formsemestre) + .filter_by(etat=False) + .count() + ): + return True, "avec modules utilisés dans des semestres verrouillés" + + nb_validations = ScolarFormSemestreValidation.query.filter_by(ue_id=ue_id).count() + if nb_validations > 0: + return True, f"avec {nb_validations} validations de jury" + + return False, "" UE_PALETTE = [ diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index a54cb42c2..06234400e 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -1185,6 +1185,9 @@ span.trombi_box a img { } /* markup non semantique pour les cas simples */ +.smallnote { + font-size: 80%; +} .fontred { color: red; diff --git a/app/templates/pn/form_ues.j2 b/app/templates/pn/form_ues.j2 index 823579004..57ac315d8 100644 --- a/app/templates/pn/form_ues.j2 +++ b/app/templates/pn/form_ues.j2 @@ -81,10 +81,13 @@ {% endfor %} {% endif %} - {% if editable and not ue.is_locked() %} - modifier + {% else %} + {{ue_is_locked[1]}} {% endif %} {% if ue.type != codes_cursus.UE_SPORT %} @@ -98,7 +101,7 @@ pas de niveau de compétence associé ! {% endif %} - {% if editable and not ue.is_locked() %} + {% if editable and not ue_is_locked[0] %} non modifiable : {{ue.is_locked()[1]}} {% else %}
  • modifier cette UE