Backend 'FormSemestre': début de modernisation + fix regressions

This commit is contained in:
Emmanuel Viennet 2024-10-19 23:52:35 +02:00
parent 7473334387
commit 24e144f372
10 changed files with 253 additions and 285 deletions

View File

@ -157,6 +157,24 @@ class User(UserMixin, ScoDocModel):
def __str__(self): def __str__(self):
return self.user_name 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): def set_password(self, password):
"Set password" "Set password"
current_app.logger.info(f"set_password({self})") current_app.logger.info(f"set_password({self})")

View File

@ -126,7 +126,7 @@ class ScoDocModel(db.Model):
@classmethod @classmethod
def get_instance(cls, oid: int, accept_none=False): 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. cherche uniquement dans le département courant.
Ne fonctionne que si le modèle a un attribut dept_id Ne fonctionne que si le modèle a un attribut dept_id

View File

@ -139,7 +139,7 @@ class FormSemestre(models.ScoDocModel):
# Relations: # Relations:
etapes = db.relationship( etapes = db.relationship(
"FormSemestreEtape", cascade="all,delete", backref="formsemestre" "FormSemestreEtape", cascade="all,delete-orphan", backref="formsemestre"
) )
modimpls = db.relationship( modimpls = db.relationship(
"ModuleImpl", "ModuleImpl",
@ -242,11 +242,9 @@ class FormSemestre(models.ScoDocModel):
args["dept_id"] = g.scodoc_dept_id args["dept_id"] = g.scodoc_dept_id
formsemestre: "FormSemestre" = cls.create_from_dict(args) formsemestre: "FormSemestre" = cls.create_from_dict(args)
db.session.flush() db.session.flush()
for etape in args["etapes"]: for etape in args.get("etapes") or []:
formsemestre.add_etape(etape) formsemestre.add_etape(etape)
db.session.commit() db.session.commit()
for u in args["responsables"]:
formsemestre.responsables.append(u)
# create default partition # create default partition
partition = Partition( partition = Partition(
formsemestre=formsemestre, partition_name=None, numero=1000000 formsemestre=formsemestre, partition_name=None, numero=1000000
@ -281,11 +279,26 @@ class FormSemestre(models.ScoDocModel):
if "date_fin" in args: if "date_fin" in args:
args["date_fin"] = scu.convert_fr_date(args["date_fin"]) args["date_fin"] = scu.convert_fr_date(args["date_fin"])
if "etat" in args: 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: if "bul_bgcolor" in args:
args["bul_bgcolor"] = args.get("bul_bgcolor") or "white" args["bul_bgcolor"] = args.get("bul_bgcolor") or "white"
if "titre" in args: if "titre" in args:
args["titre"] = args.get("titre") or "sans titre" 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 return args
@classmethod @classmethod
@ -1346,6 +1359,7 @@ class FormSemestre(models.ScoDocModel):
}, },
) )
db.session.commit() db.session.commit()
sco_cache.invalidate_formsemestre(formsemestre_id=self.id)
def etud_validations_description_html(self, etudid: int) -> str: def etud_validations_description_html(self, etudid: int) -> str:
"""Description textuelle des validations de jury de cet étudiant dans ce semestre""" """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 aurait du etre not null, mais oublié
etape_apo = db.Column(db.String(APO_CODE_STR_LEN), index=True) 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): def __bool__(self):
"Etape False if code empty" "Etape False if code empty"
return self.etape_apo is not None and (len(self.etape_apo) > 0) return self.etape_apo is not None and (len(self.etape_apo) > 0)

View File

@ -39,7 +39,7 @@ import app.scodoc.sco_utils as scu
from app import log from app import log
from app.models import Departement from app.models import Departement
from app.models import Formation, FormSemestre 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.gen_tables import GenTable
from app.scodoc.codes_cursus import NO_SEMESTRE_ID from app.scodoc.codes_cursus import NO_SEMESTRE_ID
from app.scodoc.sco_exceptions import ScoInvalidIdType, ScoValueError 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]) return ", ".join([str(x) for x in etapes])
def do_formsemestre_create( # DEPRECATED, use FormSemestre.create_formsemestre() def read_formsemestre_responsables(formsemestre_id: int) -> list[int]: # OBSOLETE
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
"""recupere liste des responsables de ce semestre """recupere liste des responsables de ce semestre
:returns: liste d'id :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] 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 # ---------------------- Coefs des UE
_formsemestre_uecoef_editor = ndb.EditableTable( _formsemestre_uecoef_editor = ndb.EditableTable(

View File

@ -50,7 +50,7 @@ from app.models import (
UniteEns, UniteEns,
) )
from app.models.formations import Formation 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 from app.models.but_refcomp import ApcParcours
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -143,7 +143,7 @@ def can_edit_sem(formsemestre_id: int = None, sem=None):
return sem return sem
resp_fields = [ RESP_FIELDS = [
"responsable_id", "responsable_id",
"responsable_id2", "responsable_id2",
"responsable_id3", "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} !", f"inconnu numéro {modimpl.responsable_id} resp. de {modimpl.id} !",
) )
for index, resp in enumerate(formsemestre.responsables): 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() group_tous = formsemestre.get_default_group()
if group_tous: if group_tous:
initvalues["edt_promo_id"] = group_tous.edt_id or "" 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", "titre",
@ -755,7 +755,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"input_type": "text_suggest", "input_type": "text_suggest",
"size": 50, "size": 50,
"withcheckbox": True, "withcheckbox": True,
"title": "%s %s" % (mod.code or "", mod.titre or ""), "title": f"""{mod.code or ""} {mod.titre or ""}""",
"allowed_values": allowed_user_names, "allowed_values": allowed_user_names,
"template": itemtemplate, "template": itemtemplate,
"text_suggest_options": { "text_suggest_options": {
@ -875,157 +875,106 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
formsemestre_id=formsemestre.id, formsemestre_id=formsemestre.id,
) )
) )
else: return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept))
return redirect(url_for("notes.index_html", scodoc_dept=g.scodoc_dept)) # Edition ou modification du semestre
else: tf[2]["gestion_compensation"] = bool(tf[2]["gestion_compensation_lst"])
if tf[2]["gestion_compensation_lst"]: tf[2]["gestion_semestrielle"] = bool(tf[2]["gestion_semestrielle_lst"])
tf[2]["gestion_compensation"] = True tf[2]["bul_hide_xml"] = not bool(tf[2]["bul_publish_xml_lst"])
else: _remap_resp_modimpls(tf[2])
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)
if "parcours" in tf[2]: if "parcours" in tf[2]:
formsemestre.parcours = [ tf[2]["parcours"] = [
db.session.get(ApcParcours, int(parcour_id_str)) db.session.get(ApcParcours, int(parcour_id_str))
for parcour_id_str in tf[2]["parcours"] 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]: if "edt_promo_id" in tf[2]:
group_tous = formsemestre.get_default_group() group_tous = formsemestre.get_default_group()
if group_tous: if group_tous:
@ -1041,6 +990,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
formsemestre.update_inscriptions_parcours_from_groups() formsemestre.update_inscriptions_parcours_from_groups()
# --- Fin # --- Fin
if edit: if edit:
log(f"""formsemestre_edit: {formsemestre}""")
if msg: if msg:
return f""" return f"""
<div class="ue_warning"><span>Attention !<ul> <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) scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id)
}">retour au tableau de bord</a> }">retour au tableau de bord</a>
""" """
else: flash("Semestre modifié")
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éé")
return flask.redirect( return flask.redirect(
url_for( url_for(
"notes.formsemestre_status", "notes.formsemestre_status",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id, 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): def _formsemestre_check_module_list(module_ids, semestre_idx):
@ -1711,7 +1704,8 @@ def formsemestre_edit_options(formsemestre_id):
"""dialog to change formsemestre options """dialog to change formsemestre options
(accessible par EditFormSemestre ou dir. etudes) (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: if not ok:
return err return err
return sco_preferences.SemPreferences(formsemestre_id).edit( return sco_preferences.SemPreferences(formsemestre_id).edit(
@ -1719,46 +1713,46 @@ def formsemestre_edit_options(formsemestre_id):
) )
def formsemestre_change_publication_bul( def formsemestre_change_publication_bul(formsemestre_id, dialog_confirmed=False):
formsemestre_id, dialog_confirmed=False, redirect=True """Change état publication bulletins sur portail"""
): formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
"""Change etat publication bulletins sur portail""" ok, err = sco_permissions_check.check_access_diretud(formsemestre)
ok, err = sco_permissions_check.check_access_diretud(formsemestre_id)
if not ok: if not ok:
return err return err
sem = sco_formsemestre.get_formsemestre(formsemestre_id) etat = not formsemestre.bul_hide_xml
etat = not sem["bul_hide_xml"]
status_url = url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre.id,
)
if not dialog_confirmed: if not dialog_confirmed:
if etat: msg = "non" if etat else ""
msg = "non"
else:
msg = ""
return scu.confirm_dialog( 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, 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. par exemple pendant la tenue d'un jury ou avant harmonisation des notes.
<br> <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="", dest_url="",
cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, cancel_url=status_url,
parameters={"bul_hide_xml": etat, "formsemestre_id": formsemestre_id}, parameters={"bul_hide_xml": etat, "formsemestre_id": formsemestre_id},
) )
args = {"formsemestre_id": formsemestre_id, "bul_hide_xml": etat} formsemestre.bul_hide_xml = etat
sco_formsemestre.do_formsemestre_edit(args) db.session.add(formsemestre)
if redirect: db.session.commit()
return flask.redirect( log(f"formsemestre_change_publication_bul: {formsemestre} -> {etat}")
"formsemestre_status?formsemestre_id=%s" % formsemestre_id
) return flask.redirect(status_url)
return None
def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None): def formsemestre_edit_uecoefs(formsemestre_id, err_ue_id=None):
"""Changement manuel des coefficients des UE capitalisées.""" """Changement manuel des coefficients des UE capitalisées."""
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) 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: if not ok:
return err return err

View File

@ -23,7 +23,11 @@ _SCO_PERMISSIONS = (
(1 << 9, "EditFormationTags", "Tagguer les formations"), (1 << 9, "EditFormationTags", "Tagguer les formations"),
(1 << 10, "EditAllNotes", "Modifier toutes les notes"), (1 << 10, "EditAllNotes", "Modifier toutes les notes"),
(1 << 11, "EditAllEvals", "Modifier toutes les évaluations"), (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 << 13, "AbsChange", "Saisir des absences ou justificatifs"),
(1 << 14, "AbsAddBillet", "Saisir des billets d'absences"), (1 << 14, "AbsAddBillet", "Saisir des billets d'absences"),
# changer adresse/photo ou pour envoyer bulletins par mail ou pour debouche # changer adresse/photo ou pour envoyer bulletins par mail ou pour debouche

View File

@ -35,16 +35,11 @@ def can_edit_suivi():
return current_user.has_permission(Permission.EtudChangeAdr) return current_user.has_permission(Permission.EtudChangeAdr)
def check_access_diretud( def check_access_diretud(formsemestre: FormSemestre):
formsemestre_id, required_permission=Permission.EditFormSemestre
):
"""Check if access granted: responsable or EditFormSemestre """Check if access granted: responsable or EditFormSemestre
Return True|False, HTML_error_page Return True|False, HTML_error_page
""" """
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) if not formsemestre.can_be_edited_by(current_user):
if (not current_user.has_permission(required_permission)) and (
current_user.id not in (u.id for u in formsemestre.responsables)
):
return ( return (
False, False,
render_template( render_template(

View File

@ -2568,8 +2568,7 @@ def check_sem_integrity(formsemestre_id, fix=False):
"""Debug. """Debug.
Check that ue and module formations are consistents 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) modimpls = sco_moduleimpl.moduleimpl_list(formsemestre_id=formsemestre_id)
bad_ue = [] bad_ue = []
bad_sem = [] bad_sem = []
@ -2584,12 +2583,12 @@ def check_sem_integrity(formsemestre_id, fix=False):
modimpl["mod"] = mod.to_dict() modimpl["mod"] = mod.to_dict()
modimpl["ue"] = ue_dict modimpl["ue"] = ue_dict
bad_ue.append(modimpl) bad_ue.append(modimpl)
if sem["formation_id"] != mod.formation_id: if formsemestre.formation_id != mod.formation_id:
bad_sem.append(modimpl) bad_sem.append(modimpl)
modimpl["mod"] = mod.to_dict() modimpl["mod"] = mod.to_dict()
H = [ H = [
f"""<p>formation_id={sem["formation_id"]}""", f"""<p>formation_id={formsemestre.formation_id}""",
] ]
if bad_ue: if bad_ue:
H += [ H += [
@ -2605,22 +2604,24 @@ def check_sem_integrity(formsemestre_id, fix=False):
H.append("<p>Aucun problème à signaler !</p>") H.append("<p>Aucun problème à signaler !</p>")
else: else:
log(f"check_sem_integrity: problem detected: formations_set={formations_set}") log(f"check_sem_integrity: problem detected: formations_set={formations_set}")
if sem["formation_id"] in formations_set: if formsemestre.formation_id in formations_set:
formations_set.remove(sem["formation_id"]) formations_set.remove(formsemestre.formation_id)
if len(formations_set) == 1: if len(formations_set) == 1:
if fix: if fix:
log(f"check_sem_integrity: trying to fix {formsemestre_id}") log(f"check_sem_integrity: trying to fix {formsemestre_id}")
formation_id = formations_set.pop() formation_id = formations_set.pop()
if sem["formation_id"] != formation_id: if formsemestre.formation_id != formation_id:
sem["formation_id"] = formation_id formsemestre.formation_id = formation_id
sco_formsemestre.do_formsemestre_edit(sem) 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>""") H.append("""<p class="alert">Problème réparé: vérifiez</p>""")
else: else:
H.append( H.append(
f""" f"""
<p class="alert">Problème détecté réparable: <p class="alert">Problème détecté réparable:
<a href="check_sem_integrity?formsemestre_id={ <a href="{url_for( "notes.check_sem_integrity", scodoc_dept=g.scodoc_dept,
formsemestre_id}&fix=1">réparer maintenant</a></p> formsemestre_id=formsemestre_id, fix=1)}">réparer maintenant</a></p>
""" """
) )
else: else:

View File

@ -3,7 +3,7 @@
"Infos sur version ScoDoc" "Infos sur version ScoDoc"
SCOVERSION = "9.7.29" SCOVERSION = "9.7.30"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -22,13 +22,13 @@ from app.models import (
Evaluation, Evaluation,
Formation, Formation,
FormationModalite, FormationModalite,
FormSemestre,
Identite, Identite,
Matiere, Matiere,
Module, Module,
ModuleImpl, ModuleImpl,
) )
from app.scodoc import codes_cursus 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_inscriptions
from app.scodoc import sco_formsemestre_validation from app.scodoc import sco_formsemestre_validation
from app.scodoc import sco_saisie_notes from app.scodoc import sco_saisie_notes
@ -255,11 +255,8 @@ class ScoFake(object):
if responsables is None: if responsables is None:
responsables = (self.default_user.id,) responsables = (self.default_user.id,)
titre = titre or "sans titre" titre = titre or "sans titre"
oid = sco_formsemestre.do_formsemestre_create(locals()) formsemestre = FormSemestre.create_formsemestre(locals())
oids = sco_formsemestre.do_formsemestre_list(args={"formsemestre_id": oid}) return formsemestre.id
if not oids:
raise ScoValueError("formsemestre not created !")
return oid
@logging_meth @logging_meth
def create_moduleimpl( def create_moduleimpl(