This commit is contained in:
Emmanuel Viennet 2024-10-14 16:46:05 +02:00
commit c7c6b49373
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_login import current_user
from app import db, log
from app import db
from app import models
from app.formations import edit_matiere
from app.models import APO_CODE_STR_LEN
from app.models import Formation, Matiere, Module, UniteEns
from app.models import FormSemestre, ModuleImpl
from app.models import ScolarNews
from app.models.but_refcomp import ApcAppCritique, ApcParcours
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
from app.scodoc.TrivialFormulator import TrivialFormulator
from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_exceptions import (
ScoValueError,
ScoLockedFormError,
ScoGenError,
ScoNonEmptyFormationObject,
)
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):
"""Delete a module"""
"""Formulaire suppression d'un module"""
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(
"Module",
msg=module.titre,
@ -221,7 +81,7 @@ def module_delete(module_id=None):
request.base_url,
scu.get_request_args(),
(("module_id", {"input_type": "hidden"}),),
initvalues=mod,
initvalues=module.to_dict(),
submitlabel="Confirmer la suppression",
cancelbutton="Annuler",
)
@ -231,37 +91,38 @@ def module_delete(module_id=None):
title="Suppression d'un module",
content="\n".join(H) + tf[1],
)
elif tf[0] == -1:
return flask.redirect(dest_url)
else:
do_module_delete(module_id)
if tf[0] == -1: # cancel
return flask.redirect(dest_url)
module.delete()
return flask.redirect(dest_url)
def do_module_edit(vals: dict) -> None:
"edit a module"
# check
mod = module_list({"module_id": vals["module_id"]})[0]
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]
module = Module.get_instance(vals["module_id"])
# edit
cnx = ndb.GetDBConnexion()
_moduleEditor.edit(cnx, vals)
db.session.get(Formation, mod["formation_id"]).invalidate_cached_sems()
modif = module.from_dict(vals)
if modif:
module.formation.invalidate_cached_sems()
def check_module_code_unicity(code, field, formation_id, module_id=None):
"true si code module unique dans la formation"
modules = module_list(args={"code": code, "formation_id": formation_id})
if module_id: # edition: supprime le module en cours
modules = [m for m in modules if m["module_id"] != module_id]
return len(modules) == 0
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 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
de cette UE (si elle n'existe pas, la crée).
"""
from app.scodoc import sco_tag_module
# --- Détermination de la formation
orig_semestre_idx = semestre_id
ue = None
if create:
if matiere_id:
matiere = Matiere.query.get_or_404(matiere_id)
matiere = Matiere.get_instance(matiere_id)
ue = matiere.ue
formation = ue.formation
orig_semestre_idx = ue.semestre_idx if semestre_id is None else semestre_id
@ -300,7 +159,7 @@ def module_edit(
ue = module.ue
module_dict = module.to_dict()
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)
is_apc = parcours.APC_SAE # BUT
@ -326,17 +185,14 @@ def module_edit(
if (module and module.matiere and (module.matiere.id == mat.id))
or (mat.id == mat.ue.matieres.first().id)
]
mat_names = [
"S%s / %s" % (mat.ue.semestre_idx, mat.ue.acronyme) for mat in matieres
]
mat_names = [f"S{mat.ue.semestre_idx} / {mat.ue.acronyme}" for mat in matieres]
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
ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
module_dict["ue_matiere_id"] = "%s!%s" % (
module_dict["ue_id"],
module_dict["matiere_id"],
ue_mat_ids = [f"{mat.ue.id}!{mat.id}" for mat in matieres]
module_dict["ue_matiere_id"] = (
f"{module_dict['ue_id']}!{module_dict['matiere_id']}"
)
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,
R2.01, ou SAÉ 3.4. Doit être unique dans la formation)""",
"allow_null": False,
"validator": lambda val, field, formation_id=formation.id: check_module_code_unicity(
val, field, formation_id, module_id=module.id if module else None
"validator": lambda val, _, formation_id=formation.id: Module.check_module_code_unicity(
val, formation_id, module_id=module.id if module else None
),
},
),
@ -602,7 +458,8 @@ def module_edit(
"title": "UE de rattachement",
"explanation": "utilisée notamment pour les malus",
"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
],
"allowed_values": [u.id for u in ues],
@ -631,7 +488,8 @@ def module_edit(
"input_type": "menu",
"type": "int",
"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],
"allowed_values": semestres_indices,
"enabled": unlocked,
@ -846,8 +704,7 @@ def module_edit(
tf[2]["matiere_id"] = matiere.id
tf[2]["semestre_id"] = ue.semestre_idx
module_id = do_module_create(tf[2])
module = db.session.get(Module, module_id)
module = Module.create_from_dict(tf[2], news=True)
else: # EDITION MODULE
# l'UE de rattachement peut changer
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.commit()
module.formation.invalidate_cached_sems()
return flask.redirect(
url_for(
"notes.ue_table",
@ -904,28 +762,36 @@ def module_edit(
def module_table(formation_id):
"""Liste des modules de la formation
(XXX inutile ou a revoir)
(affichage debug)
"""
if not formation_id:
raise ScoValueError("invalid formation !")
formation: Formation = Formation.query.get_or_404(formation_id)
formation = Formation.get_formation(formation_id)
editable = current_user.has_permission(Permission.EditFormation)
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">
""",
]
editable = current_user.has_permission(Permission.EditFormation)
for module_dict in module_list(args={"formation_id": formation_id}):
H.append('<li class="notes_module_list">%s' % module_dict)
for module in formation.modules:
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:
H.append(
'<a href="module_edit?module_id=%(module_id)s">modifier</a>'
% module_dict
)
H.append(
'<a href="module_delete?module_id=%(module_id)s">supprimer</a>'
% module_dict
f"""
<a class="stdlink" href="{
url_for('notes.module_edit', scodoc_dept=g.scodoc_dept, module_id=module.id)
}">modifier</a>
<a class="stdlink" href="{
url_for('notes.module_delete', scodoc_dept=g.scodoc_dept, module_id=module.id)
}">supprimer</a>
"""
)
H.append("</li>")
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(
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)
for ue in ues:
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
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
# le semestre ? ou affecter le malus au semestre 1 ???
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:
semestre_id = ue.semestre_idx

View File

@ -37,7 +37,6 @@ from flask_login import current_user
from app import db, log
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 (
Formation,
@ -66,7 +65,6 @@ from app.scodoc import codes_cursus
from app.scodoc import sco_edit_apc
from app.scodoc import sco_groups
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_tag_module
_ueEditor = ndb.EditableTable(
"notes_ue",
@ -375,7 +373,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"type": "float",
"min_value": 0,
"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,
"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",
"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,
},
),
@ -445,7 +445,8 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
"input_type": "boolcheckbox",
"title": "UE 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",
"default": True,
"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
if cursus.UE_IS_MODULE:
# dans ce mode, crée un (unique) module dans l'UE:
_ = edit_module.do_module_create(
_ = Module.create_from_dict(
{
"titre": tf[2]["titre"],
"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"],
},
)
db.session.commit()
ue = db.session.get(UniteEns, ue_id)
flash(f"UE créée (code {ue.ue_code})")
else:
@ -608,9 +611,10 @@ def _add_ue_semestre_id(ues: list[dict], is_apc):
ue["semestre_id"] = codes_cursus.UE_SEM_DEFAULT
else:
# était le comportement ScoDoc7
modules = edit_module.module_list(args={"ue_id": ue["ue_id"]})
if modules:
ue["semestre_id"] = modules[0]["semestre_id"]
ue = UniteEns.get_ue(ue["ue_id"])
module = ue.modules.first()
if module:
ue["semestre_id"] = module.semestre_id
else:
ue["semestre_id"] = codes_cursus.UE_SEM_DEFAULT
@ -1323,7 +1327,6 @@ def _ue_table_modules(
arrow_none,
delete_icon,
delete_disabled_icon,
unit_name="matière",
add_suppress_link=True, # lien "supprimer cette matière"
empty_list_msg="Aucun élément dans cette matière",
create_element_msg="créer un module",
@ -1361,7 +1364,6 @@ def _ue_table_modules(
H.append("</span>")
mod_editable = editable
# and not edit_module.module_is_locked(Mod['module_id'])
if mod_editable:
H.append(
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
from app import db
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 ScolarNews
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 codes_cursus
from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module
from app.scodoc import sco_xml
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
@ -123,11 +122,11 @@ def formation_export_dict(
del mat_d["id"]
del mat_d["matiere_id"]
del mat_d["ue_id"]
mods = edit_module.module_list({"matiere_id": matiere_id})
mods.sort(key=lambda m: (m["numero"] or 0, m["code"]))
mat_d["module"] = mods
for mod_d in mods:
module: Module = db.session.get(Module, mod_d["module_id"])
mat = db.session.get(Matiere, matiere_id)
mods = mat.modules.all()
mods.sort(key=lambda m: (m.numero, m.code))
mat_d["module"] = [mod.to_dict() for mod in mods]
for module, mod_d in zip(mods, mat_d["module"]):
if export_tags:
tags = [t.title for t in module.tags]
if tags:
@ -200,7 +199,8 @@ def formation_export(
if fmt is None:
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(
f_dict,
name="formation",
@ -290,14 +290,14 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F
try:
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:
raise ScoFormatError(
"""Ce document xml ne correspond pas à un programme exporté par ScoDoc.
(élément 'formation' inexistant par exemple)."""
) from exc
assert D[0] == "formation"
f_dict = D[1]
assert xml_dicts[0] == "formation"
f_dict = xml_dicts[1]
f_dict["dept_id"] = g.scodoc_dept_id
# Pour les clonages, on prend le refcomp_id donné:
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
with sco_cache.DeferredSemCacheManager():
# -- create UEs
for ue_info in D[2]:
for ue_info in xml_dicts[2]:
assert ue_info[0] == "ue"
ue_info[1]["formation_id"] = formation.id
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
if not "module_type" in mod_info[1]:
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:
modules_old2new[int(xml_module_id)] = mod_id
modules_old2new[int(xml_module_id)] = module.id
if len(mod_info) > 2:
module: Module = db.session.get(Module, mod_id)
tag_names = []
ue_coef_dict = {}
for child in mod_info[2]:

View File

@ -66,7 +66,8 @@ class Formation(ScoDocModel):
def html(self) -> str:
"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
def get_formation(cls, formation_id: int | str, dept_id: int = None) -> "Formation":
@ -334,7 +335,7 @@ class Matiere(ScoDocModel):
return e
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.
"""
from app.models.formsemestre import FormSemestre
@ -361,8 +362,6 @@ class Matiere(ScoDocModel):
def delete(self):
"Delete matière. News, inval cache."
from app.models import ScolarNews
formation = self.ue.formation
log(f"matiere.delete: matiere_id={self.id}")
if not self.can_be_deleted():

View File

@ -7,14 +7,15 @@ from flask_login import current_user
from flask_sqlalchemy.query import Query
import app
from app import db
from app import db, log
from app.auth.models import User
from app.comp import df_cache
from app.models import APO_CODE_STR_LEN, ScoDocModel
from app.models.etudiants import Identite
from app.models.evaluations import Evaluation
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 import sco_utils as scu
@ -65,6 +66,30 @@ class ModuleImpl(ScoDocModel):
def __repr__(self):
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]:
"""Les codes Apogée (codés en base comme "VRT1,VRT2").
(si non renseigné, ceux du module)

View File

@ -2,7 +2,7 @@
"""
import http
from flask import current_app, g
from flask import current_app, g, url_for
from app import db, log
from app import models
@ -13,9 +13,14 @@ from app.models.but_refcomp import (
app_critiques_modules,
parcours_modules,
)
from app.models.events import ScolarNews
from app.scodoc import sco_utils as scu
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
@ -115,13 +120,39 @@ class Module(models.ScoDocModel):
# on ne peut pas affecter directement 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:
"""Update object's fields given in dict. Add to session but don't commit.
True if modification.
- can't change ue nor formation
- can change matiere_id, iff new matiere in same ue
- 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
new_matiere_id = args.get("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}
new_parcours = args.get("parcours", []) or []
if existing_parcours != set(new_parcours):
self._set_parcours_from_list(new_parcours)
self.set_parcours_from_list(new_parcours)
return True
return modified
@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.
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)
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
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.
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

View File

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

View File

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

View File

@ -34,7 +34,6 @@ import sqlalchemy as sa
from app import db
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 (
ApcValidationAnnee,
@ -939,7 +938,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
"formsemestre_id": formsemestre_id,
"responsable_id": tf[2][f"MI{module_id}"],
}
_ = sco_moduleimpl.do_moduleimpl_create(modargs)
_ = ModuleImpl.create_from_dict(modargs)
else:
# Modification du semestre:
# 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,
"responsable_id": tf[2]["MI" + str(module_id)],
}
moduleimpl_id = sco_moduleimpl.do_moduleimpl_create(modargs)
mod = edit_module.module_list({"module_id": module_id})[0]
msg += [
"création de %s (%s)" % (mod["code"] or "?", mod["titre"] or "?")
]
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
log(
'inscription module: %s = "%s"'
% ("%s!group_id" % module_id, tf[2]["%s!group_id" % module_id])
)
group_id = tf[2]["%s!group_id" % module_id]
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, moduleimpl_id, etudids)
% (module_id, modimpl.id, etudids)
)
sco_moduleimpl.do_moduleimpl_inscrit_etuds(
moduleimpl_id,
modimpl.id,
formsemestre.id,
etudids,
)
@ -1002,7 +997,7 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
else:
log(
"inscription module:module_id=%s,moduleimpl_id=%s: aucun etudiant inscrit"
% (module_id, moduleimpl_id)
% (module_id, modimpl.id)
)
#
ok, diag = formsemestre_delete_moduleimpls(
@ -1022,7 +1017,6 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
sco_moduleimpl.do_moduleimpl_edit(
modargs, formsemestre_id=formsemestre.id
)
mod = edit_module.module_list({"module_id": module_id})[0]
# --- Association des parcours
if formsemestre is None:
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.res_common import ResultatsSemestre
from app.comp.res_compat import NotesTableCompat
from app.formations import formation_io
from app.models import (
Evaluation,
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):
"delete moduleimpl (desinscrit tous les etudiants)"
cnx = ndb.GetDBConnexion()

View File

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

View File

@ -56,12 +56,12 @@ Solution proposée (nov 2014):
import flask
from flask import flash, g, request, render_template, url_for
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 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 sco_moduleimpl
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}
)
module_id = edit_module.do_module_create(
module = Module.create_from_dict(
{
"titre": "UE extérieure",
"code": acronyme,
@ -125,11 +125,13 @@ def external_ue_create(
"semestre_id": formsemestre.semestre_id,
"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,
# affecte le 1er responsable du semestre comme resp. du module
"responsable_id": (
@ -139,7 +141,6 @@ def external_ue_create(
),
},
)
modimpl = db.session.get(ModuleImpl, moduleimpl_id)
assert modimpl
return modimpl

View File

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

View File

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

View File

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

View File

@ -117,7 +117,7 @@ def GET(
print("reply", reply.text)
raise APIError(
errmsg or f"""erreur get {url} !""",
reply.json(),
reply if reply.status_code == 404 else reply.json(),
status_code=reply.status_code,
)
if raw:
@ -220,7 +220,8 @@ def check_failure_get(path: str, headers: dict, err: str = None):
# ^ Renvoi un 404
except APIError as api_err:
if err is not None:
assert api_err.payload["message"] == err
if "message" in api_err.payload:
assert api_err.payload["message"] == err
else:
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_tp"], 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["numero"], int)
assert isinstance(module["code_apogee"], str)

View File

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

View File

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

View File

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