Clonage formation: conserve ue_code, même si vide

This commit is contained in:
Emmanuel Viennet 2024-01-26 14:57:50 +01:00
parent 431dd20911
commit 79f07deac0
6 changed files with 50 additions and 27 deletions

View File

@ -5,6 +5,7 @@ from flask import g
import pandas as pd import pandas as pd
from app import db, log from app import db, log
from app import models
from app.models import APO_CODE_STR_LEN from app.models import APO_CODE_STR_LEN
from app.models import SHORT_STR_LEN from app.models import SHORT_STR_LEN
from app.models.but_refcomp import ApcNiveau, ApcParcours from app.models.but_refcomp import ApcNiveau, ApcParcours
@ -12,7 +13,7 @@ from app.models.modules import Module
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
class UniteEns(db.Model): class UniteEns(models.ScoDocModel):
"""Unité d'Enseignement (UE)""" """Unité d'Enseignement (UE)"""
__tablename__ = "notes_ue" __tablename__ = "notes_ue"
@ -81,7 +82,7 @@ class UniteEns(db.Model):
'EXTERNE' if self.is_external else ''})>""" 'EXTERNE' if self.is_external else ''})>"""
def clone(self): def clone(self):
"""Create a new copy of this ue. """Create a new copy of this ue, add to session.
Ne copie pas le code, ni le code Apogée, ni les liens au réf. de comp. Ne copie pas le code, ni le code Apogée, ni les liens au réf. de comp.
(parcours et niveau). (parcours et niveau).
""" """
@ -100,8 +101,26 @@ class UniteEns(db.Model):
coef_rcue=self.coef_rcue, coef_rcue=self.coef_rcue,
color=self.color, color=self.color,
) )
db.session.add(ue)
return ue return ue
@classmethod
def convert_dict_fields(cls, args: dict) -> dict:
"""Convert fields from the given dict to model's attributes values. No side effect.
args: dict with args in application.
returns: dict to store in model's db.
"""
args = args.copy()
if "type" in args:
args["type"] = int(args["type"] or 0)
if "is_external" in args:
args["is_external"] = scu.to_bool(args["is_external"])
if "ects" in args:
args["ects"] = float(args["ects"])
return args
def to_dict(self, convert_objects=False, with_module_ue_coefs=True): def to_dict(self, convert_objects=False, with_module_ue_coefs=True):
"""as a dict, with the same conversions as in ScoDoc7. """as a dict, with the same conversions as in ScoDoc7.
If convert_objects, convert all attributes to native types If convert_objects, convert all attributes to native types

View File

@ -300,6 +300,7 @@ class EditableTable(object):
output_formators={}, output_formators={},
input_formators={}, input_formators={},
aux_tables=[], aux_tables=[],
convert_empty_to_nulls=True, # les arguments vides sont traduits en NULL
convert_null_outputs_to_empty=True, convert_null_outputs_to_empty=True,
html_quote=False, # changed in 9.0.10 html_quote=False, # changed in 9.0.10
fields_creators={}, # { field : [ sql_command_to_create_it ] } fields_creators={}, # { field : [ sql_command_to_create_it ] }
@ -321,6 +322,7 @@ class EditableTable(object):
self.output_formators = output_formators self.output_formators = output_formators
self.input_formators = input_formators self.input_formators = input_formators
self.convert_null_outputs_to_empty = convert_null_outputs_to_empty self.convert_null_outputs_to_empty = convert_null_outputs_to_empty
self.convert_empty_to_nulls = convert_empty_to_nulls
self.html_quote = html_quote self.html_quote = html_quote
self.fields_creators = fields_creators self.fields_creators = fields_creators
self.filter_nulls = filter_nulls self.filter_nulls = filter_nulls
@ -351,6 +353,7 @@ class EditableTable(object):
self.table_name, self.table_name,
vals, vals,
commit=True, commit=True,
convert_empty_to_nulls=self.convert_empty_to_nulls,
return_id=(self.id_name is not None), return_id=(self.id_name is not None),
ignore_conflicts=self.insert_ignore_conflicts, ignore_conflicts=self.insert_ignore_conflicts,
) )
@ -444,7 +447,7 @@ def dictfilter(d, fields, filter_nulls=True):
"""returns a copy of d with only keys listed in "fields" and non null values""" """returns a copy of d with only keys listed in "fields" and non null values"""
r = {} r = {}
for f in fields: for f in fields:
if f in d and (d[f] != None or not filter_nulls): if f in d and (d[f] is not None or not filter_nulls):
try: try:
val = d[f].strip() val = d[f].strip()
except: except:

