Import formations / ref. comp. : fixes #510

This commit is contained in:
Emmanuel Viennet 2022-10-23 23:28:24 +02:00
parent a92d2a6edf
commit dac46b8366
5 changed files with 161 additions and 121 deletions

View File

@ -61,12 +61,19 @@ class Formation(db.Model):
"titre complet pour affichage" "titre complet pour affichage"
return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}""" return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}"""
def to_dict(self): def to_dict(self, with_refcomp_attrs=False):
""" "as a dict.
Si with_refcomp_attrs, ajoute attributs permettant de retrouver le ref. de comp.
"""
e = dict(self.__dict__) e = dict(self.__dict__)
e.pop("_sa_instance_state", None) e.pop("_sa_instance_state", None)
e["departement"] = self.departement.to_dict() e["departement"] = self.departement.to_dict()
# ScoDoc7 output_formators: (backward compat) e["formation_id"] = self.id # ScoDoc7 backward compat
e["formation_id"] = self.id if with_refcomp_attrs and self.referentiel_competence:
e["refcomp_version_orebut"] = self.referentiel_competence.version_orebut
e["refcomp_specialite"] = self.referentiel_competence.specialite
e["refcomp_type_titre"] = self.referentiel_competence.type_titre
return e return e
def get_parcours(self): def get_parcours(self):

View File

@ -45,6 +45,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations from app.scodoc import sco_formations
@ -117,6 +118,7 @@ def do_formation_delete(formation_id):
), ),
) )
with sco_cache.DeferredSemCacheManager():
# Suppression des modules # Suppression des modules
for module in formation.modules: for module in formation.modules:
db.session.delete(module) db.session.delete(module)

View File

