diff --git a/app/auth/models.py b/app/auth/models.py
index 440117ed9461db246a7177f2b1f52d8a9f491972..465169c578b42ab983245a130fdab7ab685ab9e3 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -157,6 +157,24 @@ class User(UserMixin, ScoDocModel):
def __str__(self):
return self.user_name
+ @classmethod
+ def get_user(cls, user_id: int | str, accept_none=False):
+ """Get user by id, user_name or User instance, ou 404 (ou None si accept_none)
+ If user_id == -1, returns None (without exception)
+ """
+ query = None
+ if isinstance(user_id, str):
+ query = db.session.query(cls).filter_by(user_name=user_id)
+ elif isinstance(user_id, int):
+ if user_id == -1:
+ return None
+ query = db.session.query(cls).filter_by(id=user_id)
+ elif isinstance(user_id, User):
+ return user_id
+ else:
+ raise ValueError("invalid user_id")
+ return query.first_or_404() if not accept_none else query.first()
+
def set_password(self, password):
"Set password"
current_app.logger.info(f"set_password({self})")
diff --git a/app/models/__init__.py b/app/models/__init__.py
index 26ed712f02708e292b2b98dbe2e82f70fb092e62..c70d2fbdf67b7138d2f95b7890553ad6859a222b 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -126,7 +126,7 @@ class ScoDocModel(db.Model):
@classmethod
def get_instance(cls, oid: int, accept_none=False):
- """Instance du modèle ou ou 404 (ou None si accept_none),
+ """Instance du modèle ou 404 (ou None si accept_none),
cherche uniquement dans le département courant.
Ne fonctionne que si le modèle a un attribut dept_id
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index cd2f0e7d25281aef58595706852a7b4af88c663b..0de2eb85459d8723cdd7cccabd6d4954948a2940 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -139,7 +139,7 @@ class FormSemestre(models.ScoDocModel):
# Relations:
etapes = db.relationship(
- "FormSemestreEtape", cascade="all,delete", backref="formsemestre"
+ "FormSemestreEtape", cascade="all,delete-orphan", backref="formsemestre"
)
modimpls = db.relationship(
"ModuleImpl",
@@ -242,11 +242,9 @@ class FormSemestre(models.ScoDocModel):
args["dept_id"] = g.scodoc_dept_id
formsemestre: "FormSemestre" = cls.create_from_dict(args)
db.session.flush()
- for etape in args["etapes"]:
+ for etape in args.get("etapes") or []:
formsemestre.add_etape(etape)
db.session.commit()
- for u in args["responsables"]:
- formsemestre.responsables.append(u)
# create default partition
partition = Partition(
formsemestre=formsemestre, partition_name=None, numero=1000000
@@ -281,11 +279,26 @@ class FormSemestre(models.ScoDocModel):
if "date_fin" in args:
args["date_fin"] = scu.convert_fr_date(args["date_fin"])
if "etat" in args:
- args["etat"] = bool(args["etat"])
+ if args["etat"] is None:
+ del args["etat"]
+ else:
+ args["etat"] = bool(args["etat"])
if "bul_bgcolor" in args:
args["bul_bgcolor"] = args.get("bul_bgcolor") or "white"
if "titre" in args:
args["titre"] = args.get("titre") or "sans titre"
+ if "capacite_accueil" in args: # peut être un nombre, "" ou None
+ try:
+ args["capacite_accueil"] = (
+ int(args["capacite_accueil"])
+ if args["capacite_accueil"] not in ("", None)
+ else None
+ )
+ except ValueError as exc:
+ raise ScoValueError("capacite_accueil invalide") from exc
+ if "responsables" in args: # peut être liste d'uid ou de user_name ou de User
+ resp_users = [User.get_user(u) for u in args["responsables"]]
+ args["responsables"] = [u for u in resp_users if u is not None]
return args
@classmethod
@@ -1346,6 +1359,7 @@ class FormSemestre(models.ScoDocModel):
},
)
db.session.commit()
+ sco_cache.invalidate_formsemestre(formsemestre_id=self.id)
def etud_validations_description_html(self, etudid: int) -> str:
"""Description textuelle des validations de jury de cet étudiant dans ce semestre"""
@@ -1461,6 +1475,15 @@ class FormSemestreEtape(models.ScoDocModel):
# etape_apo aurait du etre not null, mais oublié
etape_apo = db.Column(db.String(APO_CODE_STR_LEN), index=True)
+ @classmethod
+ def create_from_apovdi(
+ cls, formsemestre_id: int, apovdi: ApoEtapeVDI
+ ) -> "FormSemestreEtape":
+ "Crée une instance à partir d'un objet ApoEtapeVDI. Ajoute à la session."
+ etape = cls(formsemestre_id=formsemestre_id, etape_apo=str(apovdi))
+ db.session.add(etape)
+ return etape
+
def __bool__(self):
"Etape False if code empty"
return self.etape_apo is not None and (len(self.etape_apo) > 0)
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index 5131947ff46d6b4ee51cbe32be668fffe30a52a6..31dffb7365c635d6e2d02131b5039a9351ba17f2 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -39,7 +39,7 @@ import app.scodoc.sco_utils as scu
from app import log
from app.models import Departement
from app.models import Formation, FormSemestre
-from app.scodoc import sco_cache, codes_cursus, sco_preferences
+from app.scodoc import codes_cursus, sco_preferences
from app.scodoc.gen_tables import GenTable
from app.scodoc.codes_cursus import NO_SEMESTRE_ID
from app.scodoc.sco_exceptions import ScoInvalidIdType, ScoValueError
@@ -231,63 +231,7 @@ def etapes_apo_str(etapes):
return ", ".join([str(x) for x in etapes])
-def do_formsemestre_create( # DEPRECATED, use FormSemestre.create_formsemestre()
- args, silent=False
-):
- "create a formsemestre"
- from app.models import ScolarNews
- from app.scodoc import sco_groups
-
- log("Warning: do_formsemestre_create is deprecated")
- cnx = ndb.GetDBConnexion()
- formsemestre_id = _formsemestreEditor.create(cnx, args)
- if args["etapes"]:
- args["formsemestre_id"] = formsemestre_id
- write_formsemestre_etapes(args)
- if args["responsables"]:
- args["formsemestre_id"] = formsemestre_id
- _write_formsemestre_responsables(args)
-
- # create default partition
- partition_id = sco_groups.partition_create(
- formsemestre_id,
- default=True,
- redirect=0,
- numero=1000000, # à la fin
- )
- _ = sco_groups.create_group(partition_id, default=True)
-
- # news
- if "titre" not in args:
- args["titre"] = "sans titre"
- args["formsemestre_id"] = formsemestre_id
- args["url"] = "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % args
- if not silent:
- ScolarNews.add(
- typ=ScolarNews.NEWS_SEM,
- text='Création du semestre <a href="%(url)s">%(titre)s</a>' % args,
- url=args["url"],
- max_frequency=0,
- )
- return formsemestre_id
-
-
-def do_formsemestre_edit(sem, cnx=None, **kw):
- """Apply modifications to formsemestre.
- Update etapes and resps. Invalidate cache."""
- if not cnx:
- cnx = ndb.GetDBConnexion()
-
- _formsemestreEditor.edit(cnx, sem, **kw)
- write_formsemestre_etapes(sem)
- _write_formsemestre_responsables(sem)
-
- sco_cache.invalidate_formsemestre(
- formsemestre_id=sem["formsemestre_id"]
- ) # > modif formsemestre
-
-
-def read_formsemestre_responsables(formsemestre_id: int) -> list[int]: # py3.9+ syntax
+def read_formsemestre_responsables(formsemestre_id: int) -> list[int]: # OBSOLETE
"""recupere liste des responsables de ce semestre
:returns: liste d'id
"""
@@ -301,14 +245,6 @@ def read_formsemestre_responsables(formsemestre_id: int) -> list[int]: # py3.9+
return [x["responsable_id"] for x in r]
-def _write_formsemestre_responsables(sem): # TODO old, à ré-écrire avec models
- if sem and "responsables" in sem:
- sem["responsables"] = [
- uid for uid in sem["responsables"] if (uid is not None) and (uid != -1)
- ]
- _write_formsemestre_aux(sem, "responsables", "responsable_id")
-
-
# ---------------------- Coefs des UE
_formsemestre_uecoef_editor = ndb.EditableTable(
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 17f013ef97eedd9d426e861577a0b707d26f2736..0060b9b037020e514d8af3b8498d70794315c1fc 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -50,7 +50,7 @@ from app.models import (
UniteEns,
)
from app.models.formations import Formation
-from app.models.formsemestre import FormSemestre
+from app.models.formsemestre import FormSemestre, FormSemestreEtape
from app.models.but_refcomp import ApcParcours
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@@ -143,7 +143,7 @@ def can_edit_sem(formsemestre_id: int = None, sem=None):
return sem
-resp_fields = [
+RESP_FIELDS = [
"responsable_id",
"responsable_id2",
"responsable_id3",
@@ -205,7 +205,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !",
)
for index, resp in enumerate(formsemestre.responsables):
- initvalues[resp_fields[index]] = uid2display.get(resp.id)
+ initvalues[RESP_FIELDS[index]] = uid2display.get(resp.id)
group_tous = formsemestre.get_default_group()
if group_tous:
initvalues["edt_promo_id"] = group_tous.edt_id or ""
@@ -317,7 +317,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
},
},
)
- for index, field in enumerate(resp_fields)
+ for index, field in enumerate(RESP_FIELDS)
],
(
"titre",
@@ -755,7 +755,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"input_type": "text_suggest",
"size": 50,
"withcheckbox": True,
- "title": "%s %s" % (mod.code or "", mod.titre or ""),
+ "title": f"""{mod.code or ""} {mod.titre or ""}""",
"allowed_values": allowed_user_names,
"template": itemtemplate,
"text_suggest_options": {
@@ -875,157 +875,106 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
formsemestre_id=formsemestre.id,
)
)
- else:
- return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
- else:
- if tf[2]["gestion_compensation_lst"]:
- tf[2]["gestion_compensation"] = True
- else:
- tf[2]["gestion_compensation"] = False
- if tf[2]["gestion_semestrielle_lst"]:
- tf[2]["gestion_semestrielle"] = True
- else:
- tf[2]["gestion_semestrielle"] = False
- if tf[2]["bul_publish_xml_lst"]:
- tf[2]["bul_hide_xml"] = False
- else:
- tf[2]["bul_hide_xml"] = True
- # remap les identifiants de responsables:
- for field in resp_fields:
- resp = User.get_user_from_nomplogin(tf[2][field])
- tf[2][field] = resp.id if resp else -1
- tf[2]["responsables"] = []
- for field in resp_fields:
- if tf[2][field]:
- tf[2]["responsables"].append(tf[2][field])
- for module_id in tf[2]["tf-checked"]:
- mod_resp = User.get_user_from_nomplogin(tf[2][module_id])
- if mod_resp is None:
- # Si un module n'a pas de responsable (ou inconnu),
- # l'affecte au 1er directeur des etudes:
- mod_resp_id = tf[2]["responsable_id"]
- else:
- mod_resp_id = mod_resp.id
- tf[2][module_id] = mod_resp_id
-
- # etapes:
- tf[2]["etapes"] = []
- if etapes: # menus => case supplementaire pour saisie manuelle, indicée 0
- start_i = 0
- else:
- start_i = 1
- for n in range(start_i, scu.EDIT_NB_ETAPES + 1):
- tf[2]["etapes"].append(
- ApoEtapeVDI(
- etape=tf[2]["etape_apo" + str(n)], vdi=tf[2]["vdi_apo" + str(n)]
- )
- )
- # Modules sélectionnés:
- # (retire le "MI" du début du nom de champs)
- module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]]
- _formsemestre_check_ue_bonus_unicity(module_ids_checked)
- if not edit:
- if is_apc:
- _formsemestre_check_module_list(
- module_ids_checked, tf[2]["semestre_id"]
- )
- # création du semestre
- formsemestre_id = sco_formsemestre.do_formsemestre_create(tf[2])
- # création des modules
- for module_id in module_ids_checked:
- modargs = {
- "module_id": module_id,
- "formsemestre_id": formsemestre_id,
- "responsable_id": tf[2][f"MI{module_id}"],
- }
- _ = ModuleImpl.create_from_dict(modargs)
- else:
- # Modification du semestre:
- # on doit creer les modules nouvellement selectionnés
- # modifier ceux à modifier, et DETRUIRE ceux qui ne sont plus selectionnés.
- # Note: la destruction échouera s'il y a des objets dépendants
- # (eg des évaluations définies)
- module_ids_tocreate = [
- x for x in module_ids_checked if not x in module_ids_existing
- ]
- if is_apc:
- _formsemestre_check_module_list(
- module_ids_tocreate, tf[2]["semestre_id"]
- )
- # modules existants à modifier
- module_ids_toedit = [
- x for x in module_ids_checked if x in module_ids_existing
- ]
- # modules à détruire
- module_ids_todelete = [
- x for x in module_ids_existing if not x in module_ids_checked
- ]
- #
- sco_formsemestre.do_formsemestre_edit(tf[2])
- #
- msg = []
- for module_id in module_ids_tocreate:
- modargs = {
- "module_id": module_id,
- "formsemestre_id": formsemestre.id,
- "responsable_id": tf[2]["MI" + str(module_id)],
- }
- modimpl = ModuleImpl.create_from_dict(modargs)
- assert modimpl.module_id == module_id
- mod = modimpl.module
- msg += [f"""création de {mod.code or "?"} ({mod.titre or "?"})"""]
- # INSCRIPTIONS DES ETUDIANTS
- group_id = tf[2][f"{module_id}!group_id"]
- log(f"""inscription module: {module_id}!group_id = '{group_id}'""")
- if group_id:
- etudids = [
- x["etudid"] for x in sco_groups.get_group_members(group_id)
- ]
- log(
- "inscription module:module_id=%s,moduleimpl_id=%s: %s"
- % (module_id, modimpl.id, etudids)
- )
- sco_moduleimpl.do_moduleimpl_inscrit_etuds(
- modimpl.id,
- formsemestre.id,
- etudids,
- )
- msg += [
- "inscription de %d étudiants au module %s"
- % (len(etudids), mod["code"] or "(module sans code)")
- ]
- else:
- log(
- "inscription module:module_id=%s,moduleimpl_id=%s: aucun etudiant inscrit"
- % (module_id, modimpl.id)
- )
- #
- ok, diag = formsemestre_delete_moduleimpls(
- formsemestre.id, module_ids_todelete
- )
- msg += diag
- for module_id in module_ids_toedit:
- moduleimpl_id = sco_moduleimpl.moduleimpl_list(
- formsemestre_id=formsemestre.id, module_id=module_id
- )[0]["moduleimpl_id"]
- modargs = {
- "moduleimpl_id": moduleimpl_id,
- "module_id": module_id,
- "formsemestre_id": formsemestre.id,
- "responsable_id": tf[2]["MI" + str(module_id)],
- }
- sco_moduleimpl.do_moduleimpl_edit(
- modargs, formsemestre_id=formsemestre.id
- )
- # --- Association des parcours
- if formsemestre is None:
- formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+ return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
+ # Edition ou modification du semestre
+ tf[2]["gestion_compensation"] = bool(tf[2]["gestion_compensation_lst"])
+ tf[2]["gestion_semestrielle"] = bool(tf[2]["gestion_semestrielle_lst"])
+ tf[2]["bul_hide_xml"] = not bool(tf[2]["bul_publish_xml_lst"])
+ _remap_resp_modimpls(tf[2])
if "parcours" in tf[2]:
- formsemestre.parcours = [
+ tf[2]["parcours"] = [
db.session.get(ApcParcours, int(parcour_id_str))
for parcour_id_str in tf[2]["parcours"]
]
- # --- Id edt du groupe par défault
+ # Modules sélectionnés:
+ # (retire le "MI" du début du nom de champs)
+ module_ids_checked = [int(x[2:]) for x in tf[2]["tf-checked"]]
+ _formsemestre_check_ue_bonus_unicity(module_ids_checked)
+ if not edit:
+ if is_apc:
+ _formsemestre_check_module_list(module_ids_checked, tf[2]["semestre_id"])
+ # création du semestre
+ formsemestre = FormSemestre.create_formsemestre(tf[2])
+ # création des modules
+ for module_id in module_ids_checked:
+ modargs = {
+ "module_id": module_id,
+ "formsemestre_id": formsemestre.id,
+ "responsable_id": tf[2][f"MI{module_id}"],
+ }
+ _ = ModuleImpl.create_from_dict(modargs)
+ else:
+ # Modification du semestre:
+ # on doit creer les modules nouvellement selectionnés
+ # modifier ceux à modifier, et DETRUIRE ceux qui ne sont plus selectionnés.
+ # Note: la destruction échouera s'il y a des objets dépendants
+ # (eg des évaluations définies)
+ module_ids_tocreate = [
+ x for x in module_ids_checked if not x in module_ids_existing
+ ]
+ if is_apc:
+ _formsemestre_check_module_list(module_ids_tocreate, tf[2]["semestre_id"])
+ # modules existants à modifier
+ module_ids_toedit = [x for x in module_ids_checked if x in module_ids_existing]
+ # modules à détruire
+ module_ids_todelete = [
+ x for x in module_ids_existing if not x in module_ids_checked
+ ]
+ #
+ formsemestre.from_dict(tf[2])
+ sco_cache.invalidate_formsemestre(formsemestre.id)
+ #
+ msg = []
+ for module_id in module_ids_tocreate:
+ modargs = {
+ "module_id": module_id,
+ "formsemestre_id": formsemestre.id,
+ "responsable_id": tf[2]["MI" + str(module_id)],
+ }
+ modimpl = ModuleImpl.create_from_dict(modargs)
+ assert modimpl.module_id == module_id
+ mod = modimpl.module
+ msg += [f"""création de {mod.code or "?"} ({mod.titre or "?"})"""]
+ # INSCRIPTIONS DES ETUDIANTS
+ group_id = tf[2][f"{module_id}!group_id"]
+ log(f"""inscription module: {module_id}!group_id = '{group_id}'""")
+ if group_id:
+ etudids = [x["etudid"] for x in sco_groups.get_group_members(group_id)]
+ log(
+ f"""inscription module:module_id={module_id},moduleimpl_id={
+ modimpl.id}: {etudids}"""
+ )
+ sco_moduleimpl.do_moduleimpl_inscrit_etuds(
+ modimpl.id,
+ formsemestre.id,
+ etudids,
+ )
+ msg += [
+ f"""inscription de {len(etudids)} étudiants au module {
+ mod.code or "(module sans code)"}"""
+ ]
+ else:
+ log(
+ f"""inscription module:module_id={module_id},moduleimpl_id={
+ modimpl.id}: aucun etudiant inscrit"""
+ )
+ #
+ ok, diag = formsemestre_delete_moduleimpls(formsemestre.id, module_ids_todelete)
+ msg += diag
+ for module_id in module_ids_toedit:
+ moduleimpl_id = sco_moduleimpl.moduleimpl_list(
+ formsemestre_id=formsemestre.id, module_id=module_id
+ )[0]["moduleimpl_id"]
+ modargs = {
+ "moduleimpl_id": moduleimpl_id,
+ "module_id": module_id,
+ "formsemestre_id": formsemestre.id,
+ "responsable_id": tf[2]["MI" + str(module_id)],
+ }
+ sco_moduleimpl.do_moduleimpl_edit(modargs, formsemestre_id=formsemestre.id)
+ # --- Etapes
+ _set_apo_etapes(formsemestre, tf[2], etapes)
+ # --- id edt du groupe par défault
if "edt_promo_id" in tf[2]:
group_tous = formsemestre.get_default_group()
if group_tous:
@@ -1041,6 +990,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
formsemestre.update_inscriptions_parcours_from_groups()
# --- Fin
if edit:
+ log(f"""formsemestre_edit: {formsemestre}""")
if msg:
return f"""
<div class="ue_warning"><span>Attention !<ul>
@@ -1057,25 +1007,68 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
}">retour au tableau de bord</a>
"""
- else:
- flash("Semestre modifié")
- return flask.redirect(
- url_for(
- "notes.formsemestre_status",
- scodoc_dept=g.scodoc_dept,
- formsemestre_id=formsemestre.id,
- check_parcours=0,
- )
- )
- else:
- flash("Nouveau semestre créé")
+ flash("Semestre modifié")
return flask.redirect(
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
+ check_parcours=0,
)
)
+ flash("Nouveau semestre créé")
+ return flask.redirect(
+ url_for(
+ "notes.formsemestre_status",
+ scodoc_dept=g.scodoc_dept,
+ formsemestre_id=formsemestre.id,
+ )
+ )
+
+
+def _remap_resp_modimpls(args: dict):
+ "remap les identifiants de responsables de modules"
+ for field in RESP_FIELDS:
+ resp = User.get_user_from_nomplogin(args[field])
+ args[field] = resp.id if resp else -1
+ args["responsables"] = []
+ for field in RESP_FIELDS:
+ if args[field]:
+ args["responsables"].append(args[field])
+ for module_id in args["tf-checked"]:
+ mod_resp = User.get_user_from_nomplogin(args[module_id])
+ if mod_resp is None:
+ # Si un module n'a pas de responsable (ou inconnu),
+ # l'affecte au 1er directeur des etudes:
+ mod_resp_id = args["responsable_id"]
+ else:
+ mod_resp_id = mod_resp.id
+ args[module_id] = mod_resp_id
+
+
+def _set_apo_etapes(formsemestre: FormSemestre, args: dict, etapes: list[str]):
+ """Affecte les étapes Apo du semestre,
+ à partir de args["etape_apo<n>"] et args["vdi_apo<n>]
+ """
+ # menus => case supplementaire pour saisie manuelle, indicée 0
+ start_i = 0 if etapes else 1
+ apo_etapes_vdi = []
+ for n in range(start_i, scu.EDIT_NB_ETAPES + 1):
+ apo_etapes_vdi.append(
+ ApoEtapeVDI(etape=args["etape_apo" + str(n)], vdi=args["vdi_apo" + str(n)])
+ )
+ # uniques:
+ apo_etapes_vdi_uniq = {str(e): e for e in apo_etapes_vdi if str(e)}.values()
+ formsemestre.etapes = []
+ db.session.add(formsemestre)
+ db.session.flush()
+ formsemestre.etapes = [
+ FormSemestreEtape.create_from_apovdi(formsemestre.id, etape_vdi)
+ for etape_vdi in apo_etapes_vdi_uniq
+ ]
+ db.session.add(formsemestre)
+ db.session.flush()
+ log(f"setting etapes: {formsemestre}: {formsemestre.etapes}")
def _formsemestre_check_module_list(module_ids, semestre_idx):
@@ -1711,7 +1704,8 @@ def formsemestre_edit_options(formsemestre_id):
"""dialog to change formsemestre options
(accessible par EditFormSemestre ou dir. etudes)
"""
- ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
+ formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+ ok, err = sco_permissions_check.check_access_diretud(formsemestre)
if not ok:
return err
return sco_preferences.SemPreferences(formsemestre_id).edit(
@@ -1719,46 +1713,46 @@ def formsemestre_edit_options(formsemestre_id):
)
-def formsemestre_change_publication_bul(
- formsemestre_id, dialog_confirmed=False, redirect=True
-):
- """Change etat publication bulletins sur portail"""
- ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
+def formsemestre_change_publication_bul(formsemestre_id, dialog_confirmed=False):
+ """Change état publication bulletins sur portail"""
+ formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+ ok, err = sco_permissions_check.check_access_diretud(formsemestre)
if not ok:
return err
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
- etat = not sem["bul_hide_xml"]
+ etat = not formsemestre.bul_hide_xml
+ status_url = url_for(
+ "notes.formsemestre_status",
+ scodoc_dept=g.scodoc_dept,
+ formsemestre_id=formsemestre.id,
+ )
if not dialog_confirmed:
- if etat:
- msg = "non"
- else:
- msg = ""
+ msg = "non" if etat else ""
return scu.confirm_dialog(
- "<h2>Confirmer la %s publication des bulletins ?</h2>" % msg,
+ f"<h2>Confirmer la {msg} publication des bulletins ?</h2>",
help_msg="""Il est parfois utile de désactiver la diffusion des bulletins,
par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
<br>
- Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc et un portail étudiant.
+ Ce réglage n'a d'effet que si votre établissement a interfacé ScoDoc avec
+ une passerelle étudiant.
""",
dest_url="",
- cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id,
+ cancel_url=status_url,
parameters={"bul_hide_xml": etat, "formsemestre_id": formsemestre_id},
)
- args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat}
- sco_formsemestre.do_formsemestre_edit(args)
- if redirect:
- return flask.redirect(
- "formsemestre_status?formsemestre_id=%s" % formsemestre_id
- )
- return None
+ formsemestre.bul_hide_xml = etat
+ db.session.add(formsemestre)
+ db.session.commit()
+ log(f"formsemestre_change_publication_bul: {formsemestre} -> {etat}")
+
+ return flask.redirect(status_url)
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
"""Changement manuel des coefficients des UE capitalisées."""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
- ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
+ ok, err = sco_permissions_check.check_access_diretud(formsemestre)
if not ok:
return err
diff --git a/app/scodoc/sco_permissions.py b/app/scodoc/sco_permissions.py
index 29dbc0a0bfca9c3ee50130e59647370499fedbe9..dece55410f1077ed92e757c780a70a8665d83bf1 100644
--- a/app/scodoc/sco_permissions.py
+++ b/app/scodoc/sco_permissions.py
@@ -23,7 +23,11 @@ _SCO_PERMISSIONS = (
(1 << 9, "EditFormationTags", "Tagguer les formations"),
(1 << 10, "EditAllNotes", "Modifier toutes les notes"),
(1 << 11, "EditAllEvals", "Modifier toutes les évaluations"),
- (1 << 12, "EditFormSemestre", "Mettre en place une formation (créer un semestre)"),
+ (
+ 1 << 12,
+ "EditFormSemestre",
+ "Mettre en place ou modifier un semestre de formation",
+ ),
(1 << 13, "AbsChange", "Saisir des absences ou justificatifs"),
(1 << 14, "AbsAddBillet", "Saisir des billets d'absences"),
# changer adresse/photo ou pour envoyer bulletins par mail ou pour debouche
diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py
index eebaa16286a191f45ef2051d2627aaa785b5d0cd..6f98aafd22b30906d44d91e0ec92ff71ca0eab38 100644
--- a/app/scodoc/sco_permissions_check.py
+++ b/app/scodoc/sco_permissions_check.py
@@ -35,16 +35,11 @@ def can_edit_suivi():
return current_user.has_permission(Permission.EtudChangeAdr)
-def check_access_diretud(
- formsemestre_id, required_permission=Permission.EditFormSemestre
-):
+def check_access_diretud(formsemestre: FormSemestre):
"""Check if access granted: responsable or EditFormSemestre
Return True|False, HTML_error_page
"""
- formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
- if (not current_user.has_permission(required_permission)) and (
- current_user.id not in (u.id for u in formsemestre.responsables)
- ):
+ if not formsemestre.can_be_edited_by(current_user):
return (
False,
render_template(
diff --git a/app/views/notes.py b/app/views/notes.py
index 6100e45c84152f947221ad80c486041dadfd7d0a..22d1a16fb0a0797ceae6156a5459ff1c8c54fe70 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -2568,8 +2568,7 @@ def check_sem_integrity(formsemestre_id, fix=False):
"""Debug.
Check that ue and module formations are consistents
"""
- sem = sco_formsemestre.get_formsemestre(formsemestre_id)
-
+ formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
bad_ue = []
bad_sem = []
@@ -2584,12 +2583,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
modimpl["mod"] = mod.to_dict()
modimpl["ue"] = ue_dict
bad_ue.append(modimpl)
- if sem["formation_id"] != mod.formation_id:
+ if formsemestre.formation_id != mod.formation_id:
bad_sem.append(modimpl)
modimpl["mod"] = mod.to_dict()
H = [
- f"""<p>formation_id={sem["formation_id"]}""",
+ f"""<p>formation_id={formsemestre.formation_id}""",
]
if bad_ue:
H += [
@@ -2605,22 +2604,24 @@ def check_sem_integrity(formsemestre_id, fix=False):
H.append("<p>Aucun problème à signaler !</p>")
else:
log(f"check_sem_integrity: problem detected: formations_set={formations_set}")
- if sem["formation_id"] in formations_set:
- formations_set.remove(sem["formation_id"])
+ if formsemestre.formation_id in formations_set:
+ formations_set.remove(formsemestre.formation_id)
if len(formations_set) == 1:
if fix:
log(f"check_sem_integrity: trying to fix {formsemestre_id}")
formation_id = formations_set.pop()
- if sem["formation_id"] != formation_id:
- sem["formation_id"] = formation_id
- sco_formsemestre.do_formsemestre_edit(sem)
+ if formsemestre.formation_id != formation_id:
+ formsemestre.formation_id = formation_id
+ db.session.add(formsemestre)
+ db.session.commit()
+ sco_cache.invalidate_formsemestre(formsemestre.id)
H.append("""<p class="alert">Problème réparé: vérifiez</p>""")
else:
H.append(
f"""
<p class="alert">Problème détecté réparable:
- <a href="check_sem_integrity?formsemestre_id={
- formsemestre_id}&fix=1">réparer maintenant</a></p>
+ <a href="{url_for( "notes.check_sem_integrity", scodoc_dept=g.scodoc_dept,
+ formsemestre_id=formsemestre_id, fix=1)}">réparer maintenant</a></p>
"""
)
else:
diff --git a/sco_version.py b/sco_version.py
index 2ca6bc865ce9471883c5a9a6cd82b503accf233e..7fba3c3f0440c3a93aa3e746c6009c70eea51cbe 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -3,7 +3,7 @@
"Infos sur version ScoDoc"
-SCOVERSION = "9.7.29"
+SCOVERSION = "9.7.30"
SCONAME = "ScoDoc"
diff --git a/tests/unit/sco_fake_gen.py b/tests/unit/sco_fake_gen.py
index 06a10f3f6aaceb73f21323d0da221dd24d2a32e0..65b707f2a5a38acafded38c058a984244d1124c5 100644
--- a/tests/unit/sco_fake_gen.py
+++ b/tests/unit/sco_fake_gen.py
@@ -22,13 +22,13 @@ from app.models import (
Evaluation,
Formation,
FormationModalite,
+ FormSemestre,
Identite,
Matiere,
Module,
ModuleImpl,
)
from app.scodoc import codes_cursus
-from app.scodoc import sco_formsemestre
from app.scodoc import sco_formsemestre_inscriptions
from app.scodoc import sco_formsemestre_validation
from app.scodoc import sco_saisie_notes
@@ -255,11 +255,8 @@ class ScoFake(object):
if responsables is None:
responsables = (self.default_user.id,)
titre = titre or "sans titre"
- oid = sco_formsemestre.do_formsemestre_create(locals())
- oids = sco_formsemestre.do_formsemestre_list(args={"formsemestre_id": oid})
- if not oids:
- raise ScoValueError("formsemestre not created !")
- return oid
+ formsemestre = FormSemestre.create_formsemestre(locals())
+ return formsemestre.id
@logging_meth
def create_moduleimpl(