Backend 'Matieres': utilise uniquement modèle.

This commit is contained in:
ilona 2024-10-11 14:06:02 +02:00
parent 056be2e152
commit 1dfba157c2
12 changed files with 300 additions and 357 deletions

View File

@ -25,16 +25,14 @@
# #
############################################################################## ##############################################################################
"""Ajout/Modification/Supression matieres """Ajout/Modification/Suppression matieres
(portage from DTML)
""" """
import flask import flask
from flask import g, render_template, request, url_for from flask import flash, g, render_template, request, url_for
from app import db, log from app import db, log
from app.models import Formation, Matiere, UniteEns, ScolarNews from app.models import Matiere, UniteEns
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, tf_error_message from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
@ -44,59 +42,9 @@ from app.scodoc.sco_exceptions import (
ScoNonEmptyFormationObject, ScoNonEmptyFormationObject,
) )
_matiereEditor = ndb.EditableTable(
"notes_matieres",
"matiere_id",
("matiere_id", "ue_id", "numero", "titre"),
sortkey="numero",
output_formators={"numero": ndb.int_null_is_zero},
)
def matiere_list(*args, **kw):
"list matieres"
cnx = ndb.GetDBConnexion()
return _matiereEditor.list(cnx, *args, **kw)
def do_matiere_edit(*args, **kw):
"edit a matiere"
from app.formations import edit_ue
cnx = ndb.GetDBConnexion()
# check
mat = matiere_list({"matiere_id": args[0]["matiere_id"]})[0]
if matiere_is_locked(mat["matiere_id"]):
raise ScoLockedFormError()
# edit
_matiereEditor.edit(cnx, *args, **kw)
formation_id = edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]["formation_id"]
db.session.get(Formation, formation_id).invalidate_cached_sems()
def do_matiere_create(args):
"create a matiere"
from app.formations import edit_ue
cnx = ndb.GetDBConnexion()
# check
ue = edit_ue.ue_list({"ue_id": args["ue_id"]})[0]
# create matiere
r = _matiereEditor.create(cnx, args)
# news
formation = db.session.get(Formation, ue["formation_id"])
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=ue["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
)
formation.invalidate_cached_sems()
return r
def matiere_create(ue_id=None): def matiere_create(ue_id=None):
"""Creation d'une matiere""" """Formulaire création d'une matiere"""
ue: UniteEns = UniteEns.query.get_or_404(ue_id) ue: UniteEns = UniteEns.query.get_or_404(ue_id)
default_numero = max([mat.numero for mat in ue.matieres] or [9]) + 1 default_numero = max([mat.numero for mat in ue.matieres] or [9]) + 1
H = [ H = [
@ -153,8 +101,8 @@ associé.
if tf[0] == -1: if tf[0] == -1:
return flask.redirect(dest_url) return flask.redirect(dest_url)
# check unicity # check unicity
mats = matiere_list(args={"ue_id": ue_id, "titre": tf[2]["titre"]}) nb_mats = Matiere.query.filter_by(ue_id=ue_id, titre=tf[2]["titre"]).count()
if mats: if nb_mats:
return render_template( return render_template(
"sco_page.j2", "sco_page.j2",
title="Création d'une matière", title="Création d'une matière",
@ -164,56 +112,14 @@ associé.
+ tf[1] + tf[1]
), ),
) )
_ = do_matiere_create(tf[2]) Matiere.create_from_dict(tf[2])
return flask.redirect(dest_url) return flask.redirect(dest_url)
def can_delete_matiere(matiere: Matiere) -> tuple[bool, str]:
"True si la matiere n'est pas utilisée dans des formsemestre"
locked = matiere_is_locked(matiere.id)
if locked:
return False
if any(m.modimpls.all() for m in matiere.modules):
return False
return True
def do_matiere_delete(oid):
"delete matiere and attached modules"
from app.formations import edit_module, edit_ue
cnx = ndb.GetDBConnexion()
# check
matiere = Matiere.query.get_or_404(oid)
mat = matiere_list({"matiere_id": oid})[0] # compat sco7
ue = edit_ue.ue_list({"ue_id": mat["ue_id"]})[0]
if not can_delete_matiere(matiere):
# il y a au moins un modimpl dans un module de cette matière
raise ScoNonEmptyFormationObject("Matière", matiere.titre)
log(f"do_matiere_delete: matiere_id={matiere.id}")
# delete all modules in this matiere
mods = edit_module.module_list({"matiere_id": matiere.id})
for mod in mods:
edit_module.do_module_delete(mod["module_id"])
_matiereEditor.delete(cnx, oid)
# news
formation = db.session.get(Formation, ue["formation_id"])
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=ue["formation_id"],
text=f"Modification de la formation {formation.acronyme}",
)
formation.invalidate_cached_sems()
def matiere_delete(matiere_id=None): def matiere_delete(matiere_id=None):
"""Delete matière""" """Form delete matière"""
from app.formations import edit_ue matiere = Matiere.get_instance(matiere_id)
if not matiere.can_be_deleted():
matiere = Matiere.query.get_or_404(matiere_id)
if not can_delete_matiere(matiere):
# il y a au moins un modimpl dans un module de cette matière # il y a au moins un modimpl dans un module de cette matière
raise ScoNonEmptyFormationObject( raise ScoNonEmptyFormationObject(
"Matière", "Matière",
@ -226,22 +132,20 @@ def matiere_delete(matiere_id=None):
), ),
) )
mat = matiere_list(args={"matiere_id": matiere_id})[0]
ue_dict = edit_ue.ue_list(args={"ue_id": mat["ue_id"]})[0]
H = [ H = [
"<h2>Suppression de la matière %(titre)s" % mat, f"""<h2>Suppression de la matière {matiere.titre}
" dans l'UE (%(acronyme)s))</h2>" % ue_dict, dans l'UE {matiere.ue.acronyme}</h2>""",
] ]
dest_url = url_for( dest_url = url_for(
"notes.ue_table", "notes.ue_table",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formation_id=str(ue_dict["formation_id"]), formation_id=matiere.ue.formation_id,
) )
tf = TrivialFormulator( tf = TrivialFormulator(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
(("matiere_id", {"input_type": "hidden"}),), (("matiere_id", {"input_type": "hidden"}),),
initvalues=mat, initvalues=matiere.to_dict(),
submitlabel="Confirmer la suppression", submitlabel="Confirmer la suppression",
cancelbutton="Annuler", cancelbutton="Annuler",
) )
@ -254,29 +158,23 @@ def matiere_delete(matiere_id=None):
if tf[0] == -1: if tf[0] == -1:
return flask.redirect(dest_url) return flask.redirect(dest_url)
do_matiere_delete(matiere_id) matiere.delete()
return flask.redirect(dest_url) return flask.redirect(dest_url)
def matiere_edit(matiere_id=None): def matiere_edit(matiere_id=None):
"""Edit matiere""" """Form edit matiere"""
from app.formations import edit_ue matiere: Matiere = Matiere.get_instance(matiere_id)
if matiere.is_locked():
F = matiere_list(args={"matiere_id": matiere_id}) raise ScoLockedFormError()
if not F: ue = matiere.ue
raise ScoValueError("Matière inexistante !") formation = ue.formation
F = F[0] ues = matiere.ue.formation.ues
ues = edit_ue.ue_list(args={"ue_id": F["ue_id"]}) ue_names = [f"{u.acronyme} ({u.titre or ''})" for u in ues]
if not ues: ue_ids = [u.id for u in ues]
raise ScoValueError("UE inexistante !")
ue = ues[0]
formation: Formation = Formation.query.get_or_404(ue["formation_id"])
ues = edit_ue.ue_list(args={"formation_id": ue["formation_id"]})
ue_names = ["%(acronyme)s (%(titre)s)" % u for u in ues]
ue_ids = [u["ue_id"] for u in ues]
H = [ H = [
"""<h2>Modification de la matière %(titre)s""" % F, f"""<h2>Modification de la matière {matiere.titre or 'sans titre'}
f"""(formation ({formation.acronyme}, version {formation.version})</h2>""", (formation ({formation.acronyme}, version {formation.version})</h2>""",
] ]
help_msg = """<p class="help">Les matières sont des groupes de modules dans une UE help_msg = """<p class="help">Les matières sont des groupes de modules dans une UE
d'une formation donnée. Les matières servent surtout pour la d'une formation donnée. Les matières servent surtout pour la
@ -316,14 +214,14 @@ associé.
}, },
), ),
), ),
initvalues=F, initvalues=matiere.to_dict(),
submitlabel="Modifier les valeurs", submitlabel="Modifier les valeurs",
) )
dest_url = url_for( dest_url = url_for(
"notes.ue_table", "notes.ue_table",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
formation_id=str(ue["formation_id"]), formation_id=formation.id,
) )
if tf[0] == 0: if tf[0] == 0:
return render_template( return render_template(
@ -335,8 +233,8 @@ associé.
return flask.redirect(dest_url) return flask.redirect(dest_url)
else: else:
# check unicity # check unicity
mats = matiere_list(args={"ue_id": tf[2]["ue_id"], "titre": tf[2]["titre"]}) mats = Matiere.query.filter_by(ue_id=tf[2]["ue_id"], titre=tf[2]["titre"]).all()
if len(mats) > 1 or (len(mats) == 1 and mats[0]["matiere_id"] != matiere_id): if len(mats) > 1 or (len(mats) == 1 and mats[0].id != matiere_id):
return render_template( return render_template(
"sco_page.j2", "sco_page.j2",
title="Modification d'une matière", title="Modification d'une matière",
@ -347,32 +245,18 @@ associé.
), ),
) )
modif = False
# changement d'UE ? # changement d'UE ?
if tf[2]["ue_id"] != F["ue_id"]: if tf[2]["ue_id"] != ue.id:
log("attaching mat %s to new UE %s" % (matiere_id, tf[2]["ue_id"])) log(f"attaching mat {matiere_id} to new UE id={tf[2]['ue_id']}")
ndb.SimpleQuery( new_ue = UniteEns.get_ue(tf[2]["ue_id"])
"UPDATE notes_modules SET ue_id = %(ue_id)s WHERE matiere_id=%(matiere_id)s", if new_ue.formation_id != formation.id:
{"ue_id": tf[2]["ue_id"], "matiere_id": matiere_id}, raise ScoValueError("UE does not belong to the same formation")
) matiere.ue = new_ue
modif = True
do_matiere_edit(tf[2]) modif |= matiere.from_dict(tf[2])
if modif:
db.session.commit()
matiere.ue.formation.invalidate_cached_sems()
flash("Matière modifiée", "info")
return flask.redirect(dest_url) return flask.redirect(dest_url)
def matiere_is_locked(matiere_id):
"""True if matiere should not be modified
(contains modules used in a locked formsemestre)
"""
r = ndb.SimpleDictFetch(
"""SELECT ma.id
FROM notes_matieres ma, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi
WHERE ma.id = mod.matiere_id
AND mi.module_id = mod.id
AND mi.formsemestre_id = sem.id
AND ma.id = %(matiere_id)s
AND sem.etat = false
""",
{"matiere_id": matiere_id},
)
return len(r) > 0

