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