# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. # # 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 import xml.dom.minidom import app.scodoc.sco_utils as scu import app.scodoc.notesdb as ndb from app.scodoc.notes_log import log 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_formsemestre from app.scodoc import sco_news from app.scodoc import sco_preferences from app.scodoc import sco_tag_module from app.scodoc import VERSION from app.scodoc.gen_tables import GenTable from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission _formationEditor = ndb.EditableTable( "notes_formations", "formation_id", ( "formation_id", "acronyme", "titre", "titre_officiel", "version", "formation_code", "type_parcours", "code_specialite", ), sortkey="acronyme", ) def formation_list(context, formation_id=None, args={}): """List formation(s) with given id, or matching args (when args is given, formation_id is ignored). """ if not args: if formation_id is None: args = {} else: args = {"formation_id": formation_id} cnx = ndb.GetDBConnexion() r = _formationEditor.list(cnx, args=args) # log('%d formations found' % len(r)) return r def formation_has_locked_sems(context, formation_id): "True if there is a locked formsemestre in this formation" sems = sco_formsemestre.do_formsemestre_list( context, args={"formation_id": formation_id, "etat": "0"} ) return sems 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 = formation_list(context, args={"formation_id": formation_id})[0] ues = sco_edit_ue.do_ue_list(context, {"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 = sco_edit_matiere.do_matiere_list(context, {"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 = sco_edit_module.do_module_list(context, {"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 scu.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: 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=scu.SCO_ENCODING ): """Create a formation from XML representation (format dumped by formation_export( format='xml' )) """ from app.scodoc import sco_edit_formation 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) ndb.quote_dict(F_quoted) log("F_quoted=%s" % F_quoted) # find new version number cnx = ndb.GetDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) 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 = sco_edit_formation.do_formation_create(context, 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 = sco_edit_ue.do_ue_create(context, 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 = sco_edit_matiere.do_matiere_create(context, 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 = sco_edit_module.do_module_create(context, 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 = formation_list(context, formation_id=formation_id, args=args) title = "Programmes pédagogiques" lockicon = scu.icontag( "lock32_img", title="Comporte des semestres verrouillés", border="0" ) suppricon = scu.icontag( "delete_small_img", border="0", alt="supprimer", title="Supprimer" ) editicon = scu.icontag( "edit_img", border="0", alt="modifier", title="Modifier titres et code" ) editable = REQUEST.AUTHENTICATED_USER.has_permission(Permission.ScoChangeFormation) # 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( [ '%(session_id)s' % s for s in f["sems"] ] + [ 'ajouter' % (f["acronyme"].lower().replace(" ","-"), f["formation_id"]) ] ) 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 = formation_has_locked_sems(context, f["formation_id"]) # if locked: but_locked = lockicon else: but_locked = '' if editable and not locked: but_suppr = ( '%s' % (f["acronyme"].lower().replace(" ", "-"), f["formation_id"], suppricon) ) else: but_suppr = '' if editable: but_edit = ( '%s' % (f["acronyme"].lower().replace(" ","-"), f["formation_id"], editicon) ) else: but_edit = '' 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 + scu.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=sco_preferences.SemPreferences( context, ), ) def formation_create_new_version(context, formation_id, redirect=True, REQUEST=None): "duplicate formation, with new version number" xml = formation_export(context, formation_id, export_ids=True, format="xml") new_id, modules_old2new, ues_old2new = formation_import_xml(context, REQUEST, xml) # news F = formation_list(context, args={"formation_id": new_id})[0] sco_news.add( context, REQUEST, typ=sco_news.NEWS_FORM, object=new_id, text="Nouvelle version de la formation %(acronyme)s" % F, ) if redirect: return REQUEST.RESPONSE.redirect( "ue_list?formation_id=" + new_id + "&msg=Nouvelle version !" ) else: return new_id, modules_old2new, ues_old2new