@ -33,7 +33,6 @@ import xml.dom.minidom
import flask import flask
from flask import flash, g, url_for, request from flask import flash, g, url_for, request
from flask_login import current_user from flask_login import current_user
from app.models.but_refcomp import ApcParcours
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
@ -41,10 +40,13 @@ from app import db
from app import log from app import log
from app.models import Formation, Module from app.models import Formation, Module
from app.models import ScolarNews from app.models import ScolarNews
from app.models.but_refcomp import ApcParcours, ApcReferentielCompetences
from app.scodoc import sco_cache
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
@ -107,7 +109,7 @@ def formation_export(
in desired format in desired format
""" """
formation: Formation = Formation.query.get_or_404(formation_id) formation: Formation = Formation.query.get_or_404(formation_id)
F = formation.to_dict() F = formation.to_dict(with_refcomp_attrs=True)
selector = {"formation_id": formation_id} selector = {"formation_id": formation_id}
if not export_external_ues: if not export_external_ues:
selector["is_external"] = False selector["is_external"] = False
@ -174,7 +176,7 @@ def formation_export(
) )
def formation_import_xml(doc: str, import_tags=True): def formation_import_xml(doc: str, import_tags=True, use_local_refcomp=False):
"""Create a formation from XML representation """Create a formation from XML representation
(format dumped by formation_export( format='xml' )) (format dumped by formation_export( format='xml' ))
XML may contain object (UE, modules) ids: this function returns two XML may contain object (UE, modules) ids: this function returns two
@ -183,13 +185,12 @@ def formation_import_xml(doc: str, import_tags=True):
Args: Args:
doc: str, xml data doc: str, xml data
import_tags: if false, does not import tags on modules. import_tags: if false, does not import tags on modules.
use_local_refcomp: if True, utilise les id vers les ref. de compétences.
Returns: Returns:
formation_id, modules_old2new, ues_old2new formation_id, modules_old2new, ues_old2new
""" """
from app.scodoc import sco_edit_formation from app.scodoc import sco_edit_formation
# log("formation_import_xml: doc=%s" % doc)
try: try:
dom = xml.dom.minidom.parseString(doc) dom = xml.dom.minidom.parseString(doc)
except Exception as exc: except Exception as exc:
@ -207,34 +208,54 @@ def formation_import_xml(doc: str, import_tags=True):
assert D[0] == "formation" assert D[0] == "formation"
F = D[1] F = D[1]
F["dept_id"] = g.scodoc_dept_id F["dept_id"] = g.scodoc_dept_id
referentiel_competence_id = F.get("referentiel_competence_id") # Pour les clonages, on prend le refcomp_id donné:
# find new version number referentiel_competence_id = (
cnx = ndb.GetDBConnexion() F.get("referentiel_competence_id") if use_local_refcomp else None
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
cursor.execute(
"""SELECT max(version)
FROM notes_formations
WHERE acronyme=%(acronyme)s and titre=%(titre)s and dept_id=%(dept_id)s
""",
F,
) )
res = cursor.fetchall() # Sinon, on cherche a retrouver le ref. comp.
try: if referentiel_competence_id is None:
version = int(res[0][0]) + 1 refcomp_version_orebut = F.get("refcomp_version_orebut")
except (ValueError, IndexError, TypeError): refcomp_specialite = F.get("refcomp_specialite")
version = 1 refcomp_type_titre = F.get("refcomp_type_titre")
F["version"] = version if all((refcomp_version_orebut, refcomp_specialite, refcomp_type_titre)):
refcomp = ApcReferentielCompetences.query.filter_by(
dept_id=g.scodoc_dept_id,
type_titre=refcomp_type_titre,
specialite=refcomp_specialite,
version_orebut=refcomp_version_orebut,
).first()
if refcomp:
referentiel_competence_id = refcomp.id
else:
flash(
f"Impossible de trouver le référentiel de compétence pour {refcomp_specialite} : est-il chargé ?"
)
F["referentiel_competence_id"] = referentiel_competence_id
# find new version number
formations = sco_formations.formation_list(
args={
"acronyme": F["acronyme"],
"titre": F["titre"],
"dept_id": F["dept_id"],
}
)
if formations:
version = max(f["version"] or 0 for f in formations)
else:
version = 0
F["version"] = version + 1
# create formation # create formation
# F_unquoted = F.copy()
# unescape_html_dict(F_unquoted)
formation_id = sco_edit_formation.do_formation_create(F) formation_id = sco_edit_formation.do_formation_create(F)
log(f"formation {formation_id} created") log(f"formation {formation_id} created")
ues_old2new = {} # xml ue_id : new ue_id ues_old2new = {} # xml ue_id : new ue_id
modules_old2new = {} # xml module_id : new module_id modules_old2new = {} # xml module_id : new module_id
# (nb: mecanisme utilise pour cloner semestres seulement, pas pour I/O XML) # (nb: mecanisme utilise pour cloner semestres seulement, pas pour I/O XML)
ue_reference_to_id = {} # pour les coefs APC (map reference -> ue_id) ue_reference_to_id = {} # pour les coefs APC (map reference -> ue_id)
modules_a_coefficienter = [] # Liste des modules avec coefs APC modules_a_coefficienter = [] # Liste des modules avec coefs APC
with sco_cache.DeferredSemCacheManager():
# -- create UEs # -- create UEs
for ue_info in D[2]: for ue_info in D[2]:
assert ue_info[0] == "ue" assert ue_info[0] == "ue"
@ -284,7 +305,9 @@ def formation_import_xml(doc: str, import_tags=True):
coef = float(child[1]["coef"]) coef = float(child[1]["coef"])
ue_coef_dict[ue_reference] = coef ue_coef_dict[ue_reference] = coef
elif child[0] == "parcours": elif child[0] == "parcours":
# Si on a un référentiel de compétences,
# associe les parcours de ce module (BUT) # associe les parcours de ce module (BUT)
if referentiel_competence_id:
code_parcours = child[1]["code"] code_parcours = child[1]["code"]
parcours = ApcParcours.query.filter_by( parcours = ApcParcours.query.filter_by(
code=code_parcours, code=code_parcours,
@ -294,15 +317,19 @@ def formation_import_xml(doc: str, import_tags=True):
module.parcours.append(parcours) module.parcours.append(parcours)
db.session.add(module) db.session.add(module)
else: else:
log(f"Warning: parcours {code_parcours} inexistant !") log(
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) sco_tag_module.module_tag_set(mod_id, 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)
for module, ue_coef_dict_ref in modules_a_coefficienter: for module, ue_coef_dict_ref in modules_a_coefficienter:
# remap ue ids: # remap ue ids:
ue_coef_dict = {ue_reference_to_id[k]: v for (k, v) in ue_coef_dict_ref.items()} ue_coef_dict = {
ue_reference_to_id[k]: v for (k, v) in ue_coef_dict_ref.items()
}
module.set_ue_coef_dict(ue_coef_dict) module.set_ue_coef_dict(ue_coef_dict)
db.session.commit() db.session.commit()
return formation_id, modules_old2new, ues_old2new return formation_id, modules_old2new, ues_old2new
@ -448,7 +475,9 @@ def formation_create_new_version(formation_id, redirect=True):
formation = Formation.query.get_or_404(formation_id) formation = Formation.query.get_or_404(formation_id)
resp = formation_export(formation_id, export_ids=True, format="xml") resp = formation_export(formation_id, export_ids=True, format="xml")
xml_data = resp.get_data(as_text=True) xml_data = resp.get_data(as_text=True)
new_id, modules_old2new, ues_old2new = formation_import_xml(xml_data) new_id, modules_old2new, ues_old2new = formation_import_xml(
xml_data, use_local_refcomp=True
)
# news # news
ScolarNews.add( ScolarNews.add(
typ=ScolarNews.NEWS_FORM, typ=ScolarNews.NEWS_FORM,

View File

@ -1018,6 +1018,7 @@ a.discretelink:hover {
.help { .help {
font-style: italic; font-style: italic;
max-width: 800px;
} }
.help_important { .help_important {

View File

@ -652,14 +652,6 @@ def formation_export(formation_id, export_ids=False, format=None):
@scodoc7func @scodoc7func
def formation_import_xml_form(): def formation_import_xml_form():
"form import d'une formation en XML" "form import d'une formation en XML"
H = [
html_sco_header.sco_header(page_title="Import d'une formation"),
"""<h2>Import d'une formation</h2>
<p>Création d'une formation (avec UE, matières, modules)
à partir un fichier XML (réservé aux utilisateurs avertis)</p>
""",
]
footer = html_sco_header.sco_footer()
tf = TrivialFormulator( tf = TrivialFormulator(
request.base_url, request.base_url,
scu.get_request_args(), scu.get_request_args(),
@ -668,7 +660,15 @@ def formation_import_xml_form():
cancelbutton="Annuler", cancelbutton="Annuler",
) )
if tf[0] == 0: if tf[0] == 0:
return "\n".join(H) + tf[1] + footer return f"""
{ html_sco_header.sco_header(page_title="Import d'une formation") }
<h2>Import d'une formation</h2>
<p>Création d'une formation (avec UE, matières, modules)
à partir un fichier XML (réservé aux utilisateurs avertis)
</p>
{ tf[1] }
{ html_sco_header.sco_footer() }
"""
elif tf[0] == -1: elif tf[0] == -1:
return flask.redirect(scu.NotesURL()) return flask.redirect(scu.NotesURL())
else: else:
@ -676,13 +676,14 @@ def formation_import_xml_form():
tf[2]["xmlfile"].read() tf[2]["xmlfile"].read()
) )
return ( return f"""
"\n".join(H) { html_sco_header.sco_header(page_title="Import d'une formation") }
+ """<p>Import effectué !</p> <h2>Import effectué !</h2>
<p><a class="stdlink" href="ue_list?formation_id=%s">Voir la formation</a></p>""" <p><a class="stdlink" href="{
% formation_id url_for("notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
+ footer )}">Voir la formation</a></p>
) { html_sco_header.sco_footer() }
"""
sco_publish( sco_publish(