View File

@ -767,6 +767,7 @@ def module_edit(
module_dict["semestre_id"] = 1 module_dict["semestre_id"] = 1
else: else:
module_dict["semestre_id"] = module.ue.semestre_idx module_dict["semestre_id"] = module.ue.semestre_idx
tags = module.tags if module else []
tf = TrivialFormulator( tf = TrivialFormulator(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
@ -774,7 +775,9 @@ def module_edit(
html_foot_markup=( html_foot_markup=(
f"""<div class="scobox sco_tag_module_edit"><span f"""<div class="scobox sco_tag_module_edit"><span
class="sco_tag_edit"><textarea data-module_id="{module_id}" class="module_tag_editor" class="sco_tag_edit"><textarea data-module_id="{module_id}" class="module_tag_editor"
>{','.join(sco_tag_module.module_tag_list(module_id))}</textarea></span></div> >{
','.join(t.title for t in tags)
}</textarea></span></div>
""" """
if not create if not create
else "" else ""
@ -833,10 +836,14 @@ def module_edit(
if matiere: if matiere:
tf[2]["matiere_id"] = matiere.id tf[2]["matiere_id"] = matiere.id
else: else:
matiere_id = edit_matiere.do_matiere_create( matiere = Matiere.create_from_dict(
{"ue_id": ue.id, "titre": ue.titre or "", "numero": 1}, {
"ue_id": ue.id,
"titre": ue.titre or "",
"numero": 1,
}
) )
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_id = do_module_create(tf[2])
@ -946,12 +953,6 @@ def module_is_locked(module_id):
return len(r) > 0 return len(r) > 0
def module_count_moduleimpls(module_id):
"Number of moduleimpls using this module"
mods = sco_moduleimpl.moduleimpl_list(module_id=module_id)
return len(mods)
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
): ):

View File

@ -547,9 +547,10 @@ def ue_edit(ue_id=None, create=False, formation_id=None, default_semestre_idx=No
if is_apc or cursus.UE_IS_MODULE or tf[2]["create_matiere"]: if is_apc or cursus.UE_IS_MODULE or tf[2]["create_matiere"]:
# rappel: en APC, toutes les UE ont une matière, créée ici # rappel: en APC, toutes les UE ont une matière, créée ici
# (inutilisée mais à laquelle les modules sont rattachés) # (inutilisée mais à laquelle les modules sont rattachés)
matiere_id = edit_matiere.do_matiere_create( matiere = Matiere.create_from_dict(
{"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1}, {"ue_id": ue_id, "titre": tf[2]["titre"], "numero": 1}
) )
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( _ = edit_module.do_module_create(
@ -676,9 +677,10 @@ def ue_delete(ue_id=None, delete_validations=False, dialog_confirmed=False):
return do_ue_delete(ue, delete_validations=delete_validations) return do_ue_delete(ue, delete_validations=delete_validations)
def ue_table(formation_id=None, semestre_idx=1, msg=""): # was ue_list def ue_table(formation_id=None, semestre_idx=1, msg=""):
"""Liste des matières et modules d'une formation, avec liens pour """Page affiochage ou édition d'une formation
éditer (si non verrouillée). avec UEs, matières et module,
et liens pour éditer si non verrouillée et permission.
""" """
from app.scodoc import sco_formsemestre_validation from app.scodoc import sco_formsemestre_validation
@ -1240,7 +1242,7 @@ def _ue_table_ues(
def _ue_table_matieres( def _ue_table_matieres(
parcours, parcours,
ue, ue_dict: dict,
editable, editable,
tag_editable, tag_editable,
arrow_up, arrow_up,
@ -1250,26 +1252,27 @@ def _ue_table_matieres(
delete_disabled_icon, delete_disabled_icon,
): ):
"""Édition de programme: liste des matières (et leurs modules) d'une UE.""" """Édition de programme: liste des matières (et leurs modules) d'une UE."""
ue = UniteEns.get_ue(ue_dict["ue_id"])
H = [] H = []
if not parcours.UE_IS_MODULE: if not parcours.UE_IS_MODULE:
H.append('<ul class="notes_matiere_list">') H.append('<ul class="notes_matiere_list">')
matieres = edit_matiere.matiere_list(args={"ue_id": ue["ue_id"]}) matieres = ue.matieres.all()
for mat in matieres: for mat in matieres:
if not parcours.UE_IS_MODULE: if not parcours.UE_IS_MODULE:
H.append('<li class="notes_matiere_list">') H.append('<li class="notes_matiere_list">')
if editable and not edit_matiere.matiere_is_locked(mat["matiere_id"]): if editable and not mat.is_locked():
H.append( H.append(
f"""<a class="stdlink" href="{ f"""<a class="stdlink" href="{
url_for("notes.matiere_edit", url_for("notes.matiere_edit",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"]) scodoc_dept=g.scodoc_dept, matiere_id=mat.id)
}"> }">
""" """
) )
H.append("%(titre)s" % mat) H.append(f"{mat.titre or 'sans titre'}")
if editable and not edit_matiere.matiere_is_locked(mat["matiere_id"]): if editable and not mat.is_locked():
H.append("</a>") H.append("</a>")
modules = edit_module.module_list(args={"matiere_id": mat["matiere_id"]}) modules = mat.modules.all()
H.append( H.append(
_ue_table_modules( _ue_table_modules(
parcours, parcours,
@ -1291,14 +1294,17 @@ def _ue_table_matieres(
H.append("<li>Aucune matière dans cette UE ! ") H.append("<li>Aucune matière dans cette UE ! ")
if editable: if editable:
H.append( H.append(
"""<a class="stdlink" href="ue_delete?ue_id=%(ue_id)s">supprimer l'UE</a>""" f"""<a class="stdlink" href="{
% ue url_for('notes.ue_delete', scodoc_dept=g.scodoc_dept, ue_id=ue.id)
}">supprimer l'UE</a>"""
) )
H.append("</li>") H.append("</li>")
if editable and not parcours.UE_IS_MODULE: if editable and not parcours.UE_IS_MODULE:
H.append( H.append(
'<li><a class="stdlink" href="matiere_create?ue_id=%(ue_id)s">créer une matière</a> </li>' f"""<li><a class="stdlink" href="{
% ue url_for("notes.matiere_create", scodoc_dept=g.scodoc_dept, ue_id=ue.id)
}">créer une matière</a>
</li>"""
) )
if not parcours.UE_IS_MODULE: if not parcours.UE_IS_MODULE:
H.append("</ul>") H.append("</ul>")
@ -1307,9 +1313,9 @@ def _ue_table_matieres(
def _ue_table_modules( def _ue_table_modules(
parcours, parcours,
ue, ue: UniteEns,
mat, mat: Matiere,
modules, modules: list[Module],
editable, editable,
tag_editable, tag_editable,
arrow_up, arrow_up,
@ -1326,89 +1332,84 @@ def _ue_table_modules(
H = ['<ul class="notes_module_list">'] H = ['<ul class="notes_module_list">']
im = 0 im = 0
for mod in modules: for mod in modules:
mod["nb_moduleimpls"] = edit_module.module_count_moduleimpls(mod["module_id"]) nb_moduleimpls = mod.modimpls.count()
klass = "notes_module_list" klass = "notes_module_list"
if mod["module_type"] == ModuleType.MALUS: if mod.module_type == ModuleType.MALUS:
klass += " module_malus" klass += " module_malus"
H.append('<li class="%s">' % klass) H.append(f'<li class="{klass}">')
H.append('<span class="notes_module_list_buts">') H.append('<span class="notes_module_list_buts">')
if im != 0 and editable: if im != 0 and editable:
H.append( H.append(
'<a href="module_move?module_id=%s&after=0" class="aud">%s</a>' f"""<a href="module_move?module_id={mod.id}&after=0" class="aud">{arrow_up}</a>"""
% (mod["module_id"], arrow_up)
) )
else: else:
H.append(arrow_none) H.append(arrow_none)
if im < len(modules) - 1 and editable: if im < len(modules) - 1 and editable:
H.append( H.append(
'<a href="module_move?module_id=%s&after=1" class="aud">%s</a>' f"""<a href="module_move?module_id={mod.id}&after=1" class="aud">{arrow_down}</a>"""
% (mod["module_id"], arrow_down)
) )
else: else:
H.append(arrow_none) H.append(arrow_none)
im += 1 im += 1
if mod["nb_moduleimpls"] == 0 and editable: icon = delete_icon if nb_moduleimpls == 0 and editable else delete_disabled_icon
icon = delete_icon
else:
icon = delete_disabled_icon
H.append( H.append(
'<a class="smallbutton" href="module_delete?module_id=%s">%s</a>' f"""<a class="smallbutton" href="{
% (mod["module_id"], icon) url_for("notes.module_delete", scodoc_dept=g.scodoc_dept, module_id=mod.id)
}">{icon}</a>"""
) )
H.append("</span>") H.append("</span>")
mod_editable = ( mod_editable = editable
editable # and not edit_module.module_is_locked( Mod['module_id']) # and not edit_module.module_is_locked(Mod['module_id'])
)
if mod_editable: if mod_editable:
H.append( H.append(
'<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par %(nb_moduleimpls)d sessions" href="module_edit?module_id=%(module_id)s">' f"""<a class="discretelink" title="Modifier le module numéro %(numero)s, utilisé par
% mod {nb_moduleimpls} sessions" href="{
url_for("notes.module_edit", scodoc_dept=g.scodoc_dept, module_id=mod.id)
}">"""
) )
if mod["module_type"] not in (scu.ModuleType.STANDARD, scu.ModuleType.MALUS): if mod.module_type not in (scu.ModuleType.STANDARD, scu.ModuleType.MALUS):
H.append( H.append(
f"""<span class="invalid-module-type">{scu.EMO_WARNING} type incompatible </span>""" f"""<span class="invalid-module-type">{scu.EMO_WARNING} type incompatible </span>"""
) )
H.append( H.append(
'<span class="formation_module_tit">%s</span>' f"""<span class="formation_module_tit">{scu.join_words(mod.code, mod.titre)}</span>"""
% scu.join_words(mod["code"], mod["titre"])
) )
if mod_editable: if mod_editable:
H.append("</a>") H.append("</a>")
heurescoef = ( heures = (
"%(heures_cours)s/%(heures_td)s/%(heures_tp)s, coef. %(coefficient)s" % mod f"""{mod.heures_cours or 0}/{mod.heures_td or 0}/{mod.heures_tp or 0}, """
if (mod.heures_cours or mod.heures_td or mod.heures_tp)
else ""
) )
heurescoef = f"""{heures}coef. {mod.coefficient}"""
edit_url = url_for( edit_url = url_for(
"apiweb.formation_module_set_code_apogee", "apiweb.formation_module_set_code_apogee",
scodoc_dept=g.scodoc_dept, scodoc_dept=g.scodoc_dept,
module_id=mod["module_id"], module_id=mod.id,
) )
heurescoef += f""", Apo: <span heurescoef += f""", Apo: <span
class="{'span_apo_edit' if editable else ''}" class="{'span_apo_edit' if editable else ''}"
data-url="{edit_url}" id="{mod["module_id"]}" data-url="{edit_url}" id="{mod.id}"
data-placeholder="{scu.APO_MISSING_CODE_STR}">{ data-placeholder="{scu.APO_MISSING_CODE_STR}">{
mod["code_apogee"] or "" mod.code_apogee or ""
}</span>""" }</span>"""
if tag_editable: tag_cls = "module_tag_editor" if tag_editable else "module_tag_editor_ro"
tag_cls = "module_tag_editor" tag_edit = f"""<span class="sco_tag_edit">
else: <form>
tag_cls = "module_tag_editor_ro" <textarea data-module_id="{mod.id}" class="{tag_cls}">{
tag_mk = """<span class="sco_tag_edit"><form><textarea data-module_id="{}" class="{}">{}</textarea></form></span>""" ",".join([ tag.title for tag in mod.tags ])
tag_edit = tag_mk.format( }</textarea>
mod["module_id"], </form>
tag_cls, </span>"""
",".join(sco_tag_module.module_tag_list(mod["module_id"])), if ue.semestre_idx is not None and mod.semestre_id != ue.semestre_idx:
)
if ue["semestre_idx"] is not None and mod["semestre_id"] != ue["semestre_idx"]:
warning_semestre = ' <span class="red">incohérent ?</span>' warning_semestre = ' <span class="red">incohérent ?</span>'
else: else:
warning_semestre = "" warning_semestre = ""
H.append( H.append(
" %s %s%s" % (parcours.SESSION_NAME, mod["semestre_id"], warning_semestre) f""" {parcours.SESSION_NAME} {mod.semestre_id}{warning_semestre}
+ " (%s)" % heurescoef {heurescoef}{tag_edit}"""
+ tag_edit
) )
H.append("</li>") H.append("</li>")
if not modules: if not modules:
@ -1417,7 +1418,7 @@ def _ue_table_modules(
H.append( H.append(
f"""<a class="stdlink" href="{ f"""<a class="stdlink" href="{
url_for("notes.matiere_delete", url_for("notes.matiere_delete",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}" scodoc_dept=g.scodoc_dept, matiere_id=mat.id)}"
>la supprimer</a> >la supprimer</a>
""" """
) )
@ -1426,7 +1427,7 @@ def _ue_table_modules(
H.append( H.append(
f"""<li> <a class="stdlink" href="{ f"""<li> <a class="stdlink" href="{
url_for("notes.module_create", url_for("notes.module_create",
scodoc_dept=g.scodoc_dept, matiere_id=mat["matiere_id"])}" scodoc_dept=g.scodoc_dept, matiere_id=mat.id)}"
>{create_element_msg}</a></li> >{create_element_msg}</a></li>
""" """
) )

View File

@ -36,8 +36,8 @@ 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_matiere, edit_module, edit_ue from app.formations import edit_module, edit_ue
from app.models import Formation, FormSemestre, 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 (
ApcAppCritique, ApcAppCritique,
@ -113,35 +113,35 @@ def formation_export_dict(
ue_dict.pop("code_apogee_rcue", None) ue_dict.pop("code_apogee_rcue", None)
if ue_dict.get("ects") is None: if ue_dict.get("ects") is None:
ue_dict.pop("ects", None) ue_dict.pop("ects", None)
mats = edit_matiere.matiere_list({"ue_id": ue.id}) mats = ue.matieres.all()
mats.sort(key=lambda m: m["numero"] or 0) mats.sort(key=lambda m: m.numero)
ue_dict["matiere"] = mats mats_dict = [mat.to_dict() for mat in mats]
for mat in mats: ue_dict["matiere"] = mats_dict
matiere_id = mat["matiere_id"] for mat_d in mats_dict:
matiere_id = mat_d["matiere_id"]
if not export_ids: if not export_ids:
del mat["id"] del mat_d["id"]
del mat["matiere_id"] del mat_d["matiere_id"]
del mat["ue_id"] del mat_d["ue_id"]
mods = edit_module.module_list({"matiere_id": matiere_id}) mods = edit_module.module_list({"matiere_id": matiere_id})
mods.sort(key=lambda m: (m["numero"] or 0, m["code"])) mods.sort(key=lambda m: (m["numero"] or 0, m["code"]))
mat["module"] = mods mat_d["module"] = mods
for mod in mods: for mod_d in mods:
module_id = mod["module_id"] module: Module = db.session.get(Module, mod_d["module_id"])
if export_tags: if export_tags:
tags = sco_tag_module.module_tag_list(module_id=mod["module_id"]) tags = [t.title for t in module.tags]
if tags: if tags:
mod["tags"] = [{"name": x} for x in tags] mod_d["tags"] = [{"name": x} for x in tags]
# #
module: Module = db.session.get(Module, module_id)
if module.is_apc(): if module.is_apc():
# Exporte les coefficients # Exporte les coefficients
if ue_reference_style == "id": if ue_reference_style == "id":
mod["coefficients"] = [ mod_d["coefficients"] = [
{"ue_reference": str(ue_id), "coef": str(coef)} {"ue_reference": str(ue_id), "coef": str(coef)}
for (ue_id, coef) in module.get_ue_coef_dict().items() for (ue_id, coef) in module.get_ue_coef_dict().items()
] ]
else: else:
mod["coefficients"] = [ mod_d["coefficients"] = [
{"ue_reference": ue_acronyme, "coef": str(coef)} {"ue_reference": ue_acronyme, "coef": str(coef)}
for ( for (
ue_acronyme, ue_acronyme,
@ -149,29 +149,29 @@ def formation_export_dict(
) in module.get_ue_coef_dict_acronyme().items() ) in module.get_ue_coef_dict_acronyme().items()
] ]
# Et les parcours # Et les parcours
mod["parcours"] = [ mod_d["parcours"] = [
p.to_dict(with_annees=False) for p in module.parcours p.to_dict(with_annees=False) for p in module.parcours
] ]
# Et les AC # Et les AC
if ac_as_list: if ac_as_list:
# XML préfère une liste # XML préfère une liste
mod["app_critiques"] = [ mod_d["app_critiques"] = [
x.to_dict(with_code=True) for x in module.app_critiques x.to_dict(with_code=True) for x in module.app_critiques
] ]
else: else:
mod["app_critiques"] = { mod_d["app_critiques"] = {
x.code: x.to_dict() for x in module.app_critiques x.code: x.to_dict() for x in module.app_critiques
} }
if not export_ids: if not export_ids:
del mod["id"] del mod_d["id"]
del mod["ue_id"] del mod_d["ue_id"]
del mod["matiere_id"] del mod_d["matiere_id"]
del mod["module_id"] del mod_d["module_id"]
del mod["formation_id"] del mod_d["formation_id"]
if not export_codes_apo: if not export_codes_apo:
del mod["code_apogee"] del mod_d["code_apogee"]
if mod["ects"] is None: if mod_d["ects"] is None:
del mod["ects"] del mod_d["ects"]
return f_dict return f_dict
@ -399,7 +399,8 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F
assert mat_info[0] == "matiere" assert mat_info[0] == "matiere"
mat_info[1]["ue_id"] = ue_id mat_info[1]["ue_id"] = ue_id
mat_id = edit_matiere.do_matiere_create(mat_info[1]) mat = Matiere.create_from_dict(mat_info[1])
mat_id = mat.id
# -- create modules # -- create modules
for mod_info in mat_info[2]: for mod_info in mat_info[2]:
assert mod_info[0] == "module" assert mod_info[0] == "module"
@ -460,7 +461,7 @@ def formation_import_xml(doc: str | bytes, import_tags=True, use_local_refcomp=F
f"Warning: parcours {code_parcours} inexistant !" f"Warning: parcours {code_parcours} inexistant !"
) )
if import_tags and tag_names: if import_tags and tag_names:
sco_tag_module.module_tag_set(mod_id, tag_names) module.set_tags(tag_names)
if module.is_apc() and ue_coef_dict: if module.is_apc() and ue_coef_dict:
modules_a_coefficienter.append((module, ue_coef_dict)) modules_a_coefficienter.append((module, ue_coef_dict))
# Fixe les coefs APC (à la fin pour que les UE soient créées) # Fixe les coefs APC (à la fin pour que les UE soient créées)

View File

@ -5,7 +5,7 @@ from flask import abort, g
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.comp import df_cache from app.comp import df_cache
from app.models import ScoDocModel, SHORT_STR_LEN from app.models import ScoDocModel, SHORT_STR_LEN
from app.models.but_refcomp import ( from app.models.but_refcomp import (
@ -14,6 +14,7 @@ from app.models.but_refcomp import (
ApcParcours, ApcParcours,
ApcParcoursNiveauCompetence, ApcParcoursNiveauCompetence,
) )
from app.models.events import ScolarNews
from app.models.modules import Module from app.models.modules import Module
from app.models.moduleimpls import ModuleImpl from app.models.moduleimpls import ModuleImpl
from app.models.ues import UniteEns, UEParcours from app.models.ues import UniteEns, UEParcours
@ -21,6 +22,7 @@ from app.scodoc import sco_cache
from app.scodoc import codes_cursus from app.scodoc import codes_cursus
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.codes_cursus import UE_STANDARD from app.scodoc.codes_cursus import UE_STANDARD
from app.scodoc.sco_exceptions import ScoNonEmptyFormationObject, ScoValueError
class Formation(ScoDocModel): class Formation(ScoDocModel):
@ -166,6 +168,7 @@ class Formation(ScoDocModel):
sco_cache.invalidate_formsemestre() sco_cache.invalidate_formsemestre()
def invalidate_cached_sems(self): def invalidate_cached_sems(self):
"Invalide caches de tous les formssemestres de la formation"
for sem in self.formsemestres: for sem in self.formsemestres:
sco_cache.invalidate_formsemestre(formsemestre_id=sem.id) sco_cache.invalidate_formsemestre(formsemestre_id=sem.id)
@ -312,7 +315,9 @@ class Matiere(ScoDocModel):
titre = db.Column(db.Text()) titre = db.Column(db.Text())
numero = db.Column(db.Integer, nullable=False, default=0) # ordre de présentation numero = db.Column(db.Integer, nullable=False, default=0) # ordre de présentation
modules = db.relationship("Module", lazy="dynamic", backref="matiere") modules = db.relationship(
"Module", lazy="dynamic", backref="matiere", cascade="all, delete-orphan"
)
_sco_dept_relations = ("UniteEns", "Formation") # accès au dept_id _sco_dept_relations = ("UniteEns", "Formation") # accès au dept_id
def __repr__(self): def __repr__(self):
@ -325,5 +330,75 @@ class Matiere(ScoDocModel):
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
# ScoDoc7 output_formators # ScoDoc7 output_formators
e["numero"] = e["numero"] if e["numero"] else 0 e["numero"] = e["numero"] if e["numero"] else 0
e["ue_id"] = self.id e["matiere_id"] = self.id
return e return e
def is_locked(self) -> bool:
"""True if matiere cannot be be modified
because it contains modules used in a locked formsemestre.
"""
from app.models.formsemestre import FormSemestre
mat = (
db.session.query(Matiere)
.filter_by(id=self.id)
.join(Module)
.join(ModuleImpl)
.join(FormSemestre)
.filter_by(etat=False)
.all()
)
return bool(mat)
def can_be_deleted(self) -> bool:
"True si la matiere n'est pas utilisée dans des formsemestres"
locked = self.is_locked()
if locked:
return False
if any(m.modimpls.all() for m in self.modules):
return False
return True
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():
# il y a au moins un modimpl dans un module de cette matière
raise ScoNonEmptyFormationObject("Matière", self.titre)
db.session.delete(self)
db.session.commit()
# news
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
)
# cache
formation.invalidate_cached_sems()
@classmethod
def create_from_dict(cls, data: dict) -> "Matiere":
"""Create matière from dict. Log, news, cache.
data must include ue_id, a valid UE id.
Commit session.
"""
# check ue
if data.get("ue_id") is None:
raise ScoValueError("UE id missing")
_ = UniteEns.get_ue(data["ue_id"])
mat = super().create_from_dict(data)
db.session.commit()
db.session.refresh(mat)
# news
formation = mat.ue.formation
ScolarNews.add(
typ=ScolarNews.NEWS_FORM,
obj=formation.id,
text=f"Modification de la formation {formation.acronyme}",
)
formation.invalidate_cached_sems()
return mat

View File

@ -1,9 +1,10 @@
"""ScoDoc 9 models : Modules """ScoDoc 9 models : Modules
""" """
import http
from flask import current_app, g from flask import current_app, g
from app import db from app import db, log
from app import models from app import models
from app.models import APO_CODE_STR_LEN from app.models import APO_CODE_STR_LEN
from app.models.but_refcomp import ( from app.models.but_refcomp import (
@ -75,11 +76,11 @@ class Module(models.ScoDocModel):
backref=db.backref("modules", lazy=True), backref=db.backref("modules", lazy=True),
) )
_sco_dept_relations = "Formation" # accès au dept_id _sco_dept_relations = ("Formation",) # accès au dept_id
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.ue_coefs = [] self.ue_coefs = []
super(Module, self).__init__(**kwargs) super().__init__(**kwargs)
def __repr__(self): def __repr__(self):
return f"""<Module{ModuleType(self.module_type or ModuleType.STANDARD).name return f"""<Module{ModuleType(self.module_type or ModuleType.STANDARD).name
@ -449,6 +450,43 @@ class Module(models.ScoDocModel):
db.session.add(self) db.session.add(self)
db.session.flush() db.session.flush()
def set_tags(self, taglist: str | list[str] | None = None):
"""taglist may either be:
a string with tag names separated by commas ("un,deux")
or a list of strings (["un", "deux"])
Remplace les tags existants
"""
# TODO refactoring ScoTag
# TODO code à moderniser (+ revoir classe ScoTag, utiliser modèle)
# TODO Voir ItemSuiviTag et api etud_suivi
from app.scodoc.sco_tag_module import ScoTag, ModuleTag
taglist = taglist or []
if isinstance(taglist, str):
taglist = taglist.split(",")
taglist = [t.strip() for t in taglist]
taglist = [t for t in taglist if t]
log(f"module.set_tags: module_id={self.id} taglist={taglist}")
# Check tags syntax
for tag in taglist:
if not ScoTag.check_tag_title(tag):
log(f"module.set_tags({self.id}): invalid tag title")
return scu.json_error(404, "invalid tag")
newtags = set(taglist)
oldtags = set(t.title for t in self.tags)
to_del = oldtags - newtags
to_add = newtags - oldtags
# should be atomic, but it's not.
for tagname in to_add:
t = ModuleTag(tagname, object_id=self.id)
for tagname in to_del:
t = ModuleTag(tagname)
t.remove_tag_from_object(self.id)
return "", http.HTTPStatus.NO_CONTENT
class ModuleUECoef(db.Model): class ModuleUECoef(db.Model):
"""Coefficients des modules vers les UE (APC, BUT) """Coefficients des modules vers les UE (APC, BUT)
@ -499,7 +537,7 @@ class ModuleUECoef(db.Model):
return d return d
class NotesTag(db.Model): class NotesTag(models.ScoDocModel):
"""Tag sur un module""" """Tag sur un module"""
__tablename__ = "notes_tags" __tablename__ = "notes_tags"
@ -511,6 +549,9 @@ class NotesTag(db.Model):
dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True) dept_id = db.Column(db.Integer, db.ForeignKey("departement.id"), index=True)
title = db.Column(db.Text(), nullable=False) title = db.Column(db.Text(), nullable=False)
def __repr__(self):
return f"<Tag {self.id} {self.title!r}>"
@classmethod @classmethod
def get_or_create(cls, title: str, dept_id: int | None = None) -> "NotesTag": def get_or_create(cls, title: str, dept_id: int | None = None) -> "NotesTag":
"""Get tag, or create it if it doesn't yet exists. """Get tag, or create it if it doesn't yet exists.

View File

@ -274,7 +274,8 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
pole=None, pole=None,
) -> pd.DataFrame: ) -> pd.DataFrame:
"""Calcule la moyenne par UE des étudiants pour un tag donné, """Calcule la moyenne par UE des étudiants pour un tag donné,
en ayant connaissance des informations sur le tag et des inscriptions des étudiants aux différentes UEs. en ayant connaissance des informations sur le tag et des inscriptions
des étudiants aux différentes UEs.
info_tag détermine les modules pris en compte : info_tag détermine les modules pris en compte :
* si non `None`, seuls les modules rattachés au tag sont pris en compte * si non `None`, seuls les modules rattachés au tag sont pris en compte
@ -342,7 +343,8 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
colonnes = [ue.id for ue in self.ues_standards] colonnes = [ue.id for ue in self.ues_standards]
moyennes_ues_tag = moyennes_ues_tag[colonnes] moyennes_ues_tag = moyennes_ues_tag[colonnes]
# Applique le masque d'inscription aux UE pour ne conserver que les UE dans lequel l'étudiant est inscrit # Applique le masque d'inscription aux UE pour ne conserver
# que les UE dans lequel l'étudiant est inscrit
moyennes_ues_tag = moyennes_ues_tag[colonnes] * ues_inscr_parcours_df[colonnes] moyennes_ues_tag = moyennes_ues_tag[colonnes] * ues_inscr_parcours_df[colonnes]
# Transforme les UEs en acronyme # Transforme les UEs en acronyme
@ -405,7 +407,7 @@ class ResSemBUTTag(ResultatsSemestreBUT, pe_tabletags.TableTag):
"""Vérifie l'unicité des tags""" """Vérifie l'unicité des tags"""
noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys()))) noms_tags_perso = sorted(list(set(dict_tags["personnalises"].keys())))
noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp noms_tags_auto = sorted(list(set(dict_tags["auto"].keys()))) # + noms_tags_comp
noms_tags = noms_tags_perso + noms_tags_auto # noms_tags = noms_tags_perso + noms_tags_auto
intersection = list(set(noms_tags_perso) & set(noms_tags_auto)) intersection = list(set(noms_tags_perso) & set(noms_tags_auto))
@ -455,7 +457,7 @@ def get_synthese_tags_personnalises_semestre(formsemestre: FormSemestre):
modimpl_id = modimpl.id modimpl_id = modimpl.id
# Liste des tags pour le module concerné # Liste des tags pour le module concerné
tags = sco_tag_module.module_tag_list(modimpl.module.id) tags = [t.title for t in modimpl.module.tags]
# Traitement des tags recensés, chacun pouvant étant de la forme # Traitement des tags recensés, chacun pouvant étant de la forme
# "mathématiques", "théorie", "pe:0", "maths:2" # "mathématiques", "théorie", "pe:0", "maths:2"

View File

@ -30,16 +30,15 @@
Implementation expérimentale (Jul. 2016) pour grouper les modules sur Implementation expérimentale (Jul. 2016) pour grouper les modules sur
les avis de poursuites d'études. les avis de poursuites d'études.
TODO: réécrire avec SQLAlchemy.
Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html Pour l'UI, voir https://goodies.pixabay.com/jquery/tag-editor/demo.html
""" """
import http
import re import re
from flask import g from flask import g
from app import db, log from app import db
from app.formations import edit_module
from app.models import Formation, NotesTag from app.models import Formation, NotesTag
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
@ -60,7 +59,7 @@ from app.scodoc.sco_exceptions import ScoValueError
# NOTA: ancien code, n'utile pas de modèles SQLAlchemy # NOTA: ancien code, n'utile pas de modèles SQLAlchemy
class ScoTag(object): class ScoTag:
"""Generic tags for ScoDoc""" """Generic tags for ScoDoc"""
# must be overloaded: # must be overloaded:
@ -208,8 +207,6 @@ class ModuleTag(ScoTag):
# API # API
# TODO placer dans la vraie API et ne plus utiliser sco_publish # TODO placer dans la vraie API et ne plus utiliser sco_publish
def module_tag_search(term: str | int): def module_tag_search(term: str | int):
"""List all used tag names (for auto-completion)""" """List all used tag names (for auto-completion)"""
@ -230,60 +227,6 @@ def module_tag_search(term: str | int):
return scu.sendJSON(data) return scu.sendJSON(data)
def module_tag_list(module_id="") -> list[str]:
"""les noms de tags associés à ce module"""
r = ndb.SimpleDictFetch(
"""SELECT t.title
FROM notes_modules_tags mt, notes_tags t
WHERE mt.tag_id = t.id
AND mt.module_id = %(module_id)s
""",
{"module_id": module_id},
)
return [x["title"] for x in r]
def module_tag_set(module_id="", taglist=None):
"""taglist may either be:
a string with tag names separated by commas ("un,deux")
or a list of strings (["un", "deux"])
Remplace les tags existants
"""
if not taglist:
taglist = []
elif isinstance(taglist, str):
taglist = taglist.split(",")
taglist = [t.strip() for t in taglist]
log(f"module_tag_set: module_id={module_id} taglist={taglist}")
# Check tags syntax
for tag in taglist:
if not ScoTag.check_tag_title(tag):
log(f"module_tag_set({module_id}): invalid tag title")
return scu.json_error(404, "invalid tag")
# TODO code à moderniser (+ revoir classe ScoTag, utiliser modèle)
# TODO Voir ItemSuiviTag et api etud_suivi
# Sanity check:
mod_dict = edit_module.module_list(args={"module_id": module_id})
if not mod_dict:
raise ScoValueError("invalid module !")
newtags = set(taglist)
oldtags = set(module_tag_list(module_id))
to_del = oldtags - newtags
to_add = newtags - oldtags
# should be atomic, but it's not.
for tagname in to_add:
t = ModuleTag(tagname, object_id=module_id)
for tagname in to_del:
t = ModuleTag(tagname)
t.remove_tag_from_object(module_id)
return "", http.HTTPStatus.NO_CONTENT
def split_tagname_coeff(tag: str, separateur=":") -> tuple[str, float]: def split_tagname_coeff(tag: str, separateur=":") -> tuple[str, float]:
"""Découpage d'un tag, tel que saisi par un utilisateur dans le programme, """Découpage d'un tag, tel que saisi par un utilisateur dans le programme,
pour en extraire : pour en extraire :

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_matiere, edit_module, edit_ue from app.formations import edit_module, 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, ModuleImpl, UniteEns from app.models import Evaluation, Identite, Matiere, 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
@ -110,7 +110,7 @@ def external_ue_create(
) )
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})")
matiere_id = edit_matiere.do_matiere_create( matiere = Matiere.create_from_dict(
{"ue_id": ue_id, "titre": titre or acronyme, "numero": 1} {"ue_id": ue_id, "titre": titre or acronyme, "numero": 1}
) )
@ -120,7 +120,7 @@ def external_ue_create(
"code": acronyme, "code": acronyme,
"coefficient": ects, # tous le coef. module est egal à la quantite d'ECTS "coefficient": ects, # tous le coef. module est egal à la quantite d'ECTS
"ue_id": ue_id, "ue_id": ue_id,
"matiere_id": matiere_id, "matiere_id": matiere.id,
"formation_id": formation_id, "formation_id": formation_id,
"semestre_id": formsemestre.semestre_id, "semestre_id": formsemestre.semestre_id,
"module_type": scu.ModuleType.STANDARD, "module_type": scu.ModuleType.STANDARD,

View File

@ -612,9 +612,7 @@ def formation_tag_modules_by_type(formation_id: int, semestre_idx: int):
"""Taggue tous les modules de la formation en fonction de leur type : 'res', 'sae', 'malus' """Taggue tous les modules de la formation en fonction de leur type : 'res', 'sae', 'malus'
Ne taggue pas les modules standards. Ne taggue pas les modules standards.
""" """
formation = Formation.query.filter_by( formation = Formation.get_formation(formation_id)
id=formation_id, dept_id=g.scodoc_dept_id
).first_or_404()
sco_tag_module.formation_tag_modules_by_type(formation) sco_tag_module.formation_tag_modules_by_type(formation)
flash("Formation tagguée") flash("Formation tagguée")
return flask.redirect( return flask.redirect(
@ -631,11 +629,12 @@ def formation_tag_modules_by_type(formation_id: int, semestre_idx: int):
@bp.route("/module_tag_set", methods=["POST"]) @bp.route("/module_tag_set", methods=["POST"])
@scodoc @scodoc
@permission_required(Permission.EditFormationTags) @permission_required(Permission.EditFormationTags)
def module_tag_set(): def module_tag_set(): # TODO passer dans l'API
"""Set tags on module""" """Set tags on module"""
module_id = int(request.form.get("module_id")) module_id = request.form.get("module_id")
module: Module = Module.get_instance(module_id)
taglist = request.form.get("taglist") taglist = request.form.get("taglist")
return sco_tag_module.module_tag_set(module_id, taglist) return module.set_tags(taglist)
@bp.route("/module_clone", methods=["POST"]) @bp.route("/module_clone", methods=["POST"])
@ -643,8 +642,8 @@ def module_tag_set():
@permission_required(Permission.EditFormation) @permission_required(Permission.EditFormation)
def module_clone(): def module_clone():
"""Clone existing module""" """Clone existing module"""
module_id = int(request.form.get("module_id")) module_id = request.form.get("module_id")
module = Module.query.get_or_404(module_id) module: Module = Module.get_instance(module_id)
module2 = module.clone() module2 = module.clone()
db.session.add(module2) db.session.add(module2)
db.session.commit() db.session.commit()

View File

@ -205,11 +205,8 @@ class ScoFake(object):
@logging_meth @logging_meth
def create_matiere(self, ue_id=None, titre=None, numero=0) -> int: def create_matiere(self, ue_id=None, titre=None, numero=0) -> int:
oid = edit_matiere.do_matiere_create(locals()) mat = Matiere.create_from_dict(locals())
oids = edit_matiere.matiere_list(args={"matiere_id": oid}) return mat.id
if not oids:
raise ScoValueError("matiere not created !")
return oid
@logging_meth @logging_meth
def create_module( def create_module(

View File

@ -36,8 +36,6 @@
# - do_formsemestre_delete # - do_formsemestre_delete
# - module_list # - module_list
# - do_module_delete # - do_module_delete
# - matiere_list
# - do_matiere_delete
# - ue_list # - ue_list
# - do_ue_delete # - do_ue_delete
# - do_formation_delete # - do_formation_delete
@ -50,12 +48,11 @@ import pytest
from app import db from app import db
from app.formations import ( from app.formations import (
edit_formation, edit_formation,
edit_matiere,
edit_module, edit_module,
edit_ue, edit_ue,
formation_io, formation_io,
) )
from app.models import Formation, ModuleImpl from app.models import Formation, Matiere, 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
@ -287,10 +284,12 @@ def test_formations(test_client):
assert len(lim_sem2) == 0 # deuxieme vérification si le module s'est bien sup assert len(lim_sem2) == 0 # deuxieme vérification si le module s'est bien sup
li_mat = edit_matiere.matiere_list() li_mat = Matiere.query.all()
assert len(li_mat) == 4 assert len(li_mat) == 4
edit_matiere.do_matiere_delete(oid=matiere_id3) # on supprime la matiere assert matiere_id3 in [m.matiere_id for m in li_mat]
li_mat2 = edit_matiere.matiere_list() matiere = db.session.get(Matiere, matiere_id3)
matiere.delete() # on supprime la matiere
li_mat2 = Matiere.query.all()
assert len(li_mat2) == 3 # verification de la suppression de la matiere assert len(li_mat2) == 3 # verification de la suppression de la matiere
li_ue = edit_ue.ue_list() li_ue = edit_ue.ue_list()