View File

@ -89,6 +89,7 @@ _ueEditor = ndb.EditableTable(
"color", "color",
"niveau_competence_id", "niveau_competence_id",
), ),
convert_empty_to_nulls=False, # necessaire pour ue_code == ""
sortkey="numero", sortkey="numero",
input_formators={ input_formators={
"type": ndb.int_null_is_zero, "type": ndb.int_null_is_zero,
@ -110,7 +111,7 @@ def ue_list(*args, **kw):
return _ueEditor.list(cnx, *args, **kw) return _ueEditor.list(cnx, *args, **kw)
def do_ue_create(args): def do_ue_create(args, allow_empty_ue_code=False):
"create an ue" "create an ue"
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
# check duplicates # check duplicates
@ -120,18 +121,18 @@ def do_ue_create(args):
f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé ! f"""Acronyme d'UE "{args['acronyme']}" déjà utilisé !
(chaque UE doit avoir un acronyme unique dans la formation)""" (chaque UE doit avoir un acronyme unique dans la formation)"""
) )
if ( if "ue_code" not in args or args["ue_code"] is None or not args["ue_code"].strip():
(not "ue_code" in args) if allow_empty_ue_code:
or (args["ue_code"] is None) args["ue_code"] = ""
or (not args["ue_code"].strip()) else:
): # évite les conflits: génère nouveau ue_code
# évite les conflits de code
while True: while True:
cursor = db.session.execute(sa.text("select notes_newid_ucod();")) cursor = db.session.execute(sa.text("select notes_newid_ucod();"))
code = cursor.fetchone()[0] code = cursor.fetchone()[0]
if UniteEns.query.filter_by(ue_code=code).count() == 0: if UniteEns.query.filter_by(ue_code=code).count() == 0:
break break
args["ue_code"] = code args["ue_code"] = code
# create # create
ue_id = _ueEditor.create(cnx, args) ue_id = _ueEditor.create(cnx, args)
log(f"do_ue_create: created {ue_id} with {args}") log(f"do_ue_create: created {ue_id} with {args}")

View File

@ -168,7 +168,6 @@ def formsemestre_associate_new_version(
formation_id=new_formation_id, formation_id=new_formation_id,
) )
) )
else:
return flask.redirect( return flask.redirect(
url_for( url_for(
"notes.formsemestre_status", "notes.formsemestre_status",
@ -176,7 +175,6 @@ def formsemestre_associate_new_version(
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
) )
) )
else:
raise ScoValueError("Méthode invalide") raise ScoValueError("Méthode invalide")

View File

@ -117,6 +117,7 @@ def formation_export_dict(
ues = ues.all() ues = ues.all()
ues.sort(key=lambda u: (u.semestre_idx or 0, u.numero or 0, u.acronyme)) ues.sort(key=lambda u: (u.semestre_idx or 0, u.numero or 0, u.acronyme))
f_dict["ue"] = [] f_dict["ue"] = []
ue: UniteEns
for ue in ues: for ue in ues:
ue_dict = ue.to_dict() ue_dict = ue.to_dict()
f_dict["ue"].append(ue_dict) f_dict["ue"].append(ue_dict)
@ -363,7 +364,9 @@ def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
ue_info[1]["niveau_competence_id"] = _formation_retreive_apc_niveau( ue_info[1]["niveau_competence_id"] = _formation_retreive_apc_niveau(
referentiel_competence_id, ue_info[1] referentiel_competence_id, ue_info[1]
) )
ue_id = sco_edit_ue.do_ue_create(ue_info[1]) # Note: si le code est indiqué "" dans le xml, il faut le conserver vide
# pour la comparaison ultérieure des formations XXX
ue_id = sco_edit_ue.do_ue_create(ue_info[1], allow_empty_ue_code=True)
ue: UniteEns = db.session.get(UniteEns, ue_id) ue: UniteEns = db.session.get(UniteEns, ue_id)
assert ue assert ue
if xml_ue_id: if xml_ue_id:

View File

@ -824,7 +824,6 @@ def ue_clone():
ue_id = int(request.form.get("ue_id")) ue_id = int(request.form.get("ue_id"))
ue = UniteEns.query.get_or_404(ue_id) ue = UniteEns.query.get_or_404(ue_id)
ue2 = ue.clone() ue2 = ue.clone()
db.session.add(ue2)
db.session.commit() db.session.commit()
flash(f"UE {ue.acronyme} dupliquée") flash(f"UE {ue.acronyme} dupliquée")
return flask.redirect( return flask.redirect(