Backend 'Modules': utilise uniquement modèles. + code modernization.

This commit is contained in:
ilona 2024-10-14 16:40:05 +02:00
parent 1dfba157c2
commit 29defb6f00
22 changed files with 538 additions and 560 deletions

View File

@ -33,168 +33,28 @@ from flask import flash, url_for, render_template
from flask import g, request from flask import g, request
from flask_login import current_user from flask_login import current_user
from app import db, log from app import db
from app import models from app import models
from app.formations import edit_matiere
from app.models import APO_CODE_STR_LEN from app.models import APO_CODE_STR_LEN
from app.models import Formation, Matiere, Module, UniteEns from app.models import Formation, Matiere, Module, UniteEns
from app.models import FormSemestre, ModuleImpl from app.models import FormSemestre, ModuleImpl
from app.models import ScolarNews
from app.models.but_refcomp import ApcAppCritique, ApcParcours from app.models.but_refcomp import ApcAppCritique, ApcParcours
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import ( from app.scodoc.sco_exceptions import (
ScoValueError, ScoValueError,
ScoLockedFormError,
ScoGenError,
ScoNonEmptyFormationObject, ScoNonEmptyFormationObject,
) )
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_moduleimpl
_moduleEditor = ndb.EditableTable(
"notes_modules",
"module_id",
(
"module_id",
"titre",
"code",
"abbrev",
"heures_cours",
"heures_td",
"heures_tp",
"coefficient",
"ue_id",
"matiere_id",
"formation_id",
"semestre_id",
"numero",
"code_apogee",
"module_type",
"edt_id",
#'ects'
),
sortkey="numero, code, titre",
output_formators={
"heures_cours": ndb.float_null_is_zero,
"heures_td": ndb.float_null_is_zero,
"heures_tp": ndb.float_null_is_zero,
"numero": ndb.int_null_is_zero,
"coefficient": ndb.float_null_is_zero,
"module_type": ndb.int_null_is_zero,
#'ects' : ndb.float_null_is_null
},
)
def module_list(*args, **kw):
"list modules"
cnx = ndb.GetDBConnexion()
return _moduleEditor.list(cnx, *args, **kw)
def do_module_create(args) -> int:
"Create a module. Returns id of new object."
formation = db.session.get(Formation, args["formation_id"])
# refuse de créer un module APC avec semestres incohérents:
if formation.is_apc():
ue = db.session.get(UniteEns, args["ue_id"])
if int(args.get("semestre_id", 0)) != ue.semestre_idx:
raise ScoValueError("Formation incompatible: contacter le support ScoDoc")
# create
module = Module.create_from_dict(args)
db.session.commit()
log(f"do_module_create: created {module.id} with {args}")
# news
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
)
formation.invalidate_cached_sems()
return module.id
def module_create(
matiere_id=None, module_type=None, semestre_id=None, formation_id=None
):
"""Formulaire de création d'un module
Si matiere_id est spécifié, le module sera créé dans cette matière (cas normal).
Sinon, donne le choix de l'UE de rattachement et utilise la première
matière de cette UE (si elle n'existe pas, la crée).
"""
return module_edit(
create=True,
matiere_id=matiere_id,
module_type=module_type,
semestre_id=semestre_id,
formation_id=formation_id,
)
def can_delete_module(module):
"True si le module n'est pas utilisée dans des formsemestre"
return len(module.modimpls.all()) == 0
def do_module_delete(oid):
"delete module"
module = Module.query.get_or_404(oid)
mod = module_list({"module_id": oid})[0] # sco7
if module_is_locked(module.id):
raise ScoLockedFormError()
if not can_delete_module(module):
raise ScoNonEmptyFormationObject(
"Module",
msg=module.titre,
dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=module.formation_id,
semestre_idx=module.ue.semestre_idx,
),
)
# S'il y a des moduleimpls, on ne peut pas detruire le module !
mods = sco_moduleimpl.moduleimpl_list(module_id=oid)
if mods:
err_page = f"""
<h3>Destruction du module impossible car il est utilisé dans des
semestres existants !</h3>
<p class="help">Il faut d'abord supprimer le semestre (ou en retirer
ce module).
Mais il est peut être préférable de laisser ce programme intact et
d'en créer une nouvelle version pour la modifier sans affecter
les semestres déjà en place.
</p>
<a href="{url_for('notes.ue_table', scodoc_dept=g.scodoc_dept,
formation_id=mod["formation_id"])}">reprendre</a>
"""
raise ScoGenError(err_page)
# delete
cnx = ndb.GetDBConnexion()
_moduleEditor.delete(cnx, oid)
# news
formation = module.formation
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=mod["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
)
formation.invalidate_cached_sems()
def module_delete(module_id=None): def module_delete(module_id=None):
"""Delete a module""" """Formulaire suppression d'un module"""
module = Module.query.get_or_404(module_id) module = Module.query.get_or_404(module_id)
mod = module_list(args={"module_id": module_id})[0] # sco7
if not can_delete_module(module): if not module.can_be_deleted():
raise ScoNonEmptyFormationObject( raise ScoNonEmptyFormationObject(
"Module", "Module",
msg=module.titre, msg=module.titre,
@ -221,7 +81,7 @@ def module_delete(module_id=None):
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
(("module_id", {"input_type": "hidden"}),), (("module_id", {"input_type": "hidden"}),),
initvalues=mod, initvalues=module.to_dict(),
submitlabel="Confirmer la suppression", submitlabel="Confirmer la suppression",
cancelbutton="Annuler", cancelbutton="Annuler",
) )
@ -231,37 +91,38 @@ def module_delete(module_id=None):
title="Suppression d'un module", title="Suppression d'un module",
content="\n".join(H) + tf[1], content="\n".join(H) + tf[1],
) )
elif tf[0] == -1: if tf[0] == -1: # cancel
return flask.redirect(dest_url)
else:
do_module_delete(module_id)
return flask.redirect(dest_url) return flask.redirect(dest_url)
module.delete()
return flask.redirect(dest_url)
def do_module_edit(vals: dict) -> None: def do_module_edit(vals: dict) -> None:
"edit a module" "edit a module"
# check # check
mod = module_list({"module_id": vals["module_id"]})[0] module = Module.get_instance(vals["module_id"])
if module_is_locked(mod["module_id"]):
# formation verrouillée: empeche de modifier certains champs:
vals = vals.copy()
protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id")
for f in protected_fields:
if f in vals:
del vals[f]
# edit # edit
cnx = ndb.GetDBConnexion() modif = module.from_dict(vals)
_moduleEditor.edit(cnx, vals) if modif:
db.session.get(Formation, mod["formation_id"]).invalidate_cached_sems() module.formation.invalidate_cached_sems()
def check_module_code_unicity(code, field, formation_id, module_id=None): def module_create(
"true si code module unique dans la formation" matiere_id=None, module_type=None, semestre_id=None, formation_id=None
modules = module_list(args={"code": code, "formation_id": formation_id}) ):
if module_id: # edition: supprime le module en cours """Formulaire de création d'un module
modules = [m for m in modules if m["module_id"] != module_id] Si matiere_id est spécifié, le module sera créé dans cette matière (cas normal).
Sinon, donne le choix de l'UE de rattachement et utilise la première
return len(modules) == 0 matière de cette UE (si elle n'existe pas, la crée).
"""
return module_edit(
create=True,
matiere_id=matiere_id,
module_type=module_type,
semestre_id=semestre_id,
formation_id=formation_id,
)
def module_edit( def module_edit(
@ -278,14 +139,12 @@ def module_edit(
Sinon, donne le choix de l'UE de rattachement et utilise la première matière Sinon, donne le choix de l'UE de rattachement et utilise la première matière
de cette UE (si elle n'existe pas, la crée). de cette UE (si elle n'existe pas, la crée).
""" """
from app.scodoc import sco_tag_module
# --- Détermination de la formation # --- Détermination de la formation
orig_semestre_idx = semestre_id orig_semestre_idx = semestre_id
ue = None ue = None
if create: if create:
if matiere_id: if matiere_id:
matiere = Matiere.query.get_or_404(matiere_id) matiere = Matiere.get_instance(matiere_id)
ue = matiere.ue ue = matiere.ue
formation = ue.formation formation = ue.formation
orig_semestre_idx = ue.semestre_idx if semestre_id is None else semestre_id orig_semestre_idx = ue.semestre_idx if semestre_id is None else semestre_id
@ -300,7 +159,7 @@ def module_edit(
ue = module.ue ue = module.ue
module_dict = module.to_dict() module_dict = module.to_dict()
formation = module.formation formation = module.formation
unlocked = not module_is_locked(module_id) unlocked = not module.is_locked()
parcours = codes_cursus.get_cursus_from_code(formation.type_parcours) parcours = codes_cursus.get_cursus_from_code(formation.type_parcours)
is_apc = parcours.APC_SAE # BUT is_apc = parcours.APC_SAE # BUT
@ -326,17 +185,14 @@ def module_edit(
if (module and module.matiere and (module.matiere.id == mat.id)) if (module and module.matiere and (module.matiere.id == mat.id))
or (mat.id == mat.ue.matieres.first().id) or (mat.id == mat.ue.matieres.first().id)
] ]
mat_names = [ mat_names = [f"S{mat.ue.semestre_idx} / {mat.ue.acronyme}" for mat in matieres]
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
]
else: else:
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres] mat_names = ["{mat.ue.acronyme} / {mat.titre or ''}" for mat in matieres]
if module: # edition if module: # edition
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres] ue_mat_ids = [f"{mat.ue.id}!{mat.id}" for mat in matieres]
module_dict["ue_matiere_id"] = "%s!%s" % ( module_dict["ue_matiere_id"] = (
module_dict["ue_id"], f"{module_dict['ue_id']}!{module_dict['matiere_id']}"
module_dict["matiere_id"],
) )
semestres_indices = list(range(1, parcours.NB_SEM + 1)) semestres_indices = list(range(1, parcours.NB_SEM + 1))
@ -433,8 +289,8 @@ def module_edit(
"explanation": """code du module (issu du programme, exemple M1203, "explanation": """code du module (issu du programme, exemple M1203,
R2.01, ou SAÉ 3.4. Doit être unique dans la formation)""", R2.01, ou SAÉ 3.4. Doit être unique dans la formation)""",
"allow_null": False, "allow_null": False,
"validator": lambda val, field, formation_id=formation.id: check_module_code_unicity( "validator": lambda val, _, formation_id=formation.id: Module.check_module_code_unicity(
val, field, formation_id, module_id=module.id if module else None val, formation_id, module_id=module.id if module else None
), ),
}, },
), ),
@ -602,7 +458,8 @@ def module_edit(
"title": "UE de rattachement", "title": "UE de rattachement",
"explanation": "utilisée notamment pour les malus", "explanation": "utilisée notamment pour les malus",
"labels": [ "labels": [
f"S{u.semestre_idx if u.semestre_idx is not None else '.'} / {u.acronyme} {u.titre}" f"""S{u.semestre_idx if u.semestre_idx is not None else '.'
} / {u.acronyme} {u.titre}"""
for u in ues for u in ues
], ],
"allowed_values": [u.id for u in ues], "allowed_values": [u.id for u in ues],
@ -631,7 +488,8 @@ def module_edit(
"input_type": "menu", "input_type": "menu",
"type": "int", "type": "int",
"title": parcours.SESSION_NAME.capitalize(), "title": parcours.SESSION_NAME.capitalize(),
"explanation": f"{parcours.SESSION_NAME} de début du module dans la formation standard", "explanation": f"""{parcours.SESSION_NAME
} de début du module dans la formation standard""",
"labels": [str(x) for x in semestres_indices], "labels": [str(x) for x in semestres_indices],
"allowed_values": semestres_indices, "allowed_values": semestres_indices,
"enabled": unlocked, "enabled": unlocked,
@ -846,8 +704,7 @@ def module_edit(
tf[2]["matiere_id"] = matiere.id tf[2]["matiere_id"] = matiere.id
tf[2]["semestre_id"] = ue.semestre_idx tf[2]["semestre_id"] = ue.semestre_idx
module_id = do_module_create(tf[2]) module = Module.create_from_dict(tf[2], news=True)
module = db.session.get(Module, module_id)
else: # EDITION MODULE else: # EDITION MODULE
# l'UE de rattachement peut changer # l'UE de rattachement peut changer
tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!") tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
@ -892,6 +749,7 @@ def module_edit(
] ]
db.session.add(module) db.session.add(module)
db.session.commit() db.session.commit()
module.formation.invalidate_cached_sems()
return flask.redirect( return flask.redirect(
url_for( url_for(
"notes.ue_table", "notes.ue_table",
@ -904,28 +762,36 @@ def module_edit(
def module_table(formation_id): def module_table(formation_id):
"""Liste des modules de la formation """Liste des modules de la formation
(XXX inutile ou a revoir) (affichage debug)
""" """
if not formation_id: formation = Formation.get_formation(formation_id)
raise ScoValueError("invalid formation !") editable = current_user.has_permission(Permission.EditFormation)
formation: Formation = Formation.query.get_or_404(formation_id)
H = [ H = [
f"""<h2>Listes des modules dans la formation {formation.titre} ({formation.acronyme}</h2> f"""<h2>Listes des modules dans la formation
{formation.titre} ({formation.acronyme} (debug)
</h2>
<ul class="notes_module_list"> <ul class="notes_module_list">
""", """,
] ]
editable = current_user.has_permission(Permission.EditFormation)
for module_dict in module_list(args={"formation_id": formation_id}): for module in formation.modules:
H.append('<li class="notes_module_list">%s' % module_dict) m_dict = module.to_dict()
m_dict["parcours"] = [p.code for p in module.parcours]
str_module = str(m_dict).replace(",", ",\n")
H.append(
f'<li class="notes_module_list"><pre style="margin-bottom: 1px;">{str_module}</pre>'
)
if editable: if editable:
H.append( H.append(
'<a href="module_edit?module_id=%(module_id)s">modifier</a>' f"""
% module_dict <a class="stdlink" href="{
) url_for('notes.module_edit', scodoc_dept=g.scodoc_dept, module_id=module.id)
H.append( }">modifier</a>
'<a href="module_delete?module_id=%(module_id)s">supprimer</a>' <a class="stdlink" href="{
% module_dict url_for('notes.module_delete', scodoc_dept=g.scodoc_dept, module_id=module.id)
}">supprimer</a>
"""
) )
H.append("</li>") H.append("</li>")
H.append("</ul>") H.append("</ul>")
@ -936,23 +802,6 @@ def module_table(formation_id):
) )
def module_is_locked(module_id):
"""True if module should not be modified
(used in a locked formsemestre)
"""
r = ndb.SimpleDictFetch(
"""SELECT mi.id
FROM notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi
WHERE mi.module_id = mod.id
AND mi.formsemestre_id = sem.id
AND mi.module_id = %(module_id)s
AND sem.etat = false
""",
{"module_id": module_id},
)
return len(r) > 0
def formation_add_malus_modules( def formation_add_malus_modules(
formation_id: int, semestre_id: int = None, titre=None, redirect=True formation_id: int, semestre_id: int = None, titre=None, redirect=True
): ):
@ -966,7 +815,7 @@ def formation_add_malus_modules(
ues = ues.filter_by(semestre_idx=semestre_id) ues = ues.filter_by(semestre_idx=semestre_id)
for ue in ues: for ue in ues:
if ue.type == codes_cursus.UE_STANDARD: if ue.type == codes_cursus.UE_STANDARD:
if ue_add_malus_module(ue, titre=titre) != None: if ue_add_malus_module(ue, titre=titre) is not None:
nb += 1 nb += 1
flash(f"Modules de malus ajoutés dans {nb} UEs du S{semestre_id}") flash(f"Modules de malus ajoutés dans {nb} UEs du S{semestre_id}")
@ -1002,7 +851,8 @@ def ue_add_malus_module(ue: UniteEns, titre=None, code=None) -> int:
# c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement # c'est ennuyeux: dans ce cas, on pourrait demander à indiquer explicitement
# le semestre ? ou affecter le malus au semestre 1 ??? # le semestre ? ou affecter le malus au semestre 1 ???
raise ScoValueError( raise ScoValueError(
"Impossible d'ajouter un malus si l'UE n'a pas de numéro de semestre et ne comporte pas d'autres modules" """Impossible d'ajouter un malus si l'UE n'a pas de numéro de semestre
et ne comporte pas d'autres modules"""
) )
else: else:
semestre_id = ue.semestre_idx semestre_id = ue.semestre_idx

