forked from ScoDoc/ScoDoc
Import formations / ref. comp. : fixes #510
This commit is contained in:
parent
a92d2a6edf
commit
dac46b8366
@ -61,12 +61,19 @@ class Formation(db.Model):
|
||||
"titre complet pour affichage"
|
||||
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.pop("_sa_instance_state", None)
|
||||
e["departement"] = self.departement.to_dict()
|
||||
# ScoDoc7 output_formators: (backward compat)
|
||||
e["formation_id"] = self.id
|
||||
e["formation_id"] = self.id # ScoDoc7 backward compat
|
||||
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
|
||||
|
||||
def get_parcours(self):
|
||||
|
@ -45,6 +45,7 @@ from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoNonEmptyFormationObject
|
||||
|
||||
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_edit_ue
|
||||
from app.scodoc import sco_formations
|
||||
@ -117,15 +118,16 @@ def do_formation_delete(formation_id):
|
||||
),
|
||||
)
|
||||
|
||||
# Suppression des modules
|
||||
for module in formation.modules:
|
||||
db.session.delete(module)
|
||||
db.session.flush()
|
||||
# Suppression des UEs
|
||||
for ue in formation.ues:
|
||||
sco_edit_ue.do_ue_delete(ue, force=True)
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
# Suppression des modules
|
||||
for module in formation.modules:
|
||||
db.session.delete(module)
|
||||
db.session.flush()
|
||||
# Suppression des UEs
|
||||
for ue in formation.ues:
|
||||
sco_edit_ue.do_ue_delete(ue, force=True)
|
||||
|
||||
db.session.delete(formation)
|
||||
db.session.delete(formation)
|
||||
|
||||
# news
|
||||
ScolarNews.add(
|
||||
|
@ -33,7 +33,6 @@ import xml.dom.minidom
|
||||
import flask
|
||||
from flask import flash, g, url_for, request
|
||||
from flask_login import current_user
|
||||
from app.models.but_refcomp import ApcParcours
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
import app.scodoc.notesdb as ndb
|
||||
@ -41,10 +40,13 @@ from app import db
|
||||
from app import log
|
||||
from app.models import Formation, Module
|
||||
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_edit_matiere
|
||||
from app.scodoc import sco_edit_module
|
||||
from app.scodoc import sco_edit_ue
|
||||
from app.scodoc import sco_formations
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_tag_module
|
||||
@ -107,7 +109,7 @@ def formation_export(
|
||||
in desired format
|
||||
"""
|
||||
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}
|
||||
if not export_external_ues:
|
||||
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
|
||||
(format dumped by formation_export( format='xml' ))
|
||||
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:
|
||||
doc: str, xml data
|
||||
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:
|
||||
formation_id, modules_old2new, ues_old2new
|
||||
"""
|
||||
from app.scodoc import sco_edit_formation
|
||||
|
||||
# log("formation_import_xml: doc=%s" % doc)
|
||||
try:
|
||||
dom = xml.dom.minidom.parseString(doc)
|
||||
except Exception as exc:
|
||||
@ -207,104 +208,130 @@ def formation_import_xml(doc: str, import_tags=True):
|
||||
assert D[0] == "formation"
|
||||
F = D[1]
|
||||
F["dept_id"] = g.scodoc_dept_id
|
||||
referentiel_competence_id = F.get("referentiel_competence_id")
|
||||
# find new version number
|
||||
cnx = ndb.GetDBConnexion()
|
||||
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,
|
||||
# Pour les clonages, on prend le refcomp_id donné:
|
||||
referentiel_competence_id = (
|
||||
F.get("referentiel_competence_id") if use_local_refcomp else None
|
||||
)
|
||||
res = cursor.fetchall()
|
||||
try:
|
||||
version = int(res[0][0]) + 1
|
||||
except (ValueError, IndexError, TypeError):
|
||||
version = 1
|
||||
F["version"] = version
|
||||
# Sinon, on cherche a retrouver le ref. comp.
|
||||
if referentiel_competence_id is None:
|
||||
refcomp_version_orebut = F.get("refcomp_version_orebut")
|
||||
refcomp_specialite = F.get("refcomp_specialite")
|
||||
refcomp_type_titre = F.get("refcomp_type_titre")
|
||||
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
|
||||
# F_unquoted = F.copy()
|
||||
# unescape_html_dict(F_unquoted)
|
||||
formation_id = sco_edit_formation.do_formation_create(F)
|
||||
log(f"formation {formation_id} created")
|
||||
|
||||
ues_old2new = {} # xml ue_id : new ue_id
|
||||
modules_old2new = {} # xml module_id : new module_id
|
||||
# (nb: mecanisme utilise pour cloner semestres seulement, pas pour I/O XML)
|
||||
|
||||
ue_reference_to_id = {} # pour les coefs APC (map reference -> ue_id)
|
||||
modules_a_coefficienter = [] # Liste des modules avec coefs APC
|
||||
# -- create UEs
|
||||
for ue_info in D[2]:
|
||||
assert ue_info[0] == "ue"
|
||||
ue_info[1]["formation_id"] = formation_id
|
||||
if "ue_id" in ue_info[1]:
|
||||
xml_ue_id = int(ue_info[1]["ue_id"])
|
||||
del ue_info[1]["ue_id"]
|
||||
else:
|
||||
xml_ue_id = None
|
||||
ue_id = sco_edit_ue.do_ue_create(ue_info[1])
|
||||
if xml_ue_id:
|
||||
ues_old2new[xml_ue_id] = ue_id
|
||||
# élément optionnel présent dans les exports BUT:
|
||||
ue_reference = ue_info[1].get("reference")
|
||||
if ue_reference:
|
||||
ue_reference_to_id[int(ue_reference)] = ue_id
|
||||
# -- create matieres
|
||||
for mat_info in ue_info[2]:
|
||||
assert mat_info[0] == "matiere"
|
||||
mat_info[1]["ue_id"] = ue_id
|
||||
mat_id = sco_edit_matiere.do_matiere_create(mat_info[1])
|
||||
# -- create modules
|
||||
for mod_info in mat_info[2]:
|
||||
assert mod_info[0] == "module"
|
||||
if "module_id" in mod_info[1]:
|
||||
xml_module_id = int(mod_info[1]["module_id"])
|
||||
del mod_info[1]["module_id"]
|
||||
else:
|
||||
xml_module_id = None
|
||||
mod_info[1]["formation_id"] = formation_id
|
||||
mod_info[1]["matiere_id"] = mat_id
|
||||
mod_info[1]["ue_id"] = ue_id
|
||||
if not "module_type" in mod_info[1]:
|
||||
mod_info[1]["module_type"] = scu.ModuleType.STANDARD
|
||||
mod_id = sco_edit_module.do_module_create(mod_info[1])
|
||||
if xml_module_id:
|
||||
modules_old2new[int(xml_module_id)] = mod_id
|
||||
if len(mod_info) > 2:
|
||||
module: Module = Module.query.get(mod_id)
|
||||
tag_names = []
|
||||
ue_coef_dict = {}
|
||||
for child in mod_info[2]:
|
||||
if child[0] == "tags" and import_tags:
|
||||
tag_names.append(child[1]["name"])
|
||||
elif child[0] == "coefficients":
|
||||
ue_reference = int(child[1]["ue_reference"])
|
||||
coef = float(child[1]["coef"])
|
||||
ue_coef_dict[ue_reference] = coef
|
||||
elif child[0] == "parcours":
|
||||
# associe les parcours de ce module (BUT)
|
||||
code_parcours = child[1]["code"]
|
||||
parcours = ApcParcours.query.filter_by(
|
||||
code=code_parcours,
|
||||
referentiel_id=referentiel_competence_id,
|
||||
).first()
|
||||
if parcours:
|
||||
module.parcours.append(parcours)
|
||||
db.session.add(module)
|
||||
else:
|
||||
log(f"Warning: parcours {code_parcours} inexistant !")
|
||||
if import_tags and tag_names:
|
||||
sco_tag_module.module_tag_set(mod_id, tag_names)
|
||||
if module.is_apc() and ue_coef_dict:
|
||||
modules_a_coefficienter.append((module, ue_coef_dict))
|
||||
# Fixe les coefs APC (à la fin pour que les UE soient crées)
|
||||
for module, ue_coef_dict_ref in modules_a_coefficienter:
|
||||
# remap ue ids:
|
||||
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)
|
||||
db.session.commit()
|
||||
with sco_cache.DeferredSemCacheManager():
|
||||
# -- create UEs
|
||||
for ue_info in D[2]:
|
||||
assert ue_info[0] == "ue"
|
||||
ue_info[1]["formation_id"] = formation_id
|
||||
if "ue_id" in ue_info[1]:
|
||||
xml_ue_id = int(ue_info[1]["ue_id"])
|
||||
del ue_info[1]["ue_id"]
|
||||
else:
|
||||
xml_ue_id = None
|
||||
ue_id = sco_edit_ue.do_ue_create(ue_info[1])
|
||||
if xml_ue_id:
|
||||
ues_old2new[xml_ue_id] = ue_id
|
||||
# élément optionnel présent dans les exports BUT:
|
||||
ue_reference = ue_info[1].get("reference")
|
||||
if ue_reference:
|
||||
ue_reference_to_id[int(ue_reference)] = ue_id
|
||||
# -- create matieres
|
||||
for mat_info in ue_info[2]:
|
||||
assert mat_info[0] == "matiere"
|
||||
mat_info[1]["ue_id"] = ue_id
|
||||
mat_id = sco_edit_matiere.do_matiere_create(mat_info[1])
|
||||
# -- create modules
|
||||
for mod_info in mat_info[2]:
|
||||
assert mod_info[0] == "module"
|
||||
if "module_id" in mod_info[1]:
|
||||
xml_module_id = int(mod_info[1]["module_id"])
|
||||
del mod_info[1]["module_id"]
|
||||
else:
|
||||
xml_module_id = None
|
||||
mod_info[1]["formation_id"] = formation_id
|
||||
mod_info[1]["matiere_id"] = mat_id
|
||||
mod_info[1]["ue_id"] = ue_id
|
||||
if not "module_type" in mod_info[1]:
|
||||
mod_info[1]["module_type"] = scu.ModuleType.STANDARD
|
||||
mod_id = sco_edit_module.do_module_create(mod_info[1])
|
||||
if xml_module_id:
|
||||
modules_old2new[int(xml_module_id)] = mod_id
|
||||
if len(mod_info) > 2:
|
||||
module: Module = Module.query.get(mod_id)
|
||||
tag_names = []
|
||||
ue_coef_dict = {}
|
||||
for child in mod_info[2]:
|
||||
if child[0] == "tags" and import_tags:
|
||||
tag_names.append(child[1]["name"])
|
||||
elif child[0] == "coefficients":
|
||||
ue_reference = int(child[1]["ue_reference"])
|
||||
coef = float(child[1]["coef"])
|
||||
ue_coef_dict[ue_reference] = coef
|
||||
elif child[0] == "parcours":
|
||||
# Si on a un référentiel de compétences,
|
||||
# associe les parcours de ce module (BUT)
|
||||
if referentiel_competence_id:
|
||||
code_parcours = child[1]["code"]
|
||||
parcours = ApcParcours.query.filter_by(
|
||||
code=code_parcours,
|
||||
referentiel_id=referentiel_competence_id,
|
||||
).first()
|
||||
if parcours:
|
||||
module.parcours.append(parcours)
|
||||
db.session.add(module)
|
||||
else:
|
||||
log(
|
||||
f"Warning: parcours {code_parcours} inexistant !"
|
||||
)
|
||||
if import_tags and tag_names:
|
||||
sco_tag_module.module_tag_set(mod_id, tag_names)
|
||||
if module.is_apc() and ue_coef_dict:
|
||||
modules_a_coefficienter.append((module, ue_coef_dict))
|
||||
# Fixe les coefs APC (à la fin pour que les UE soient créées)
|
||||
for module, ue_coef_dict_ref in modules_a_coefficienter:
|
||||
# remap ue ids:
|
||||
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)
|
||||
db.session.commit()
|
||||
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)
|
||||
resp = formation_export(formation_id, export_ids=True, format="xml")
|
||||
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
|
||||
ScolarNews.add(
|
||||
typ=ScolarNews.NEWS_FORM,
|
||||
|
@ -1018,6 +1018,7 @@ a.discretelink:hover {
|
||||
|
||||
.help {
|
||||
font-style: italic;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.help_important {
|
||||
|
@ -652,14 +652,6 @@ def formation_export(formation_id, export_ids=False, format=None):
|
||||
@scodoc7func
|
||||
def formation_import_xml_form():
|
||||
"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(
|
||||
request.base_url,
|
||||
scu.get_request_args(),
|
||||
@ -668,7 +660,15 @@ def formation_import_xml_form():
|
||||
cancelbutton="Annuler",
|
||||
)
|
||||
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:
|
||||
return flask.redirect(scu.NotesURL())
|
||||
else:
|
||||
@ -676,13 +676,14 @@ def formation_import_xml_form():
|
||||
tf[2]["xmlfile"].read()
|
||||
)
|
||||
|
||||
return (
|
||||
"\n".join(H)
|
||||
+ """<p>Import effectué !</p>
|
||||
<p><a class="stdlink" href="ue_list?formation_id=%s">Voir la formation</a></p>"""
|
||||
% formation_id
|
||||
+ footer
|
||||
)
|
||||
return f"""
|
||||
{ html_sco_header.sco_header(page_title="Import d'une formation") }
|
||||
<h2>Import effectué !</h2>
|
||||
<p><a class="stdlink" href="{
|
||||
url_for("notes.ue_list", scodoc_dept=g.scodoc_dept, formation_id=formation_id
|
||||
)}">Voir la formation</a></p>
|
||||
{ html_sco_header.sco_footer() }
|
||||
"""
|
||||
|
||||
|
||||
sco_publish(
|
||||
|
Loading…
Reference in New Issue
Block a user