From 24e144f3723d0520ef0afc0e48c9428737b5f0a7 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 19 Oct 2024 23:52:35 +0200 Subject: [PATCH] =?UTF-8?q?Backend=20'FormSemestre':=20d=C3=A9but=20de=20m?= =?UTF-8?q?odernisation=20+=20fix=20regressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/models.py | 18 ++ app/models/__init__.py | 2 +- app/models/formsemestre.py | 33 ++- app/scodoc/sco_formsemestre.py | 68 +---- app/scodoc/sco_formsemestre_edit.py | 368 ++++++++++++++-------------- app/scodoc/sco_permissions.py | 6 +- app/scodoc/sco_permissions_check.py | 9 +- app/views/notes.py | 23 +- sco_version.py | 2 +- tests/unit/sco_fake_gen.py | 9 +- 10 files changed, 253 insertions(+), 285 deletions(-) diff --git a/app/auth/models.py b/app/auth/models.py index 440117ed9..465169c57 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 26ed712f0..c70d2fbdf 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 cd2f0e7d2..0de2eb854 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 5131947ff..31dffb736 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 %(titre)s' % 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 17f013ef9..0060b9b03 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"""
Attention !
    @@ -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 """ - 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"] et args["vdi_apo] + """ + # 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( - "

    Confirmer la %s publication des bulletins ?

    " % msg, + f"

    Confirmer la {msg} publication 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.
    - 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 29dbc0a0b..dece55410 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 eebaa1628..6f98aafd2 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 6100e45c8..22d1a16fb 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"""

    formation_id={sem["formation_id"]}""", + f"""

    formation_id={formsemestre.formation_id}""", ] if bad_ue: H += [ @@ -2605,22 +2604,24 @@ def check_sem_integrity(formsemestre_id, fix=False): H.append("

    Aucun problème à signaler !

    ") 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("""

    Problème réparé: vérifiez

    """) else: H.append( f"""

    Problème détecté réparable: - réparer maintenant

    + réparer maintenant

    """ ) else: diff --git a/sco_version.py b/sco_version.py index 2ca6bc865..7fba3c3f0 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 06a10f3f6..65b707f2a 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(