View File

@ -37,7 +37,6 @@ from flask_login import current_user
from app import db, log from app import db, log
from app.but import apc_edit_ue from app.but import apc_edit_ue
from app.formations import edit_matiere, edit_module
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
from app.models import ( from app.models import (
Formation, Formation,
@ -66,7 +65,6 @@ from app.scodoc import codes_cursus
from app.scodoc import sco_edit_apc from app.scodoc import sco_edit_apc
from app.scodoc import sco_groups from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
from app.scodoc import sco_tag_module
_ueEditor = ndb.EditableTable( _ueEditor = ndb.EditableTable(
"notes_ue", "notes_ue",
@ -375,7 +373,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"type": "float", "type": "float",
"min_value": 0, "min_value": 0,
"title": "Coef. RCUE", "title": "Coef. RCUE",
"explanation": """pondération utilisée pour le calcul de la moyenne du RCUE. Laisser à 1, sauf si votre établissement a explicitement décidé de pondérations. "explanation": """pondération utilisée pour le calcul de la moyenne du RCUE.
Laisser à 1, sauf si votre établissement a explicitement décidé de pondérations.
""", """,
"defaut": 1.0, "defaut": 1.0,
"allow_null": False, "allow_null": False,
@ -421,7 +420,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
{ {
"title": "Code Apogée", "title": "Code Apogée",
"size": 25, "size": 25,
"explanation": "(optionnel) code élément pédagogique Apogée ou liste de codes ELP séparés par des virgules", "explanation": """(optionnel) code élément pédagogique Apogée
ou liste de codes ELP séparés par des virgules""",
"max_length": APO_CODE_STR_LEN, "max_length": APO_CODE_STR_LEN,
}, },
), ),
@ -445,7 +445,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"input_type": "boolcheckbox", "input_type": "boolcheckbox",
"title": "UE externe", "title": "UE externe",
"readonly": not create, # ne permet pas de transformer une UE existante en externe "readonly": not create, # ne permet pas de transformer une UE existante en externe
"explanation": "réservé pour les capitalisations d'UE effectuées à l'extérieur de l'établissement", "explanation": """réservé pour les capitalisations d'UEs
effectuées à l'extérieur de l'établissement""",
}, },
), ),
( (
@ -465,7 +466,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"input_type": "boolcheckbox", "input_type": "boolcheckbox",
"default": True, "default": True,
"title": "Créer matière identique", "title": "Créer matière identique",
"explanation": "créer immédiatement une matière dans cette UE (utile si on n'utilise pas de matières)", "explanation": """créer immédiatement une matière dans cette UE
(utile si on n'utilise pas de matières)""",
}, },
) )
) )
@ -553,7 +555,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
matiere_id = matiere.id matiere_id = matiere.id
if cursus.UE_IS_MODULE: if cursus.UE_IS_MODULE:
# dans ce mode, crée un (unique) module dans l'UE: # dans ce mode, crée un (unique) module dans l'UE:
_ = edit_module.do_module_create( _ = Module.create_from_dict(
{ {
"titre": tf[2]["titre"], "titre": tf[2]["titre"],
"code": tf[2]["acronyme"], "code": tf[2]["acronyme"],
@ -565,6 +567,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"semestre_id": tf[2]["semestre_idx"], "semestre_id": tf[2]["semestre_idx"],
}, },
) )
db.session.commit()
ue = db.session.get(UniteEns, ue_id) ue = db.session.get(UniteEns, ue_id)
flash(f"UE créée (code {ue.ue_code})") flash(f"UE créée (code {ue.ue_code})")
else: else:
@ -608,9 +611,10 @@ def _add_ue_semestre_id(ues: list[dict], is_apc):
ue["semestre_id"] = codes_cursus.UE_SEM_DEFAULT ue["semestre_id"] = codes_cursus.UE_SEM_DEFAULT
else: else:
# était le comportement ScoDoc7 # était le comportement ScoDoc7
modules = edit_module.module_list(args={"ue_id": ue["ue_id"]}) ue = UniteEns.get_ue(ue["ue_id"])
if modules: module = ue.modules.first()
ue["semestre_id"] = modules[0]["semestre_id"] if module:
ue["semestre_id"] = module.semestre_id
else: else:
ue["semestre_id"] = codes_cursus.UE_SEM_DEFAULT ue["semestre_id"] = codes_cursus.UE_SEM_DEFAULT
@ -1323,7 +1327,6 @@ def _ue_table_modules(
arrow_none, arrow_none,
delete_icon, delete_icon,
delete_disabled_icon, delete_disabled_icon,
unit_name="matière",
add_suppress_link=True, # lien "supprimer cette matière" add_suppress_link=True, # lien "supprimer cette matière"
empty_list_msg="Aucun élément dans cette matière", empty_list_msg="Aucun élément dans cette matière",
create_element_msg="créer un module", create_element_msg="créer un module",
@ -1361,7 +1364,6 @@ def _ue_table_modules(
H.append("</span>") H.append("</span>")
mod_editable = editable mod_editable = editable
# and not edit_module.module_is_locked(Mod['module_id'])
if mod_editable: if mod_editable:
H.append( H.append(
f"""<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par f"""<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par

View File

@ -36,7 +36,7 @@ from flask_login import current_user
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app import db from app import db
from app import log from app import log
from app.formations import edit_module, edit_ue from app.formations import edit_ue
from app.models import Formation, FormSemestre, Matiere, Module, UniteEns from app.models import Formation, FormSemestre, Matiere, Module, UniteEns
from app.models import ScolarNews from app.models import ScolarNews
from app.models.but_refcomp import ( from app.models.but_refcomp import (
@ -49,7 +49,6 @@ from app.models.but_refcomp import (
from app.scodoc import sco_cache from app.scodoc import sco_cache
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
from app.scodoc import sco_xml from app.scodoc import sco_xml
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
@ -123,11 +122,11 @@ def formation_export_dict(
del mat_d["id"] del mat_d["id"]
del mat_d["matiere_id"] del mat_d["matiere_id"]
del mat_d["ue_id"] del mat_d["ue_id"]
mods = edit_module.module_list({"matiere_id": matiere_id}) mat = db.session.get(Matiere, matiere_id)
mods.sort(key=lambda m: (m["numero"] or 0, m["code"])) mods = mat.modules.all()
mat_d["module"] = mods mods.sort(key=lambda m: (m.numero, m.code))
for mod_d in mods: mat_d["module"] = [mod.to_dict() for mod in mods]
module: Module = db.session.get(Module, mod_d["module_id"]) for module, mod_d in zip(mods, mat_d["module"]):
if export_tags: if export_tags:
tags = [t.title for t in module.tags] tags = [t.title for t in module.tags]
if tags: if tags:
@ -200,7 +199,8 @@ def formation_export(
if fmt is None: if fmt is None:
return f_dict return f_dict
filename = f"scodoc_formation_{formation.departement.acronym}_{formation.acronyme or ''}_v{formation.version}" filename = f"""scodoc_formation_{formation.departement.acronym
}_{formation.acronyme or ''}_v{formation.version}"""
return scu.sendResult( return scu.sendResult(
f_dict, f_dict,
name="formation", name="formation",
@ -290,14 +290,14 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F
try: try:
f = dom.getElementsByTagName("formation")[0] # or dom.documentElement f = dom.getElementsByTagName("formation")[0] # or dom.documentElement
D = sco_xml.xml_to_dicts(f) xml_dicts = sco_xml.xml_to_dicts(f)
except Exception as exc: except Exception as exc:
raise ScoFormatError( raise ScoFormatError(
"""Ce document xml ne correspond pas à un programme exporté par ScoDoc. """Ce document xml ne correspond pas à un programme exporté par ScoDoc.
(élément 'formation' inexistant par exemple).""" (élément 'formation' inexistant par exemple)."""
) from exc ) from exc
assert D[0] == "formation" assert xml_dicts[0] == "formation"
f_dict = D[1] f_dict = xml_dicts[1]
f_dict["dept_id"] = g.scodoc_dept_id f_dict["dept_id"] = g.scodoc_dept_id
# Pour les clonages, on prend le refcomp_id donné: # Pour les clonages, on prend le refcomp_id donné:
referentiel_competence_id = ( referentiel_competence_id = (
@ -334,7 +334,7 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F
modules_a_coefficienter = [] # Liste des modules avec coefs APC modules_a_coefficienter = [] # Liste des modules avec coefs APC
with sco_cache.DeferredSemCacheManager(): with sco_cache.DeferredSemCacheManager():
# -- create UEs # -- create UEs
for ue_info in D[2]: for ue_info in xml_dicts[2]:
assert ue_info[0] == "ue" assert ue_info[0] == "ue"
ue_info[1]["formation_id"] = formation.id ue_info[1]["formation_id"] = formation.id
if "ue_id" in ue_info[1]: if "ue_id" in ue_info[1]:
@ -414,11 +414,12 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F
mod_info[1]["ue_id"] = ue_id mod_info[1]["ue_id"] = ue_id
if not "module_type" in mod_info[1]: if not "module_type" in mod_info[1]:
mod_info[1]["module_type"] = scu.ModuleType.STANDARD mod_info[1]["module_type"] = scu.ModuleType.STANDARD
mod_id = edit_module.do_module_create(mod_info[1]) module = Module.create_from_dict(
mod_info[1], news=True, inval_cache=True
)
if xml_module_id: if xml_module_id:
modules_old2new[int(xml_module_id)] = mod_id modules_old2new[int(xml_module_id)] = module.id
if len(mod_info) > 2: if len(mod_info) > 2:
module: Module = db.session.get(Module, mod_id)
tag_names = [] tag_names = []
ue_coef_dict = {} ue_coef_dict = {}
for child in mod_info[2]: for child in mod_info[2]:

View File

@ -66,7 +66,8 @@ class Formation(ScoDocModel):
def html(self) -> str: def html(self) -> str:
"titre complet pour affichage" "titre complet pour affichage"
return f"""Formation {self.titre} ({self.acronyme}) version {self.version} code <tt>{self.formation_code}</tt>""" return f"""Formation {self.titre} ({self.acronyme}) version {self.version
} code <tt>{self.formation_code}</tt>"""
@classmethod @classmethod
def get_formation(cls, formation_id: int | str, dept_id: int = None) -> "Formation": def get_formation(cls, formation_id: int | str, dept_id: int = None) -> "Formation":
@ -334,7 +335,7 @@ class Matiere(ScoDocModel):
return e return e
def is_locked(self) -> bool: def is_locked(self) -> bool:
"""True if matiere cannot be be modified """True if matiere can't modified
because it contains modules used in a locked formsemestre. because it contains modules used in a locked formsemestre.
""" """
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
@ -361,8 +362,6 @@ class Matiere(ScoDocModel):
def delete(self): def delete(self):
"Delete matière. News, inval cache." "Delete matière. News, inval cache."
from app.models import ScolarNews
formation = self.ue.formation formation = self.ue.formation
log(f"matiere.delete: matiere_id={self.id}") log(f"matiere.delete: matiere_id={self.id}")
if not self.can_be_deleted(): if not self.can_be_deleted():

View File

@ -7,14 +7,15 @@ from flask_login import current_user
from flask_sqlalchemy.query import Query from flask_sqlalchemy.query import Query
import app import app
from app import db from app import db, log
from app.auth.models import User from app.auth.models import User
from app.comp import df_cache from app.comp import df_cache
from app.models import APO_CODE_STR_LEN, ScoDocModel from app.models import APO_CODE_STR_LEN, ScoDocModel
from app.models.etudiants import Identite from app.models.etudiants import Identite
from app.models.evaluations import Evaluation from app.models.evaluations import Evaluation
from app.models.modules import Module from app.models.modules import Module
from app.scodoc.sco_exceptions import AccessDenied, ScoLockedSemError from app.scodoc import sco_cache
from app.scodoc.sco_exceptions import AccessDenied, ScoLockedSemError, ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -65,6 +66,30 @@ class ModuleImpl(ScoDocModel):
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>" return f"<{self.__class__.__name__} {self.id} module={repr(self.module)}>"
@classmethod
def create_from_dict(cls, data: dict) -> "ModuleImpl":
"""Create modimpl from dict. Log, inval. cache.
data must include valid formsemestre_id, module_id and responsable_id
Commit session.
"""
from app.models import FormSemestre
# check required args
for required_arg in ("formsemestre_id", "module_id", "responsable_id"):
if required_arg not in data:
raise ScoValueError(f"missing argument: {required_arg}")
_ = FormSemestre.get_formsemestre(data["formsemestre_id"])
_ = Module.get_instance(data["module_id"])
if not db.session.get(User, data["responsable_id"]):
abort(404, "responsable_id invalide")
modimpl = super().create_from_dict(data)
db.session.commit()
db.session.refresh(modimpl)
log(f"ModuleImpl.create: created {modimpl.id} with {data}")
sco_cache.invalidate_formsemestre(formsemestre_id=modimpl.formsemestre_id)
return modimpl
def get_codes_apogee(self) -> set[str]: def get_codes_apogee(self) -> set[str]:
"""Les codes Apogée (codés en base comme "VRT1,VRT2"). """Les codes Apogée (codés en base comme "VRT1,VRT2").
(si non renseigné, ceux du module) (si non renseigné, ceux du module)

View File

@ -2,7 +2,7 @@
""" """
import http import http
from flask import current_app, g from flask import current_app, g, url_for
from app import db, log from app import db, log
from app import models from app import models
@ -13,9 +13,14 @@ from app.models.but_refcomp import (
app_critiques_modules, app_critiques_modules,
parcours_modules, parcours_modules,
) )
from app.models.events import ScolarNews
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.codes_cursus import UE_SPORT from app.scodoc.codes_cursus import UE_SPORT
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import (
ScoValueError,
ScoLockedFormError,
ScoNonEmptyFormationObject,
)
from app.scodoc.sco_utils import ModuleType from app.scodoc.sco_utils import ModuleType
@ -115,13 +120,39 @@ class Module(models.ScoDocModel):
# on ne peut pas affecter directement parcours # on ne peut pas affecter directement parcours
return super().filter_model_attributes(args, (excluded or set()) | {"parcours"}) return super().filter_model_attributes(args, (excluded or set()) | {"parcours"})
@classmethod
def check_module_code_unicity(cls, code, formation_id, module_id=None) -> bool:
"true si code module unique dans la formation"
from app.models import Formation
formation = Formation.get_formation(formation_id)
query = formation.modules.filter_by(code=code)
if module_id is not None: # edition: supprime le module en cours
query = query.filter(Module.id != module_id)
return query.count() == 0
def from_dict(self, args: dict, excluded: set[str] | None = None) -> bool: def from_dict(self, args: dict, excluded: set[str] | None = None) -> bool:
"""Update object's fields given in dict. Add to session but don't commit. """Update object's fields given in dict. Add to session but don't commit.
True if modification. True if modification.
- can't change ue nor formation - can't change ue nor formation
- can change matiere_id, iff new matiere in same ue - can change matiere_id, iff new matiere in same ue
- can change parcours: parcours list of ApcParcour id or instances. - can change parcours: parcours list of ApcParcour id or instances.
Ne modifie pas les coefficients APC ue_coefs
""" """
args = args.copy()
if "ue_coefs" in args:
del args["ue_coefs"]
if self.is_locked():
# formation verrouillée: empeche de modifier coefficient, matiere, and semestre_id
protected_fields = ("coefficient", "matiere_id", "semestre_id")
for f in protected_fields:
if f in args:
del args[f]
# Unicité du code
if "code" in args and not Module.check_module_code_unicity(
args["code"], self.formation_id, self.id
):
raise ScoValueError("code module déjà utilisé")
# Vérifie les changements de matiere # Vérifie les changements de matiere
new_matiere_id = args.get("matiere_id", self.matiere_id) new_matiere_id = args.get("matiere_id", self.matiere_id)
if new_matiere_id != self.matiere_id: if new_matiere_id != self.matiere_id:
@ -139,20 +170,103 @@ class Module(models.ScoDocModel):
existing_parcours = {p.id for p in self.parcours} existing_parcours = {p.id for p in self.parcours}
new_parcours = args.get("parcours", []) or [] new_parcours = args.get("parcours", []) or []
if existing_parcours != set(new_parcours): if existing_parcours != set(new_parcours):
self._set_parcours_from_list(new_parcours) self.set_parcours_from_list(new_parcours)
return True return True
return modified return modified
@classmethod @classmethod
def create_from_dict(cls, data: dict) -> "Module": def create_from_dict(
cls,
data: dict,
inval_cache=False,
news=False,
) -> "Module":
"""Create from given dict, add parcours. """Create from given dict, add parcours.
Flush session.""" Flush session.
Si news, commit and log news.
"""
from app.models.formations import Formation
# check required arguments
for required_arg in ("code", "formation_id", "ue_id"):
if required_arg not in data:
raise ScoValueError(f"missing argument: {required_arg}")
if not data["code"]:
raise ScoValueError("module code must be non empty")
# Check formation
formation = Formation.get_formation(data["formation_id"])
ue = UniteEns.get_ue(data["ue_id"])
# refuse de créer un module APC avec semestres semestre du module != semestre de l'UE
if formation.is_apc():
if int(data.get("semestre_id", 1)) != ue.semestre_idx:
raise ScoValueError(
"Formation incompatible: indices UE et module différents"
)
module = super().create_from_dict(data) module = super().create_from_dict(data)
db.session.flush() db.session.flush()
module._set_parcours_from_list(data.get("parcours", []) or []) module.set_parcours_from_list(data.get("parcours", []) or [])
log(f"module_create: created {module.id} with {data}")
if news:
db.session.commit()
db.session.refresh(module)
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
)
if inval_cache:
formation.invalidate_cached_sems()
return module return module
def _set_parcours_from_list(self, parcours: list[ApcParcours | int]): def is_locked(self) -> bool:
"""True if module cannot be modified
because it is used in a locked formsemestre.
"""
from app.models import FormSemestre, ModuleImpl
mods = (
db.session.query(Module)
.filter_by(id=self.id)
.join(ModuleImpl)
.join(FormSemestre)
.filter_by(etat=False)
.all()
)
return bool(mods)
def can_be_deleted(self) -> bool:
"""True if module can be deleted"""
return self.modimpls.count() == 0
def delete(self):
"Delete module. News, inval cache."
if self.is_locked():
raise ScoLockedFormError()
if not self.can_be_deleted():
raise ScoNonEmptyFormationObject(
"Module",
msg=self.titre or self.code,
dest_url=url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
formation_id=self.formation_id,
semestre_idx=self.ue.semestre_idx,
),
)
formation = self.formation
db.session.delete(self)
log(f"Module.delete({self.id})")
db.session.commit()
# news
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
)
formation.invalidate_cached_sems()
def set_parcours_from_list(self, parcours: list[ApcParcours | int]):
"""Ajoute ces parcours à la liste des parcours du module. """Ajoute ces parcours à la liste des parcours du module.
Chaque élément est soit un objet parcours soit un id. Chaque élément est soit un objet parcours soit un id.
S'assure que chaque parcours est dans le référentiel de compétence S'assure que chaque parcours est dans le référentiel de compétence

View File

@ -41,8 +41,8 @@ from app import ScoValueError
from app import comp from app import comp
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
from app.models import FormSemestre, UniteEns from app.models import FormSemestre, UniteEns
import app.pe.pe_affichage as pe_affichage from app.pe import pe_affichage
import app.pe.pe_etudiant as pe_etudiant from app.pe import pe_etudiant
from app.pe.moys import pe_tabletags, pe_moytag from app.pe.moys import pe_tabletags, pe_moytag
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc import codes_cursus as sco_codes from app.scodoc import codes_cursus as sco_codes
@ -59,13 +59,18 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
def __init__( def __init__(
self, self,
formsemestre: FormSemestre, formsemestre: FormSemestre,
options={"moyennes_tags": True, "moyennes_ue_res_sae": False}, options: dict | None = None,
): ):
""" """
Args: Args:
formsemestre: le ``FormSemestre`` sur lequel il se base formsemestre: le ``FormSemestre`` sur lequel il se base
options: Un dictionnaire d'options options: Un dictionnaire d'options
""" """
options = (
{"moyennes_tags": True, "moyennes_ue_res_sae": False}
if options is None
else options
)
ResultatsSemestreBUT.__init__(self, formsemestre) ResultatsSemestreBUT.__init__(self, formsemestre)
pe_tabletags.TableTag.__init__(self) pe_tabletags.TableTag.__init__(self)

View File

@ -491,9 +491,9 @@ def dict_decision_jury(
] ]
d["decision_ue"] = [] d["decision_ue"] = []
if decision[ if decision["decisions_ue"]:
"decisions_ue" # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id):
]: # and sco_preferences.get_preference( 'bul_show_uevalid', formsemestre_id): always publish (car utile pour export Apogee) # always publish (car utile pour export Apogee)
for ue_id in decision["decisions_ue"].keys(): for ue_id in decision["decisions_ue"].keys():
ue = edit_ue.ue_list({"ue_id": ue_id})[0] ue = edit_ue.ue_list({"ue_id": ue_id})[0]
d["decision_ue"].append( d["decision_ue"].append(

View File

@ -34,7 +34,6 @@ import sqlalchemy as sa
from app import db from app import db
from app.auth.models import User from app.auth.models import User
from app.formations import edit_module
from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN from app.models import APO_CODE_STR_LEN, SHORT_STR_LEN
from app.models import ( from app.models import (
ApcValidationAnnee, ApcValidationAnnee,
@ -939,7 +938,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"formsemestre_id": formsemestre_id, "formsemestre_id": formsemestre_id,
"responsable_id": tf[2][f"MI{module_id}"], "responsable_id": tf[2][f"MI{module_id}"],
} }
_ = sco_moduleimpl.do_moduleimpl_create(modargs) _ = ModuleImpl.create_from_dict(modargs)
else: else:
# Modification du semestre: # Modification du semestre:
# on doit creer les modules nouvellement selectionnés # on doit creer les modules nouvellement selectionnés
@ -971,27 +970,23 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"formsemestre_id": formsemestre.id, "formsemestre_id": formsemestre.id,
"responsable_id": tf[2]["MI" + str(module_id)], "responsable_id": tf[2]["MI" + str(module_id)],
} }
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs) modimpl = ModuleImpl.create_from_dict(modargs)
mod = edit_module.module_list({"module_id": module_id})[0] assert modimpl.module_id == module_id
msg += [ mod = modimpl.module
"création de %s (%s)" % (mod["code"] or "?", mod["titre"] or "?") msg += [f"""création de {mod.code or "?"} ({mod.titre or "?"})"""]
]
# INSCRIPTIONS DES ETUDIANTS # INSCRIPTIONS DES ETUDIANTS
log( group_id = tf[2][f"{module_id}!group_id"]
'inscription module: %s = "%s"' log(f"""inscription module: {module_id}!group_id = '{group_id}'""")
% ("%s!group_id" % module_id, tf[2]["%s!group_id" % module_id])
)
group_id = tf[2]["%s!group_id" % module_id]
if group_id: if group_id:
etudids = [ etudids = [
x["etudid"] for x in sco_groups.get_group_members(group_id) x["etudid"] for x in sco_groups.get_group_members(group_id)
] ]
log( log(
"inscription module:module_id=%s,moduleimpl_id=%s: %s" "inscription module:module_id=%s,moduleimpl_id=%s: %s"
% (module_id, moduleimpl_id, etudids) % (module_id, modimpl.id, etudids)
) )
sco_moduleimpl.do_moduleimpl_inscrit_etuds( sco_moduleimpl.do_moduleimpl_inscrit_etuds(
moduleimpl_id, modimpl.id,
formsemestre.id, formsemestre.id,
etudids, etudids,
) )
@ -1002,7 +997,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
else: else:
log( log(
"inscription module:module_id=%s,moduleimpl_id=%s: aucun etudiant inscrit" "inscription module:module_id=%s,moduleimpl_id=%s: aucun etudiant inscrit"
% (module_id, moduleimpl_id) % (module_id, modimpl.id)
) )
# #
ok, diag = formsemestre_delete_moduleimpls( ok, diag = formsemestre_delete_moduleimpls(
@ -1022,7 +1017,6 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
sco_moduleimpl.do_moduleimpl_edit( sco_moduleimpl.do_moduleimpl_edit(
modargs, formsemestre_id=formsemestre.id modargs, formsemestre_id=formsemestre.id
) )
mod = edit_module.module_list({"module_id": module_id})[0]
# --- Association des parcours # --- Association des parcours
if formsemestre is None: if formsemestre is None:
formsemestre = FormSemestre.get_formsemestre(formsemestre_id) formsemestre = FormSemestre.get_formsemestre(formsemestre_id)

View File

@ -40,7 +40,6 @@ from app.but.cursus_but import formsemestre_warning_apc_setup
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_common import ResultatsSemestre from app.comp.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat from app.comp.res_compat import NotesTableCompat
from app.formations import formation_io
from app.models import ( from app.models import (
Evaluation, Evaluation,
Formation, Formation,

View File

@ -51,18 +51,6 @@ _moduleimplEditor = ndb.EditableTable(
) )
def do_moduleimpl_create(args):
"create a moduleimpl"
# TODO remplacer par une methode de ModuleImpl qui appelle
# super().create_from_dict() puis invalide le formsemestre
cnx = ndb.GetDBConnexion()
r = _moduleimplEditor.create(cnx, args)
sco_cache.invalidate_formsemestre(
formsemestre_id=args["formsemestre_id"]
) # > creation moduleimpl
return r
def do_moduleimpl_delete(oid, formsemestre_id=None): def do_moduleimpl_delete(oid, formsemestre_id=None):
"delete moduleimpl (desinscrit tous les etudiants)" "delete moduleimpl (desinscrit tous les etudiants)"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()

View File

@ -48,8 +48,7 @@ from wtforms import (
HiddenField, HiddenField,
SelectMultipleField, SelectMultipleField,
) )
from app.formations import edit_module from app.models import Evaluation, Module, ModuleImpl
from app.models import Evaluation, ModuleImpl
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
@ -243,9 +242,9 @@ class PlacementRunner:
self.moduleimpl_data = sco_moduleimpl.moduleimpl_list( self.moduleimpl_data = sco_moduleimpl.moduleimpl_list(
moduleimpl_id=self.moduleimpl_id moduleimpl_id=self.moduleimpl_id
)[0] )[0]
self.module_data = edit_module.module_list( self.module_data = Module.get_module(
args={"module_id": self.moduleimpl_data["module_id"]} self.moduleimpl_data["module_id"]
)[0] ).to_dict()
self.sem = sco_formsemestre.get_formsemestre( self.sem = sco_formsemestre.get_formsemestre(
self.moduleimpl_data["formsemestre_id"] self.moduleimpl_data["formsemestre_id"]
) )

View File

@ -56,12 +56,12 @@ Solution proposée (nov 2014):
import flask import flask
from flask import flash, g, request, render_template, url_for from flask import flash, g, request, render_template, url_for
from flask_login import current_user from flask_login import current_user
from app.formations import edit_module, edit_ue from app.formations import edit_ue
from app.models.formsemestre import FormSemestre from app.models.formsemestre import FormSemestre
from app import db, log from app import db, log
from app.models import Evaluation, Identite, Matiere, ModuleImpl, UniteEns from app.models import Evaluation, Identite, Matiere, Module, ModuleImpl, UniteEns
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_moduleimpl from app.scodoc import sco_moduleimpl
from app.scodoc import sco_saisie_notes from app.scodoc import sco_saisie_notes
@ -114,7 +114,7 @@ def external_ue_create(
{"ue_id": ue_id, "titre": titre or acronyme, "numero": 1} {"ue_id": ue_id, "titre": titre or acronyme, "numero": 1}
) )
module_id = edit_module.do_module_create( module = Module.create_from_dict(
{ {
"titre": "UE extérieure", "titre": "UE extérieure",
"code": acronyme, "code": acronyme,
@ -125,11 +125,13 @@ def external_ue_create(
"semestre_id": formsemestre.semestre_id, "semestre_id": formsemestre.semestre_id,
"module_type": scu.ModuleType.STANDARD, "module_type": scu.ModuleType.STANDARD,
}, },
news=True,
inval_cache=True,
) )
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create( modimpl = ModuleImpl.create_from_dict(
{ {
"module_id": module_id, "module_id": module.id,
"formsemestre_id": formsemestre_id, "formsemestre_id": formsemestre_id,
# affecte le 1er responsable du semestre comme resp. du module # affecte le 1er responsable du semestre comme resp. du module
"responsable_id": ( "responsable_id": (
@ -139,7 +141,6 @@ def external_ue_create(
), ),
}, },
) )
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
assert modimpl assert modimpl
return modimpl return modimpl

View File

@ -2575,21 +2575,21 @@ def check_sem_integrity(formsemestre_id, fix=False):
bad_sem = [] bad_sem = []
formations_set = set() # les formations mentionnées dans les UE et modules formations_set = set() # les formations mentionnées dans les UE et modules
for modimpl in modimpls: for modimpl in modimpls:
mod = edit_module.module_list({"module_id": modimpl["module_id"]})[0] mod = Module.get_instance(modimpl["module_id"])
formations_set.add(mod["formation_id"]) formations_set.add(mod.formation_id)
ue = UniteEns.query.get_or_404(mod["ue_id"]) ue = mod.ue
ue_dict = ue.to_dict() ue_dict = ue.to_dict()
formations_set.add(ue_dict["formation_id"]) formations_set.add(ue_dict["formation_id"])
if ue_dict["formation_id"] != mod["formation_id"]: if ue_dict["formation_id"] != mod.formation_id:
modimpl["mod"] = mod 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 sem["formation_id"] != mod.formation_id:
bad_sem.append(modimpl) bad_sem.append(modimpl)
modimpl["mod"] = mod modimpl["mod"] = mod.to_dict()
H = [ H = [
"<p>formation_id=%s" % sem["formation_id"], f"""<p>formation_id={sem["formation_id"]}""",
] ]
if bad_ue: if bad_ue:
H += [ H += [

View File

@ -86,7 +86,6 @@ from app.scodoc import (
sco_archives_etud, sco_archives_etud,
sco_bug_report, sco_bug_report,
sco_cache, sco_cache,
sco_debouche,
sco_dept, sco_dept,
sco_dump_db, sco_dump_db,
sco_etud, sco_etud,

View File

@ -1,6 +1,8 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"Infos sur version ScoDoc"
SCOVERSION = "9.7.28" SCOVERSION = "9.7.28"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"

View File

@ -117,7 +117,7 @@ def GET(
print("reply", reply.text) print("reply", reply.text)
raise APIError( raise APIError(
errmsg or f"""erreur get {url} !""", errmsg or f"""erreur get {url} !""",
reply.json(), reply if reply.status_code == 404 else reply.json(),
status_code=reply.status_code, status_code=reply.status_code,
) )
if raw: if raw:
@ -220,7 +220,8 @@ def check_failure_get(path: str, headers: dict, err: str = None):
# ^ Renvoi un 404 # ^ Renvoi un 404
except APIError as api_err: except APIError as api_err:
if err is not None: if err is not None:
assert api_err.payload["message"] == err if "message" in api_err.payload:
assert api_err.payload["message"] == err
else: else:
raise APIError("Le GET n'aurait pas du fonctionner") raise APIError("Le GET n'aurait pas du fonctionner")

View File

@ -177,7 +177,7 @@ def test_formation_export(api_headers):
assert isinstance(module["heures_td"], float) assert isinstance(module["heures_td"], float)
assert isinstance(module["heures_tp"], float) assert isinstance(module["heures_tp"], float)
assert isinstance(module["coefficient"], float) assert isinstance(module["coefficient"], float)
assert isinstance(module["ects"], str) assert isinstance(module["ects"], str) if "ects" in module else True
assert isinstance(module["semestre_id"], int) assert isinstance(module["semestre_id"], int)
assert isinstance(module["numero"], int) assert isinstance(module["numero"], int)
assert isinstance(module["code_apogee"], str) assert isinstance(module["code_apogee"], str)

View File

@ -1,5 +1,6 @@
"""Utilitaires pour les tests de l'API """Utilitaires pour les tests de l'API
""" """
import json import json
@ -37,11 +38,11 @@ def verify_occurences_ids_etuds(json_response) -> bool:
DEPARTEMENT_FIELDS = [ DEPARTEMENT_FIELDS = [
"id",
"acronym", "acronym",
"description",
"visible",
"date_creation", "date_creation",
"description",
"id",
"visible",
] ]
# Champs "données personnelles" # Champs "données personnelles"
@ -67,17 +68,17 @@ ETUD_FIELDS = {
FORMATION_FIELDS = { FORMATION_FIELDS = {
"dept_id",
"acronyme", "acronyme",
"titre_officiel",
"formation_code",
"code_specialite", "code_specialite",
"id", "dept_id",
"titre", "formation_code",
"version",
"type_parcours",
"referentiel_competence_id",
"formation_id", "formation_id",
"id",
"referentiel_competence_id",
"titre_officiel",
"titre",
"type_parcours",
"version",
} }
FORMATION_EXPORT_FIELDS = { FORMATION_EXPORT_FIELDS = {
@ -95,39 +96,42 @@ FORMATION_EXPORT_FIELDS = {
FORMATION_EXPORT_UE_FIELDS = { FORMATION_EXPORT_UE_FIELDS = {
"acronyme", "acronyme",
"code_apogee",
"coefficient",
"color",
"ects",
"is_external",
"matiere",
"numero", "numero",
"reference",
"semestre_idx",
"titre", "titre",
"type", "type",
"ue_code", "ue_code",
"ects",
"is_external",
"code_apogee",
"coefficient",
"semestre_idx",
"color",
"reference",
"matiere",
} }
FORMATION_EXPORT_UE_MATIERE_FIELDS = { FORMATION_EXPORT_UE_MATIERE_FIELDS = {
"titre",
"numero",
"module", "module",
"numero",
"titre",
} }
FORMATION_EXPORT_UE_MATIERE_MODULE_FIELDS = { FORMATION_EXPORT_UE_MATIERE_MODULE_FIELDS = {
"titre",
"abbrev", "abbrev",
"app_critiques",
"code_apogee",
"code", "code",
"coefficient",
"coefficients",
"edt_id",
"heures_cours", "heures_cours",
"heures_td", "heures_td",
"coefficient", "heures_tp",
"ects",
"semestre_id",
"numero",
"code_apogee",
"module_type", "module_type",
"coefficients", "numero",
"parcours",
"semestre_id",
"titre",
} }
FORMATION_EXPORT_UE_MATIERE_MODULE_COEF_FIELDS = { FORMATION_EXPORT_UE_MATIERE_MODULE_COEF_FIELDS = {
@ -136,42 +140,42 @@ FORMATION_EXPORT_UE_MATIERE_MODULE_COEF_FIELDS = {
} }
FORMSEMESTRE_FIELDS = [ FORMSEMESTRE_FIELDS = [
"titre", "block_moyenne_generale",
"gestion_semestrielle", "block_moyennes",
"scodoc7_id",
"date_debut",
"bul_bgcolor", "bul_bgcolor",
"bul_hide_xml",
"date_debut_iso",
"date_debut",
"date_fin_iso",
"date_fin", "date_fin",
"resp_can_edit", "departement",
"dept_id", "dept_id",
"elt_annee_apo",
"elt_sem_apo",
"ens_can_edit_eval",
"etape_apo",
"etat", "etat",
"resp_can_change_ens", "formation_id",
"formation",
"formsemestre_id",
"gestion_compensation",
"gestion_semestrielle",
"id", "id",
"modalite", "modalite",
"ens_can_edit_eval",
"formation_id",
"gestion_compensation",
"elt_sem_apo",
"semestre_id",
"bul_hide_xml",
"elt_annee_apo",
"block_moyenne_generale",
"formsemestre_id",
"titre_num",
"titre_formation",
"date_debut_iso",
"date_fin_iso",
"responsables",
"parcours", "parcours",
"departement", "resp_can_change_ens",
"formation", "resp_can_edit",
"etape_apo", "responsables",
"block_moyennes", "scodoc7_id",
"semestre_id",
"titre_formation",
"titre_num",
"titre",
] ]
FSEM_FIELDS = { FSEM_FIELDS = {
"block_moyennes",
"block_moyenne_generale", "block_moyenne_generale",
"block_moyennes",
"bul_bgcolor", "bul_bgcolor",
"bul_hide_xml", "bul_hide_xml",
"date_debut_iso", "date_debut_iso",
@ -198,123 +202,123 @@ FSEM_FIELDS = {
} }
MODIMPL_FIELDS = { MODIMPL_FIELDS = {
"id",
"formsemestre_id",
"computation_expr", "computation_expr",
"module_id",
"responsable_id",
"moduleimpl_id",
"ens", "ens",
"formsemestre_id",
"id",
"module_id",
"module", "module",
"moduleimpl_id",
"responsable_id",
} }
MODULE_FIELDS = { MODULE_FIELDS = {
"heures_tp",
"code_apogee",
"titre",
"coefficient",
"module_type",
"id",
"ects",
"abbrev", "abbrev",
"ue_id", "code_apogee",
"code", "code",
"coefficient",
"ects",
"formation_id", "formation_id",
"heures_cours", "heures_cours",
"matiere_id",
"heures_td", "heures_td",
"semestre_id", "heures_tp",
"numero", "id",
"matiere_id",
"module_id", "module_id",
"module_type",
"numero",
"semestre_id",
"titre",
"ue_id",
} }
UE_FIELDS = { UE_FIELDS = {
"semestre_idx",
"type",
"formation_id",
"ue_code",
"id",
"ects",
"acronyme", "acronyme",
"is_external",
"numero",
"code_apogee", "code_apogee",
"titre",
"coefficient", "coefficient",
"color", "color",
"ects",
"formation_id",
"id",
"is_external",
"numero",
"semestre_idx",
"titre",
"type",
"ue_code",
"ue_id", "ue_id",
} }
BULLETIN_FIELDS = { BULLETIN_FIELDS = {
"version",
"type",
"date", "date",
"publie", "etat_inscription",
"etudiant", "etudiant",
"formation", "formation",
"formsemestre_id", "formsemestre_id",
"etat_inscription",
"options", "options",
"publie",
"ressources", "ressources",
"saes", "saes",
"ues",
"semestre", "semestre",
"type",
"ues",
"version",
} }
BULLETIN_ETUDIANT_FIELDS = { BULLETIN_ETUDIANT_FIELDS = {
"boursier",
"civilite", "civilite",
"code_ine", "code_ine",
"code_nip", "code_nip",
"codepostaldomicile",
"date_naissance", "date_naissance",
"dept_id",
"dept_acronym", "dept_acronym",
"dept_id",
"dept_naissance",
"description",
"domicile",
"email", "email",
"emailperso", "emailperso",
"etudid", "etudid",
"nom",
"prenom",
"nomprenom",
"lieu_naissance",
"dept_naissance",
"nationalite",
"boursier",
"fiche_url",
"photo_url",
"id",
"domicile",
"villedomicile",
"telephone",
"fax", "fax",
"description", "fiche_url",
"codepostaldomicile", "id",
"lieu_naissance",
"nationalite",
"nom",
"nomprenom",
"paysdomicile", "paysdomicile",
"photo_url",
"prenom",
"telephone",
"telephonemobile", "telephonemobile",
"typeadresse", "typeadresse",
"villedomicile",
} }
BULLETIN_FORMATION_FIELDS = {"id", "acronyme", "titre_officiel", "titre"} BULLETIN_FORMATION_FIELDS = {"id", "acronyme", "titre_officiel", "titre"}
BULLETIN_OPTIONS_FIELDS = { BULLETIN_OPTIONS_FIELDS = {
"show_abs",
"show_abs_modules", "show_abs_modules",
"show_ects", "show_abs",
"show_codemodules", "show_codemodules",
"show_coef",
"show_date_inscr",
"show_ects",
"show_matieres", "show_matieres",
"show_rangs", "show_minmax_eval",
"show_ue_rangs", "show_minmax_mod",
"show_minmax",
"show_mod_rangs", "show_mod_rangs",
"show_moypromo", "show_moypromo",
"show_minmax", "show_rangs",
"show_minmax_mod",
"show_minmax_eval",
"show_coef",
"show_ue_cap_details",
"show_ue_cap_current",
"show_temporary", "show_temporary",
"temporary_txt", "show_ue_cap_current",
"show_ue_cap_details",
"show_ue_rangs",
"show_uevalid", "show_uevalid",
"show_date_inscr", "temporary_txt",
} }
BULLETIN_RESSOURCES_FIELDS = { BULLETIN_RESSOURCES_FIELDS = {
@ -346,23 +350,23 @@ BULLETIN_SAES_FIELDS = {
########### RESSOURCES ET SAES ########### ########### RESSOURCES ET SAES ###########
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_FIELDS = { BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_FIELDS = {
"id",
"titre",
"code_apogee", "code_apogee",
"url",
"moyenne",
"evaluations", "evaluations",
"id",
"moyenne",
"titre",
"url",
} }
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_FIELDS = { BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_FIELDS = {
"id", "coef",
"description",
"date", "date",
"description",
"heure_debut", "heure_debut",
"heure_fin", "heure_fin",
"coef", "id",
"poids",
"note", "note",
"poids",
"url", "url",
} }
@ -373,10 +377,10 @@ BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_POIDS_FIELDS = {
} }
BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_NOTE_FIELDS = { BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_NOTE_FIELDS = {
"value",
"min",
"max", "max",
"min",
"moy", "moy",
"value",
} }
@ -384,19 +388,19 @@ BULLETIN_RESSOURCES_ET_SAES_RESSOURCE_ET_SAE_EVALUATION_NOTE_FIELDS = {
BULLETIN_UES_FIELDS = {"RT1.1", "RT2.1", "RT3.1"} BULLETIN_UES_FIELDS = {"RT1.1", "RT2.1", "RT3.1"}
BULLETIN_UES_UE_FIELDS = { BULLETIN_UES_UE_FIELDS = {
"id", "bonus",
"titre", "capitalise",
"numero",
"type",
"color", "color",
"competence", "competence",
"moyenne", "ECTS",
"bonus", "id",
"malus", "malus",
"capitalise", "moyenne",
"numero",
"ressources", "ressources",
"saes", "saes",
"ECTS", "titre",
"type",
} }
BULLETIN_UES_UE_MOYENNE_FIELDS = {"value", "min", "max", "moy", "rang", "total"} BULLETIN_UES_UE_MOYENNE_FIELDS = {"value", "min", "max", "moy", "rang", "total"}
@ -461,16 +465,16 @@ BULLETIN_UES_UE_ECTS_FIELDS = {"acquis", "total"}
########### SEMESTRE ########### ########### SEMESTRE ###########
BULLETIN_SEMESTRE_FIELDS = { BULLETIN_SEMESTRE_FIELDS = {
"etapes", "absences",
"annee_universitaire",
"date_debut", "date_debut",
"date_fin", "date_fin",
"annee_universitaire",
"numero",
"inscription",
"groupes",
"absences",
"ECTS", "ECTS",
"etapes",
"groupes",
"inscription",
"notes", "notes",
"numero",
"rang", "rang",
} }
@ -484,78 +488,78 @@ BULLETIN_SEMESTRE_RANG_FIELDS = {"value", "total"}
EVAL_FIELDS = { EVAL_FIELDS = {
"id", "coefficient",
"description",
"date_debut", "date_debut",
"date_fin", "date_fin",
"coefficient", "description",
"etat",
"evaluation_type", "evaluation_type",
"id",
"moduleimpl_id", "moduleimpl_id",
"nb_inscrits",
"nb_notes_abs",
"nb_notes_att",
"nb_notes_exc",
"nb_notes_manquantes",
"note_max", "note_max",
"numero", "numero",
"poids", "poids",
"publish_incomplete", "publish_incomplete",
"visibulletin",
"etat",
"nb_inscrits",
"nb_notes_manquantes",
"nb_notes_abs",
"nb_notes_att",
"nb_notes_exc",
"saisie_notes", "saisie_notes",
"visibulletin",
} }
SAISIE_NOTES_FIELDS = {"datetime_debut", "datetime_fin", "datetime_mediane"} SAISIE_NOTES_FIELDS = {"datetime_debut", "datetime_fin", "datetime_mediane"}
REF_COMP_FIELDS = { REF_COMP_FIELDS = {
"dept_id",
"annexe", "annexe",
"specialite", "competences",
"specialite_long", "dept_id",
"type_structure", "parcours",
"type_departement",
"type_titre",
"version_orebut",
"scodoc_date_loaded", "scodoc_date_loaded",
"scodoc_orig_filename", "scodoc_orig_filename",
"competences", "specialite_long",
"parcours", "specialite",
"type_departement",
"type_structure",
"type_titre",
"version_orebut",
} }
ABSENCES_FIELDS = { ABSENCES_FIELDS = {
"jour", "begin",
"matin", "description",
"end",
"estabs", "estabs",
"estjust", "estjust",
"description", "jour",
"begin", "matin",
"end",
} }
ABSENCES_GROUP_ETAT_FIELDS = {"etudid", "list_abs"} ABSENCES_GROUP_ETAT_FIELDS = {"etudid", "list_abs"}
FORMSEMESTRE_ETUD_FIELDS = { FORMSEMESTRE_ETUD_FIELDS = {
"id",
"code_nip",
"code_ine",
"nom",
"nom_usuel",
"prenom",
"civilite", "civilite",
"code_ine",
"code_nip",
"groups", "groups",
"id",
"nom_usuel",
"nom",
"prenom",
} }
FORMSEMESTRE_ETUS_GROUPS_FIELDS = { FORMSEMESTRE_ETUS_GROUPS_FIELDS = {
"partition_id",
"id",
"formsemestre_id",
"partition_name",
"numero",
"bul_show_rank", "bul_show_rank",
"show_in_lists", "formsemestre_id",
"group_id", "group_id",
"group_name", "group_name",
"id",
"numero",
"partition_id",
"partition_name",
"show_in_lists",
} }
EVALUATIONS_FIELDS = { EVALUATIONS_FIELDS = {
@ -573,107 +577,107 @@ EVALUATIONS_FIELDS = {
} }
NOTES_FIELDS = { NOTES_FIELDS = {
"etudid",
"evaluation_id",
"value",
"comment", "comment",
"date", "date",
"etudid",
"evaluation_id",
"uid", "uid",
"value",
} }
PARTITIONS_FIELDS = { PARTITIONS_FIELDS = {
"id",
"formsemestre_id",
"partition_name",
"numero",
"bul_show_rank", "bul_show_rank",
"formsemestre_id",
"id",
"numero",
"partition_name",
"show_in_lists", "show_in_lists",
} }
PARTITION_GROUPS_ETUD_FIELDS = { PARTITION_GROUPS_ETUD_FIELDS = {
"id", "civilite",
"code_ine",
"code_nip",
"dept_id", "dept_id",
"id",
"nom_usuel",
"nom", "nom",
"prenom", "prenom",
"nom_usuel",
"civilite",
"code_nip",
"code_ine",
} }
FORMSEMESTRE_BULLETINS_FIELDS = { FORMSEMESTRE_BULLETINS_FIELDS = {
"version",
"type",
"date", "date",
"publie", "etat_inscription",
"etudiant", "etudiant",
"formation", "formation",
"formsemestre_id", "formsemestre_id",
"etat_inscription",
"options", "options",
"publie",
"ressources", "ressources",
"saes", "saes",
"ues",
"semestre", "semestre",
"type",
"ues",
"version",
} }
FORMSEMESTRE_BULLETINS_ETU_FIELDS = { FORMSEMESTRE_BULLETINS_ETU_FIELDS = {
"boursier",
"civilite", "civilite",
"code_ine", "code_ine",
"code_nip", "code_nip",
"codepostaldomicile",
"date_naissance", "date_naissance",
"dept_id",
"dept_acronym", "dept_acronym",
"dept_id",
"dept_naissance",
"description",
"domicile",
"email", "email",
"emailperso", "emailperso",
"etudid", "etudid",
"nom", "fax",
"prenom",
"nomprenom",
"lieu_naissance",
"dept_naissance",
"nationalite",
"boursier",
"fiche_url", "fiche_url",
"photo_url",
"id", "id",
"codepostaldomicile", "lieu_naissance",
"nationalite",
"nom",
"nomprenom",
"paysdomicile", "paysdomicile",
"photo_url",
"prenom",
"telephone",
"telephonemobile", "telephonemobile",
"typeadresse", "typeadresse",
"domicile",
"villedomicile", "villedomicile",
"telephone",
"fax",
"description",
} }
FORMSEMESTRE_BULLETINS_FORMATION_FIELDS = { FORMSEMESTRE_BULLETINS_FORMATION_FIELDS = {
"id",
"acronyme", "acronyme",
"id",
"titre_officiel", "titre_officiel",
"titre", "titre",
} }
FORMSEMESTRE_BULLETINS_OPT_FIELDS = { FORMSEMESTRE_BULLETINS_OPT_FIELDS = {
"show_abs",
"show_abs_modules", "show_abs_modules",
"show_ects", "show_abs",
"show_codemodules", "show_codemodules",
"show_coef",
"show_date_inscr",
"show_ects",
"show_matieres", "show_matieres",
"show_rangs", "show_minmax_eval",
"show_ue_rangs", "show_minmax_mod",
"show_minmax",
"show_mod_rangs", "show_mod_rangs",
"show_moypromo", "show_moypromo",
"show_minmax", "show_rangs",
"show_minmax_mod",
"show_minmax_eval",
"show_coef",
"show_ue_cap_details",
"show_ue_cap_current",
"show_temporary", "show_temporary",
"temporary_txt", "show_ue_cap_current",
"show_ue_cap_details",
"show_ue_rangs",
"show_uevalid", "show_uevalid",
"show_date_inscr", "temporary_txt",
} }

View File

@ -12,7 +12,8 @@ Usage: pytest tests/scenarios/test_scenario1_formation.py
# code écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en août 2021 # code écrit par Fares Amer, mai 2021 et porté sur ScoDoc 8 en août 2021
from tests.unit import sco_fake_gen from tests.unit import sco_fake_gen
from app.formations import edit_module, formation_io from app.formations import formation_io
from app.models import Formation
@pytest.mark.skip # test obsolete @pytest.mark.skip # test obsolete
@ -52,11 +53,11 @@ def run_scenario1():
] ]
# --- Implémentation des modules # --- Implémentation des modules
modules = edit_module.module_list({"formation_id": formation_id}) formation = Formation.get_formation(formation_id)
mods_imp = [] mods_imp = []
for mod in modules: for mod in formation.modules:
mi = G.create_moduleimpl( mi = G.create_moduleimpl(
module_id=mod["module_id"], module_id=mod.id,
formsemestre_id=sems[mod["semestre_id"] - 1]["formsemestre_id"], formsemestre_id=sems[mod.semestre_id - 1]["formsemestre_id"],
) )
mods_imp.append(mi) mods_imp.append(mi)

View File

@ -16,7 +16,7 @@ import typing
from app import db, log from app import db, log
from app.auth.models import User from app.auth.models import User
from app.formations import edit_matiere, edit_module, edit_ue from app.formations import edit_ue
from app.models import ( from app.models import (
Departement, Departement,
Evaluation, Evaluation,
@ -24,13 +24,13 @@ from app.models import (
FormationModalite, FormationModalite,
Identite, Identite,
Matiere, Matiere,
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
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_moduleimpl
from app.scodoc import sco_saisie_notes from app.scodoc import sco_saisie_notes
from app.scodoc import sco_synchro_etuds from app.scodoc import sco_synchro_etuds
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
@ -228,11 +228,8 @@ class ScoFake(object):
matiere = db.session.get(Matiere, matiere_id) matiere = db.session.get(Matiere, matiere_id)
ue_id = matiere.ue.id ue_id = matiere.ue.id
formation_id = matiere.ue.formation.id formation_id = matiere.ue.formation.id
oid = edit_module.do_module_create(locals()) module = Module.create_from_dict(locals(), news=True, inval_cache=True)
oids = edit_module.module_list(args={"module_id": oid}) return module.id
if not oids:
raise ScoValueError(f"module not created ! (oid={oid})")
return oid
@logging_meth @logging_meth
def create_formsemestre( def create_formsemestre(
@ -276,11 +273,8 @@ class ScoFake(object):
) -> int: ) -> int:
if not responsable_id: if not responsable_id:
responsable_id = self.default_user.id responsable_id = self.default_user.id
oid = sco_moduleimpl.do_moduleimpl_create(locals()) modimpl = ModuleImpl.create_from_dict(locals())
oids = sco_moduleimpl.moduleimpl_list(moduleimpl_id=oid) # API inconsistency return modimpl.id
if not oids:
raise ScoValueError("moduleimpl not created !")
return oid
@logging_meth @logging_meth
def inscrit_etudiant(self, formsemestre_id: int, etud: dict): def inscrit_etudiant(self, formsemestre_id: int, etud: dict):

View File

@ -34,8 +34,7 @@
# - moduleimpl_list # - moduleimpl_list
# - do_module_impl_with_module_list # - do_module_impl_with_module_list
# - do_formsemestre_delete # - do_formsemestre_delete
# - module_list # - Module.delete
# - do_module_delete
# - ue_list # - ue_list
# - do_ue_delete # - do_ue_delete
# - do_formation_delete # - do_formation_delete
@ -52,7 +51,7 @@ from app.formations import (
edit_ue, edit_ue,
formation_io, formation_io,
) )
from app.models import Formation, Matiere, ModuleImpl from app.models import Formation, Matiere, Module, ModuleImpl
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_exceptions from app.scodoc import sco_exceptions
from app.scodoc import sco_formsemestre_edit from app.scodoc import sco_formsemestre_edit
@ -259,7 +258,7 @@ def test_formations(test_client):
formsemestre_id=sem2["formsemestre_id"] formsemestre_id=sem2["formsemestre_id"]
) )
li_module = edit_module.module_list() li_module = Module.query.all()
assert len(li_module) == 4 assert len(li_module) == 4
# Suppression impossible car utilisé dans le semestre formsemestre_idt: # Suppression impossible car utilisé dans le semestre formsemestre_idt:
module3 = db.session.get(ModuleImpl, mi3).module module3 = db.session.get(ModuleImpl, mi3).module
@ -268,13 +267,14 @@ def test_formations(test_client):
sco_formsemestre_edit.do_formsemestre_delete(formsemestre_idt) sco_formsemestre_edit.do_formsemestre_delete(formsemestre_idt)
li_module2_before = edit_module.module_list() li_module2_before = Module.query.all()
edit_module.do_module_delete(module3.id) module3.delete()
edit_module.do_module_delete(module_id_t) module_t = db.session.get(Module, module_id_t)
module_t.delete()
# deuxieme methode de supression d'un module # deuxieme methode de supression d'un module
li_module2_after = edit_module.module_list() li_module2_after = Module.query.all()
assert ( assert (
len(li_module2_after) == len(li_module2_before) - 2 len(li_module2_after) == len(li_module2_before) - 2
@ -340,14 +340,14 @@ def test_import_formation(test_client, filename="formation-exemple-1.xml"):
) )
] ]
# et les modules # et les modules
modules = edit_module.module_list({"formation_id": formation_id}) formation = Formation.get_formation(formation_id)
for mod in modules: for mod in formation.modules:
moduleimpl_id = G.create_moduleimpl( moduleimpl_id = G.create_moduleimpl(
module_id=mod["module_id"], module_id=mod.id,
formsemestre_id=formsemestre_ids[mod["semestre_id"] - 1], formsemestre_id=formsemestre_ids[mod.semestre_id - 1],
) )
mi = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] mi = sco_moduleimpl.moduleimpl_list(moduleimpl_id=moduleimpl_id)[0]
assert mi["module_id"] == mod["module_id"] assert mi["module_id"] == mod.id
# --- Export formation en XML # --- Export formation en XML
doc1 = formation_io.formation_export(formation_id, fmt="xml").get_data(as_text=True) doc1 = formation_io.formation_export(formation_id, fmt="xml").get_data(as_text=True)