diff --git a/app/models/__init__.py b/app/models/__init__.py
index d29b6bf3b1..d259966ff2 100644
--- a/app/models/__init__.py
+++ b/app/models/__init__.py
@@ -1,14 +1,25 @@
# -*- coding: UTF-8 -*
"""Modèles base de données ScoDoc
-XXX version préliminaire ScoDoc8 #sco8 sans département
"""
+import sqlalchemy
+
CODE_STR_LEN = 16 # chaine pour les codes
SHORT_STR_LEN = 32 # courtes chaine, eg acronymes
APO_CODE_STR_LEN = 512 # nb de car max d'un code Apogée (il peut y en avoir plusieurs)
GROUPNAME_STR_LEN = 64
+convention = {
+ "ix": "ix_%(column_0_label)s",
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s",
+}
+
+metadata_obj = sqlalchemy.MetaData(naming_convention=convention)
+
from app.models.raw_sql_init import create_database_functions
from app.models.absences import Absence, AbsenceNotification, BilletAbsence
diff --git a/app/models/but_refcomp.py b/app/models/but_refcomp.py
index 6a9205fb35..3b28676514 100644
--- a/app/models/but_refcomp.py
+++ b/app/models/but_refcomp.py
@@ -321,6 +321,21 @@ ApcAppCritiqueModules = db.Table(
)
+parcours_modules = db.Table(
+ "parcours_modules",
+ db.Column(
+ "parcours_id", db.Integer, db.ForeignKey("apc_parcours.id"), primary_key=True
+ ),
+ db.Column(
+ "module_id",
+ db.Integer,
+ db.ForeignKey("notes_modules.id", ondelete="CASCADE"),
+ primary_key=True,
+ ),
+)
+"""Association parcours <-> modules (many-to-many)"""
+
+
class ApcParcours(db.Model, XMLModel):
id = db.Column(db.Integer, primary_key=True)
referentiel_id = db.Column(
@@ -335,6 +350,11 @@ class ApcParcours(db.Model, XMLModel):
lazy="dynamic",
cascade="all, delete-orphan",
)
+ # modules = db.relationship(
+ # "Module",
+ # secondary=parcours_modules,
+ # back_populates="parcours",
+ # )
def __repr__(self):
return f"<{self.__class__.__name__} {self.code}>"
diff --git a/app/models/modules.py b/app/models/modules.py
index 67ff3de0df..5ffac90435 100644
--- a/app/models/modules.py
+++ b/app/models/modules.py
@@ -3,6 +3,7 @@
from app import db
from app.models import APO_CODE_STR_LEN
+from app.models.but_refcomp import parcours_modules
from app.scodoc import sco_utils as scu
from app.scodoc.sco_codes_parcours import UE_SPORT
from app.scodoc.sco_utils import ModuleType
@@ -44,6 +45,14 @@ class Module(db.Model):
lazy=True,
backref=db.backref("modules", lazy=True),
)
+ # BUT
+ parcours = db.relationship(
+ "ApcParcours",
+ secondary=parcours_modules,
+ lazy="subquery",
+ # cascade="all, delete",
+ backref=db.backref("modules", lazy=True),
+ )
def __init__(self, **kwargs):
self.ue_coefs = []
diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py
index c53094ef53..11bb53af19 100644
--- a/app/scodoc/sco_edit_apc.py
+++ b/app/scodoc/sco_edit_apc.py
@@ -134,7 +134,10 @@ def html_edit_formation_apc(
tag_editable=tag_editable,
icons=icons,
scu=scu,
- ),
+ semestre_id=semestre_idx,
+ )
+ if ues_by_sem[semestre_idx].count() > 0
+ else "",
render_template(
"pn/form_mods.html",
formation=formation,
@@ -147,7 +150,10 @@ def html_edit_formation_apc(
tag_editable=tag_editable,
icons=icons,
scu=scu,
- ),
+ semestre_id=semestre_idx,
+ )
+ if ues_by_sem[semestre_idx].count() > 0
+ else "",
render_template(
"pn/form_mods.html",
formation=formation,
@@ -159,7 +165,10 @@ def html_edit_formation_apc(
tag_editable=tag_editable,
icons=icons,
scu=scu,
- ),
+ semestre_id=semestre_idx,
+ )
+ if ues_by_sem[semestre_idx].count() > 0
+ else """créer une UE pour pouvoir ajouter des modules""",
]
return "\n".join(H)
diff --git a/app/scodoc/sco_edit_module.py b/app/scodoc/sco_edit_module.py
index ece30a3455..3aa00452e9 100644
--- a/app/scodoc/sco_edit_module.py
+++ b/app/scodoc/sco_edit_module.py
@@ -33,12 +33,13 @@ from flask import url_for, render_template
from flask import g, request
from flask_login import current_user
-from app import log
+from app import db, log
from app import models
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 ApcParcours
import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu
@@ -121,6 +122,13 @@ def module_create(
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,
+ )
if matiere_id:
matiere = Matiere.query.get_or_404(matiere_id)
ue = matiere.ue
@@ -472,30 +480,56 @@ def check_module_code_unicity(code, field, formation_id, module_id=None):
return len(Mods) == 0
-def module_edit(module_id=None):
- """Edit a module"""
- from app.scodoc import sco_formations
+def module_edit(
+ module_id=None,
+ create=False,
+ matiere_id=None,
+ module_type=None,
+ semestre_id=None,
+ formation_id=None,
+):
+ """Formulaire édition ou création module.
+ Si create, création nouveau 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).
+ """
from app.scodoc import sco_tag_module
- if not module_id:
- raise ScoValueError("invalid module !")
- modules = module_list(args={"module_id": module_id})
- if not modules:
- raise ScoValueError("invalid module !")
- module = modules[0]
- a_module = models.Module.query.get(module_id)
- unlocked = not module_is_locked(module_id)
- formation_id = module["formation_id"]
- formation = sco_formations.formation_list(args={"formation_id": formation_id})[0]
- parcours = sco_codes_parcours.get_parcours_from_code(formation["type_parcours"])
+ # --- Détermination de la formation
+ orig_semestre_idx = None
+ if create:
+ if matiere_id:
+ matiere = Matiere.query.get_or_404(matiere_id)
+ ue = matiere.ue
+ formation = ue.formation
+ orig_semestre_idx = ue.semestre_idx if semestre_id is None else semestre_id
+ else:
+ formation = Formation.query.get_or_404(formation_id)
+ module = None
+ unlocked = True
+ else:
+ if not module_id:
+ raise ValueError("missing module_id !")
+ module = models.Module.query.get_or_404(module_id)
+ module_dict = module.to_dict()
+ formation = module.formation
+ unlocked = not module_is_locked(module_id)
+
+ parcours = sco_codes_parcours.get_parcours_from_code(formation.type_parcours)
is_apc = parcours.APC_SAE # BUT
- in_use = len(a_module.modimpls.all()) > 0 # il y a des modimpls
+ if not create:
+ orig_semestre_idx = module.ue.semestre_idx if is_apc else module.semestre_id
+ if orig_semestre_idx is None:
+ orig_semestre_idx = 1
+ # il y a-t-il des modimpls ?
+ in_use = (module is not None) and (len(module.modimpls.all()) > 0)
matieres = Matiere.query.filter(
- Matiere.ue_id == UniteEns.id, UniteEns.formation_id == formation_id
+ Matiere.ue_id == UniteEns.id, UniteEns.formation_id == formation.id
).order_by(UniteEns.semestre_idx, UniteEns.numero, Matiere.numero)
if in_use:
# restreint aux matières du même semestre
- matieres = matieres.filter(UniteEns.semestre_idx == a_module.ue.semestre_idx)
+ matieres = matieres.filter(UniteEns.semestre_idx == module.ue.semestre_idx)
if is_apc:
# ne conserve que la 1ere matière de chaque UE,
@@ -503,7 +537,8 @@ def module_edit(module_id=None):
matieres = [
mat
for mat in matieres
- if a_module.matiere.id == mat.id or mat.id == mat.ue.matieres.first().id
+ if ((module is not None) 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
@@ -511,14 +546,43 @@ def module_edit(module_id=None):
else:
mat_names = ["%s / %s" % (mat.ue.acronyme, mat.titre or "") for mat in matieres]
- ue_mat_ids = ["%s!%s" % (mat.ue.id, mat.id) for mat in matieres]
- module["ue_matiere_id"] = "%s!%s" % (module["ue_id"], module["matiere_id"])
+ 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"],
+ )
semestres_indices = list(range(1, parcours.NB_SEM + 1))
+ # Toutes les UE de la formation (tout parcours):
+ ues = formation.ues.order_by(
+ UniteEns.semestre_idx, UniteEns.numero, UniteEns.acronyme
+ ).all()
+
+ # --- Titre de la page
+ if create:
+ if is_apc and module_type is not None:
+ object_name = scu.MODULE_TYPE_NAMES[module_type]
+ else:
+ object_name = "Module"
+ page_title = f"Création {object_name}"
+ if matiere_id:
+ title = f"""Création {object_name} dans la matière
+ {matiere.titre},
+ (UE {ue.acronyme}), semestre {ue.semestre_idx}
+ """
+ else:
+ title = f"""Création {object_name} dans la formation
+ {formation.acronyme}"""
+ else:
+ page_title = "Modification du module {module.code or module.titre or ''}"
+ title = f"""Modification du module {module.code or ''} {module.titre or ''}
+ (formation {formation.acronyme}, version {formation.version})
+ """
H = [
html_sco_header.sco_header(
- page_title=f"Modification du module {a_module.code or a_module.titre or ''}",
+ page_title=page_title,
cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css"],
javascripts=[
"libjs/jQuery-tagEditor/jquery.tag-editor.min.js",
@@ -526,17 +590,19 @@ def module_edit(module_id=None):
"js/module_tag_editor.js",
],
),
- f"""
Modification du module {a_module.code or ''} {a_module.titre or ''}""",
- """ (formation %(acronyme)s, version %(version)s)
""" % formation,
+ f"""{title}
""",
render_template(
"scodoc/help/modules.html",
is_apc=is_apc,
+ semestre_id=semestre_id,
formsemestres=FormSemestre.query.filter(
ModuleImpl.formsemestre_id == FormSemestre.id,
ModuleImpl.module_id == module_id,
)
.order_by(FormSemestre.date_debut)
- .all(),
+ .all()
+ if not create
+ else None,
),
]
if not unlocked:
@@ -547,28 +613,55 @@ def module_edit(module_id=None):
module_types = scu.ModuleType # tous les types
else:
# ne propose pas SAE et Ressources, sauf si déjà de ce type...
- module_types = (
- set(scu.ModuleType) - {scu.ModuleType.RESSOURCE, scu.ModuleType.SAE}
- ) | {
- scu.ModuleType(a_module.module_type)
- if a_module.module_type
- else scu.ModuleType.STANDARD
+ module_types = set(scu.ModuleType) - {
+ scu.ModuleType.RESSOURCE,
+ scu.ModuleType.SAE,
}
+ if module:
+ module_types |= {
+ scu.ModuleType(module.module_type)
+ if module.module_type
+ else scu.ModuleType.STANDARD
+ }
+ # Numéro du module
+ # cherche le numero adéquat (pour placer le module en fin de liste)
+ if module:
+ default_num = module.numero
+ else:
+ modules = formation.modules.all()
+ if modules:
+ default_num = max([m.numero or 0 for m in modules]) + 10
+ else:
+ default_num = 10
descr = [
(
"code",
{
"size": 10,
- "explanation": "code du module (issu du programme, exemple M1203 ou R2.01. Doit être unique dans la formation)",
+ "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
+ "validator": lambda val, field, formation_id=formation.id: check_module_code_unicity(
+ val, field, formation_id, module_id=module.id if module else None
),
},
),
- ("titre", {"size": 30, "explanation": "nom du module"}),
- ("abbrev", {"size": 20, "explanation": "nom abrégé (pour bulletins)"}),
+ (
+ "titre",
+ {
+ "size": 30,
+ "explanation": """nom du module. Exemple:
+ Introduction à la démarche ergonomique""",
+ },
+ ),
+ (
+ "abbrev",
+ {
+ "size": 20,
+ "explanation": """nom abrégé (pour bulletins).
+ Exemple: Intro. à l'ergonomie""",
+ },
+ ),
(
"module_type",
{
@@ -583,50 +676,63 @@ def module_edit(module_id=None):
(
"heures_cours",
{
- "title": "Heures CM :",
+ "title": "Heures cours :",
"size": 4,
"type": "float",
- "explanation": "nombre d'heures de cours",
+ "explanation": "nombre d'heures de cours (optionnel)",
},
),
(
"heures_td",
{
- "title": "Heures TD :",
+ "title": "Heures de TD :",
"size": 4,
"type": "float",
- "explanation": "nombre d'heures de Travaux Dirigés",
+ "explanation": "nombre d'heures de Travaux Dirigés (optionnel)",
},
),
(
"heures_tp",
{
- "title": "Heures TP :",
+ "title": "Heures de TP :",
"size": 4,
"type": "float",
- "explanation": "nombre d'heures de Travaux Pratiques",
+ "explanation": "nombre d'heures de Travaux Pratiques (optionnel)",
},
),
]
if is_apc:
- coefs_lst = a_module.ue_coefs_list()
- if coefs_lst:
- coefs_descr_txt = ", ".join(
- [f"{ue.acronyme}: {c}" for (ue, c) in coefs_lst]
- )
+ if module:
+ coefs_lst = module.ue_coefs_list()
+ if coefs_lst:
+ coefs_descr_txt = ", ".join(
+ [f"{ue.acronyme}: {c}" for (ue, c) in coefs_lst]
+ )
+ else:
+ coefs_descr_txt = """non définis"""
+ descr += [
+ (
+ "ue_coefs",
+ {
+ "readonly": True,
+ "title": "Coefficients vers les UE ",
+ "default": coefs_descr_txt,
+ "explanation": "
(passer par la page d'édition de la formation pour modifier les coefficients)",
+ },
+ )
+ ]
else:
- coefs_descr_txt = """non définis"""
- descr += [
- (
- "ue_coefs",
- {
- "readonly": True,
- "title": "Coefficients vers les UE ",
- "default": coefs_descr_txt,
- "explanation": "
(passer par la page d'édition de la formation pour modifier les coefficients)",
- },
- )
- ]
+ descr += [
+ (
+ "sep_ue_coefs",
+ {
+ "input_type": "separator",
+ "title": """
+ (les coefficients vers les UE se fixent sur la page dédiée)
+
""",
+ },
+ ),
+ ]
else: # Module classique avec coef scalaire:
descr += [
(
@@ -641,30 +747,64 @@ def module_edit(module_id=None):
),
]
descr += [
- ("formation_id", {"input_type": "hidden"}),
- ("ue_id", {"input_type": "hidden"}),
- ("module_id", {"input_type": "hidden"}),
(
- "ue_matiere_id",
+ "formation_id",
{
- "input_type": "menu",
- "title": "Rattachement :" if is_apc else "Matière :",
- "explanation": (
- "UE de rattachement"
- + (
- " module utilisé, ne peut pas être changé de semestre"
- if in_use
- else ""
- )
- )
- if is_apc
- else "un module appartient à une seule matière.",
- "labels": mat_names,
- "allowed_values": ue_mat_ids,
- "enabled": unlocked,
+ "input_type": "hidden",
+ "default": formation.id,
},
),
]
+ if module:
+ descr += [
+ ("ue_id", {"input_type": "hidden"}),
+ ("module_id", {"input_type": "hidden"}),
+ (
+ "ue_matiere_id",
+ {
+ "input_type": "menu",
+ "title": "Rattachement :" if is_apc else "Matière :",
+ "explanation": (
+ "UE de rattachement, utilisée notamment pour les malus"
+ + (
+ " (module utilisé, ne peut pas être changé de semestre)"
+ if in_use
+ else ""
+ )
+ )
+ if is_apc
+ else "un module appartient à une seule matière.",
+ "labels": mat_names,
+ "allowed_values": ue_mat_ids,
+ "enabled": unlocked,
+ },
+ ),
+ ]
+ else: # Création
+ if matiere_id:
+ descr += [
+ ("ue_id", {"default": ue.id, "input_type": "hidden"}),
+ ("matiere_id", {"default": matiere_id, "input_type": "hidden"}),
+ ]
+ else:
+ # choix de l'UE de rattachement
+ descr += [
+ (
+ "ue_id",
+ {
+ "input_type": "menu",
+ "type": "int",
+ "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}"
+ for u in ues
+ ],
+ "allowed_values": [u.id for u in ues],
+ },
+ ),
+ ]
+
if is_apc:
# le semestre du module est toujours celui de son UE
descr += [
@@ -710,17 +850,56 @@ def module_edit(module_id=None):
"numero",
{
"size": 2,
- "explanation": "numéro (1,2,3,4...) pour ordre d'affichage",
+ "explanation": "numéro (1, 2, 3, 4, ...) pour ordre d'affichage",
"type": "int",
+ "default": default_num,
},
),
]
+ # Choix des parcours
+ if is_apc:
+ ref_comp = formation.referentiel_competence
+ if ref_comp:
+ descr += [
+ (
+ "parcours",
+ {
+ "input_type": "checkbox",
+ "vertical": True,
+ "labels": [parcour.libelle for parcour in ref_comp.parcours],
+ "allowed_values": [
+ str(parcour.id) for parcour in ref_comp.parcours
+ ],
+ "explanation": "parcours dans lesquels est utilisé ce module.",
+ },
+ )
+ ]
+ if module:
+ module_dict["parcours"] = [
+ str(parcour.id) for parcour in module.parcours
+ ]
+ else:
+ descr += [
+ (
+ "parcours",
+ {
+ "input_type": "separator",
+ "title": f"""Pas de parcours:
+ associer un référentiel de compétence
+ """,
+ },
+ )
+ ]
# force module semestre_idx to its UE
- if a_module.ue.semestre_idx:
- module["semestre_id"] = a_module.ue.semestre_idx
- # Filet de sécurité si jamais l'UE n'a pas non plus de semestre:
- if not module["semestre_id"]:
- module["semestre_id"] = 1
+ if module:
+ if module.ue.semestre_idx is None:
+ # Filet de sécurité si jamais l'UE n'a pas non plus de semestre:
+ module_dict["semestre_id"] = 1
+ else:
+ module_dict["semestre_id"] = module.ue.semestre_idx
+
tf = TrivialFormulator(
request.base_url,
scu.get_request_args(),
@@ -728,8 +907,9 @@ def module_edit(module_id=None):
html_foot_markup="""
""".format(
module_id, ",".join(sco_tag_module.module_tag_list(module_id))
),
- initvalues=module,
- submitlabel="Modifier ce module",
+ initvalues=module_dict if module else {},
+ submitlabel="Modifier ce module" if module else "Créer ce module",
+ cancelbutton="Annuler",
)
#
if tf[0] == 0:
@@ -739,38 +919,66 @@ def module_edit(module_id=None):
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=formation_id,
- semestre_idx=module["semestre_id"],
+ formation_id=formation.id,
+ semestre_idx=orig_semestre_idx,
)
)
else:
- # l'UE de rattachement peut changer
- tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
- x, y = tf[2]["ue_matiere_id"].split("!")
- tf[2]["ue_id"] = int(x)
- tf[2]["matiere_id"] = int(y)
- old_ue_id = a_module.ue.id
- new_ue_id = tf[2]["ue_id"]
- if (old_ue_id != new_ue_id) and in_use:
- new_ue = UniteEns.query.get_or_404(new_ue_id)
- if new_ue.semestre_idx != a_module.ue.semestre_idx:
- # pas changer de semestre un module utilisé !
- raise ScoValueError(
- "Module utilisé: il ne peut pas être changé de semestre !"
- )
- # En APC, force le semestre égal à celui de l'UE
- if is_apc:
- selected_ue = UniteEns.query.get(tf[2]["ue_id"])
- if selected_ue is None:
- raise ValueError("UE invalide")
- tf[2]["semestre_id"] = selected_ue.semestre_idx
- # Check unicité code module dans la formation
- do_module_edit(tf[2])
+ if create:
+ if not matiere_id:
+ # formulaire avec choix UE de rattachement
+ ue = UniteEns.query.get(tf[2]["ue_id"])
+ if ue is None:
+ raise ValueError("UE invalide")
+ matiere = ue.matieres.first()
+ if matiere:
+ tf[2]["matiere_id"] = matiere.id
+ else:
+ matiere_id = sco_edit_matiere.do_matiere_create(
+ {"ue_id": ue.id, "titre": ue.titre, "numero": 1},
+ )
+ tf[2]["matiere_id"] = matiere_id
+
+ tf[2]["semestre_id"] = ue.semestre_idx
+ module_id = do_module_create(tf[2])
+ module = Module.query.get(module_id)
+ else: # EDITION MODULE
+ # l'UE de rattachement peut changer
+ tf[2]["ue_id"], tf[2]["matiere_id"] = tf[2]["ue_matiere_id"].split("!")
+ x, y = tf[2]["ue_matiere_id"].split("!")
+ tf[2]["ue_id"] = int(x)
+ tf[2]["matiere_id"] = int(y)
+ old_ue_id = module.ue.id
+ new_ue_id = tf[2]["ue_id"]
+ if (old_ue_id != new_ue_id) and in_use:
+ new_ue = UniteEns.query.get_or_404(new_ue_id)
+ if new_ue.semestre_idx != module.ue.semestre_idx:
+ # pas changer de semestre un module utilisé !
+ raise ScoValueError(
+ "Module utilisé: il ne peut pas être changé de semestre !"
+ )
+ # En APC, force le semestre égal à celui de l'UE
+ if is_apc:
+ selected_ue = UniteEns.query.get(tf[2]["ue_id"])
+ if selected_ue is None:
+ raise ValueError("UE invalide")
+ tf[2]["semestre_id"] = selected_ue.semestre_idx
+ # Check unicité code module dans la formation
+ # ??? TODO
+ #
+ do_module_edit(tf[2])
+ # Modifie les parcours
+ module.parcours = [
+ ApcParcours.query.get(int(parcour_id_str))
+ for parcour_id_str in tf[2]["parcours"]
+ ]
+ db.session.add(module)
+ db.session.commit()
return flask.redirect(
url_for(
"notes.ue_table",
scodoc_dept=g.scodoc_dept,
- formation_id=formation_id,
+ formation_id=formation.id,
semestre_idx=tf[2]["semestre_id"],
)
)
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index b400d881d9..066a49a521 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -255,7 +255,9 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
title = f"Modification de l'UE {ue.acronyme} {ue.titre}"
initvalues = ue_dict
submitlabel = "Modifier les valeurs"
- can_change_semestre_id = (ue.modules.count() == 0) or (ue.semestre_idx is None)
+ can_change_semestre_id = (
+ (ue.modules.count() == 0) or (ue.semestre_idx is None)
+ ) and ue.niveau_competence is None
else:
ue = None
title = "Création d'une UE"
@@ -287,7 +289,7 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
f"""
UE du semestre S{ue.semestre_idx}
"""
- if is_apc
+ if is_apc and ue
else "",
]
@@ -1015,9 +1017,6 @@ def _ue_table_ues(
}">transformer en UE ordinaire """
)
H.append("")
- breakpoint()
- if ue.niveau_competence is None:
- H.append(" pas de compétence associée ")
ue_editable = editable and not ue_is_locked(ue["ue_id"])
if ue_editable:
H.append(
diff --git a/app/templates/pn/form_mods.html b/app/templates/pn/form_mods.html
index 10927bc88b..4e3031ab28 100644
--- a/app/templates/pn/form_mods.html
+++ b/app/templates/pn/form_mods.html
@@ -84,13 +84,15 @@
url_for("notes.module_create",
scodoc_dept=g.scodoc_dept,
module_type=module_type|int,
- matiere_id=matiere_parent.id
+ matiere_id=matiere_parent.id,
+ semestre_id=semestre_id,
)}}"
{% else %}"{{
url_for("notes.module_create",
scodoc_dept=g.scodoc_dept,
module_type=module_type|int,
- formation_id=formation.id
+ formation_id=formation.id,
+ semestre_id=semestre_id,
)}}"
{% endif %}
>{{create_element_msg}}
diff --git a/migrations/versions/6002d7d366e5_assoc_ue_niveau.py b/migrations/versions/6002d7d366e5_assoc_ue_niveau.py
index 449ff06cc0..cf6c5932e2 100644
--- a/migrations/versions/6002d7d366e5_assoc_ue_niveau.py
+++ b/migrations/versions/6002d7d366e5_assoc_ue_niveau.py
@@ -22,13 +22,39 @@ def upgrade():
"notes_ue", sa.Column("niveau_competence_id", sa.Integer(), nullable=True)
)
op.create_foreign_key(
- None, "notes_ue", "apc_niveau", ["niveau_competence_id"], ["id"]
+ "notes_ue_niveau_competence_id_fkey",
+ "notes_ue",
+ "apc_niveau",
+ ["niveau_competence_id"],
+ ["id"],
+ )
+ op.create_table(
+ "parcours_modules",
+ sa.Column("parcours_id", sa.Integer(), nullable=False),
+ sa.Column("module_id", sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(
+ ["module_id"],
+ ["notes_modules.id"],
+ # nom ajouté manuellement:
+ name="parcours_modules_module_id_fkey",
+ ondelete="CASCADE",
+ ),
+ sa.ForeignKeyConstraint(
+ ["parcours_id"],
+ ["apc_parcours.id"],
+ # nom ajouté manuellement:
+ name="parcours_modules_parcours_id_fkey",
+ ),
+ sa.PrimaryKeyConstraint("parcours_id", "module_id"),
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
- op.drop_constraint(None, "notes_ue", type_="foreignkey")
+ op.drop_constraint(
+ "notes_ue_niveau_competence_id_fkey", "notes_ue", type_="foreignkey"
+ )
op.drop_column("notes_ue", "niveau_competence_id")
+ op.drop_table("parcours_modules")
# ### end Alembic commands ###
diff --git a/tests/unit/test_refcomp.py b/tests/unit/test_refcomp.py
index f9e92a37d6..2e56ed3c28 100644
--- a/tests/unit/test_refcomp.py
+++ b/tests/unit/test_refcomp.py
@@ -4,35 +4,57 @@ Utiliser par exemple comme:
pytest tests/unit/test_refcomp.py
"""
-import io
+
from flask import g
-import app
+
from app import db
from app import models
from app.but.import_refcomp import orebut_import_refcomp
+from app.models import UniteEns
from app.models.but_refcomp import (
ApcReferentielCompetences,
ApcCompetence,
ApcSituationPro,
+ ApcNiveau,
)
+from tests.unit import setup
+
+REF_RT_XML = open(
+ "ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
+).read()
+
def test_but_refcomp(test_client):
"""modèles ref. comp."""
- xml_data = open(
- "ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
- ).read()
dept_id = models.Departement.query.first().id
- ref = orebut_import_refcomp(xml_data, dept_id)
- assert ref.competences.count() == 13
- assert ref.competences[0].situations.count() == 3
- assert ref.competences[0].situations[0].libelle.startswith("Conception ")
+ ref_comp: ApcReferentielCompetences = orebut_import_refcomp(REF_RT_XML, dept_id)
+ assert ref_comp.competences.count() == 13
+ assert ref_comp.competences[0].situations.count() == 3
+ assert ref_comp.competences[0].situations[0].libelle.startswith("Conception ")
assert (
- ref.competences[-1].situations[-1].libelle
+ ref_comp.competences[-1].situations[-1].libelle
== "Administration des services multimédia"
)
# test cascades on delete
- db.session.delete(ref)
+ db.session.delete(ref_comp)
db.session.commit()
assert ApcCompetence.query.count() == 0
assert ApcSituationPro.query.count() == 0
+
+
+def test_but_assoc_ue_parcours(test_client):
+ """Association UE / Niveau compétence"""
+ dept_id = models.Departement.query.first().id
+ G, formation_id, (ue1_id, ue2_id, ue3_id), module_ids = setup.build_formation_test()
+ ref_comp: ApcReferentielCompetences = orebut_import_refcomp(REF_RT_XML, dept_id)
+ ue = UniteEns.query.get(ue1_id)
+ assert ue.niveau_competence is None
+ niveau = ApcNiveau.query.first()
+ ue.niveau_competence = niveau
+ db.session.add(ue)
+ db.session.commit()
+ ue = UniteEns.query.get(ue1_id)
+ assert ue.niveau_competence == niveau
+ assert len(niveau.ues) == 1
+ assert niveau.ues[0] == ue