2020-09-26 16:19:37 +02:00
|
|
|
# -*- mode: python -*-
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# Gestion scolarite IUT
|
|
|
|
#
|
2021-01-01 17:51:08 +01:00
|
|
|
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
|
2020-09-26 16:19:37 +02:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
#
|
|
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
|
|
|
"""Import / Export de formations
|
|
|
|
"""
|
|
|
|
from operator import itemgetter
|
2021-02-03 22:00:41 +01:00
|
|
|
import xml.dom.minidom
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
from sco_utils import *
|
|
|
|
|
2021-02-03 22:00:41 +01:00
|
|
|
import notesdb as ndb
|
2020-09-26 16:19:37 +02:00
|
|
|
from notes_log import log
|
|
|
|
import sco_codes_parcours
|
|
|
|
import sco_formsemestre
|
|
|
|
import sco_tag_module
|
|
|
|
from gen_tables import GenTable
|
2021-02-03 22:00:41 +01:00
|
|
|
from sco_exceptions import ScoValueError
|
|
|
|
from sco_permissions import ScoChangeFormation
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def formation_export(
|
|
|
|
context, formation_id, export_ids=False, export_tags=True, format=None, REQUEST=None
|
|
|
|
):
|
|
|
|
"""Get a formation, with UE, matieres, modules
|
|
|
|
in desired format
|
|
|
|
"""
|
|
|
|
F = context.formation_list(args={"formation_id": formation_id})[0]
|
|
|
|
ues = context.do_ue_list({"formation_id": formation_id})
|
|
|
|
F["ue"] = ues
|
|
|
|
for ue in ues:
|
|
|
|
ue_id = ue["ue_id"]
|
|
|
|
if not export_ids:
|
|
|
|
del ue["ue_id"]
|
|
|
|
del ue["formation_id"]
|
|
|
|
if ue["ects"] is None:
|
|
|
|
del ue["ects"]
|
|
|
|
mats = context.do_matiere_list({"ue_id": ue_id})
|
|
|
|
ue["matiere"] = mats
|
|
|
|
for mat in mats:
|
|
|
|
matiere_id = mat["matiere_id"]
|
|
|
|
if not export_ids:
|
|
|
|
del mat["matiere_id"]
|
|
|
|
del mat["ue_id"]
|
|
|
|
mods = context.do_module_list({"matiere_id": matiere_id})
|
|
|
|
mat["module"] = mods
|
|
|
|
for mod in mods:
|
|
|
|
if export_tags:
|
|
|
|
# mod['tags'] = sco_tag_module.module_tag_list(context, module_id=mod['module_id'])
|
|
|
|
tags = sco_tag_module.module_tag_list(
|
|
|
|
context, module_id=mod["module_id"]
|
|
|
|
)
|
|
|
|
if tags:
|
|
|
|
mod["tags"] = [{"name": x} for x in tags]
|
|
|
|
if not export_ids:
|
|
|
|
del mod["ue_id"]
|
|
|
|
del mod["matiere_id"]
|
|
|
|
del mod["module_id"]
|
|
|
|
del mod["formation_id"]
|
|
|
|
if mod["ects"] is None:
|
|
|
|
del mod["ects"]
|
|
|
|
|
|
|
|
return sendResult(
|
|
|
|
REQUEST, F, name="formation", format=format, force_outer_xml_tag=False
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
ELEMENT_NODE = 1
|
|
|
|
TEXT_NODE = 3
|
|
|
|
|
|
|
|
|
|
|
|
def XMLToDicts(element, encoding):
|
|
|
|
"""Represent dom element as a dict
|
|
|
|
Example:
|
|
|
|
<foo x="1" y="2"><bar z="2"/></foo>
|
|
|
|
will give us:
|
|
|
|
('foo', {'y': '2', 'x': '1'}, [('bar', {'z': '2'}, [])])
|
|
|
|
"""
|
|
|
|
d = {}
|
|
|
|
# attributes
|
|
|
|
if element.attributes:
|
|
|
|
for i in range(len(element.attributes)):
|
|
|
|
a = element.attributes.item(i).nodeName.encode(encoding)
|
|
|
|
v = element.getAttribute(element.attributes.item(i).nodeName)
|
|
|
|
d[a] = v.encode(encoding)
|
|
|
|
# descendants
|
|
|
|
childs = []
|
|
|
|
for child in element.childNodes:
|
|
|
|
if child.nodeType == ELEMENT_NODE:
|
|
|
|
childs.append(XMLToDicts(child, encoding))
|
|
|
|
return (element.nodeName.encode(encoding), d, childs)
|
|
|
|
|
|
|
|
|
|
|
|
def formation_import_xml(
|
|
|
|
context, REQUEST, doc, import_tags=True, encoding=SCO_ENCODING
|
|
|
|
):
|
|
|
|
"""Create a formation from XML representation
|
|
|
|
(format dumped by formation_export( format='xml' ))
|
|
|
|
"""
|
|
|
|
log("formation_import_xml: doc=%s" % doc)
|
|
|
|
try:
|
|
|
|
dom = xml.dom.minidom.parseString(doc)
|
|
|
|
except:
|
|
|
|
log("formation_import_xml: invalid XML data")
|
|
|
|
raise ScoValueError("Fichier XML invalide")
|
|
|
|
|
|
|
|
f = dom.getElementsByTagName("formation")[0] # or dom.documentElement
|
|
|
|
D = XMLToDicts(f, encoding)
|
|
|
|
assert D[0] == "formation"
|
|
|
|
F = D[1]
|
|
|
|
F_quoted = F.copy()
|
|
|
|
log("F=%s" % F)
|
2021-02-03 22:00:41 +01:00
|
|
|
ndb.quote_dict(F_quoted)
|
2020-09-26 16:19:37 +02:00
|
|
|
log("F_quoted=%s" % F_quoted)
|
|
|
|
# find new version number
|
|
|
|
cnx = context.GetDBConnexion()
|
2021-02-03 22:00:41 +01:00
|
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
2020-09-26 16:19:37 +02:00
|
|
|
log(
|
|
|
|
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s"
|
|
|
|
% F_quoted
|
|
|
|
)
|
|
|
|
cursor.execute(
|
|
|
|
"select max(version) from notes_formations where acronyme=%(acronyme)s and titre=%(titre)s",
|
|
|
|
F_quoted,
|
|
|
|
)
|
|
|
|
res = cursor.fetchall()
|
|
|
|
try:
|
|
|
|
version = int(res[0][0]) + 1
|
|
|
|
except:
|
|
|
|
version = 1
|
|
|
|
F["version"] = version
|
|
|
|
# create formation
|
|
|
|
# F_unquoted = F.copy()
|
|
|
|
# unescape_html_dict(F_unquoted)
|
|
|
|
formation_id = context.do_formation_create(F, REQUEST)
|
|
|
|
log("formation %s created" % formation_id)
|
|
|
|
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)
|
|
|
|
# -- 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 = ue_info[1]["ue_id"]
|
|
|
|
del ue_info[1]["ue_id"]
|
|
|
|
else:
|
|
|
|
xml_ue_id = None
|
|
|
|
ue_id = context.do_ue_create(ue_info[1], REQUEST)
|
|
|
|
if xml_ue_id:
|
|
|
|
ues_old2new[xml_ue_id] = 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 = context.do_matiere_create(mat_info[1], REQUEST)
|
|
|
|
# -- create modules
|
|
|
|
for mod_info in mat_info[2]:
|
|
|
|
assert mod_info[0] == "module"
|
|
|
|
if "module_id" in mod_info[1]:
|
|
|
|
xml_module_id = 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
|
|
|
|
mod_id = context.do_module_create(mod_info[1], REQUEST)
|
|
|
|
if xml_module_id:
|
|
|
|
modules_old2new[xml_module_id] = mod_id
|
|
|
|
if import_tags:
|
|
|
|
if len(mod_info) > 2:
|
|
|
|
tag_names = [t[1]["name"] for t in mod_info[2]]
|
|
|
|
sco_tag_module.module_tag_set(context, mod_id, tag_names)
|
|
|
|
|
|
|
|
return formation_id, modules_old2new, ues_old2new
|
|
|
|
|
|
|
|
|
|
|
|
def formation_list_table(context, formation_id=None, args={}, REQUEST=None):
|
|
|
|
"""List formation, grouped by titre and sorted by versions
|
|
|
|
and listing associated semestres
|
|
|
|
returns a table
|
|
|
|
"""
|
|
|
|
formations = context.formation_list(formation_id=formation_id, args=args)
|
|
|
|
title = "Programmes pédagogiques"
|
|
|
|
lockicon = icontag(
|
|
|
|
"lock32_img", title="Comporte des semestres verrouillés", border="0"
|
|
|
|
)
|
|
|
|
suppricon = icontag(
|
|
|
|
"delete_small_img", border="0", alt="supprimer", title="Supprimer"
|
|
|
|
)
|
|
|
|
editicon = icontag(
|
|
|
|
"edit_img", border="0", alt="modifier", title="Modifier titres et code"
|
|
|
|
)
|
|
|
|
|
|
|
|
editable = REQUEST.AUTHENTICATED_USER.has_permission(ScoChangeFormation, context)
|
|
|
|
|
|
|
|
# Traduit/ajoute des champs à afficher:
|
|
|
|
for f in formations:
|
|
|
|
try:
|
|
|
|
f["parcours_name"] = sco_codes_parcours.get_parcours_from_code(
|
|
|
|
f["type_parcours"]
|
|
|
|
).NAME
|
|
|
|
except:
|
|
|
|
f["parcours_name"] = ""
|
|
|
|
f["_titre_target"] = "ue_list?formation_id=%(formation_id)s" % f
|
|
|
|
f["_titre_link_class"] = "stdlink"
|
|
|
|
# Ajoute les semestres associés à chaque formation:
|
|
|
|
f["sems"] = sco_formsemestre.do_formsemestre_list(
|
|
|
|
context, args={"formation_id": f["formation_id"]}
|
|
|
|
)
|
|
|
|
f["sems_list_txt"] = ", ".join([s["session_id"] for s in f["sems"]])
|
|
|
|
f["_sems_list_txt_html"] = ", ".join(
|
|
|
|
[
|
|
|
|
'<a class="discretelink" href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(session_id)s<a>'
|
|
|
|
% s
|
|
|
|
for s in f["sems"]
|
|
|
|
]
|
|
|
|
+ [
|
|
|
|
'<a class="stdlink" href="formsemestre_createwithmodules?formation_id=%(formation_id)s&semestre_id=1">ajouter</a>'
|
|
|
|
% f
|
|
|
|
]
|
|
|
|
)
|
|
|
|
if f["sems"]:
|
|
|
|
f["date_fin_dernier_sem"] = max([s["date_fin_iso"] for s in f["sems"]])
|
|
|
|
f["annee_dernier_sem"] = f["date_fin_dernier_sem"].split("-")[0]
|
|
|
|
else:
|
|
|
|
f["date_fin_dernier_sem"] = ""
|
|
|
|
f["annee_dernier_sem"] = ""
|
|
|
|
locked = context.formation_has_locked_sems(f["formation_id"])
|
|
|
|
#
|
|
|
|
if locked:
|
|
|
|
but_locked = lockicon
|
|
|
|
else:
|
|
|
|
but_locked = '<span class="but_placeholder"></span>'
|
|
|
|
if editable and not locked:
|
|
|
|
but_suppr = (
|
|
|
|
'<a class="stdlink" href="formation_delete?formation_id=%s">%s</a>'
|
|
|
|
% (f["formation_id"], suppricon)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
but_suppr = '<span class="but_placeholder"></span>'
|
|
|
|
if editable:
|
|
|
|
but_edit = (
|
|
|
|
'<a class="stdlink" href="formation_edit?formation_id=%s">%s</a>'
|
|
|
|
% (f["formation_id"], editicon)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
but_edit = '<span class="but_placeholder"></span>'
|
|
|
|
f["buttons"] = ""
|
|
|
|
f["_buttons_html"] = but_locked + but_suppr + but_edit
|
|
|
|
# Tri par annee_denier_sem, type, acronyme, titre, version décroissante
|
|
|
|
formations.sort(key=itemgetter("version"), reverse=True)
|
|
|
|
formations.sort(key=itemgetter("titre"))
|
|
|
|
formations.sort(key=itemgetter("acronyme"))
|
|
|
|
formations.sort(key=itemgetter("parcours_name"))
|
|
|
|
formations.sort(
|
|
|
|
key=itemgetter("annee_dernier_sem"), reverse=True
|
|
|
|
) # plus recemments utilises en tete
|
|
|
|
|
|
|
|
#
|
|
|
|
columns_ids = (
|
|
|
|
"buttons",
|
|
|
|
"acronyme",
|
|
|
|
"parcours_name",
|
|
|
|
"formation_code",
|
|
|
|
"version",
|
|
|
|
"titre",
|
|
|
|
"sems_list_txt",
|
|
|
|
)
|
|
|
|
titles = {
|
|
|
|
"buttons": "",
|
|
|
|
"acronyme": "Acro.",
|
|
|
|
"parcours_name": "Type",
|
|
|
|
"titre": "Titre",
|
|
|
|
"version": "Version",
|
|
|
|
"formation_code": "Code",
|
|
|
|
"sems_list_txt": "Semestres",
|
|
|
|
}
|
|
|
|
return GenTable(
|
|
|
|
columns_ids=columns_ids,
|
|
|
|
rows=formations,
|
|
|
|
titles=titles,
|
|
|
|
origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
|
|
|
|
caption=title,
|
|
|
|
html_caption=title,
|
|
|
|
table_id="formation_list_table",
|
|
|
|
html_class="formation_list_table table_leftalign",
|
|
|
|
html_with_td_classes=True,
|
|
|
|
html_sortable=True,
|
|
|
|
base_url="%s?formation_id=%s" % (REQUEST.URL0, formation_id),
|
|
|
|
page_title=title,
|
|
|
|
pdf_title=title,
|
|
|
|
preferences=context.get_preferences(),
|
|
|
|
)
|