1
0
forked from ScoDoc/ScoDoc

migration exports xml

This commit is contained in:
Emmanuel Viennet 2021-07-10 13:55:35 +02:00
parent 6f885edfe4
commit dc726f1d10
9 changed files with 262 additions and 198 deletions

View File

@ -58,11 +58,28 @@ Installation:
donc utiliser: donc utiliser:
pip install -r requirements.txt pip install -r requirements-2.7.txt
pour régénerer ce fichier: pour régénerer ce fichier:
pip freeze > requirements.txt pip freeze > requirements-2.7.txt
## Setup python3.7
Debian 10 est livré avec Python 3.7.
apt-get install python3-dev
apt-get install python3-venv
python3 -m venv venv
Puis installation de Flask:
source venv/bin/activate
pip install flask
pip install wheel
Installer les dépendances:
pip install -r requirements-2.7.txt
## Bases de données ## Bases de données
ScoDoc8 utilise les bases de département de ScoDoc7, mais une nouvelle base ScoDoc8 utilise les bases de département de ScoDoc7, mais une nouvelle base

View File

@ -43,11 +43,10 @@ Par exemple, la clé '_css_row_class' spécifie le style CSS de la ligne.
from __future__ import print_function from __future__ import print_function
import random import random
from collections import OrderedDict from collections import OrderedDict
from xml.etree import ElementTree
# XML generation package (apt-get install jaxml)
import jaxml
import json import json
from xml.etree import ElementTree
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
from reportlab.lib.colors import Color from reportlab.lib.colors import Color
@ -59,6 +58,7 @@ from app.scodoc import html_sco_header
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc import sco_excel from app.scodoc import sco_excel
from app.scodoc import sco_pdf from app.scodoc import sco_pdf
from app.scodoc import sco_xml
from app.scodoc.sco_pdf import SU from app.scodoc.sco_pdf import SU
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
@ -567,28 +567,25 @@ class GenTable(object):
The tag names <table> and <row> can be changed using The tag names <table> and <row> can be changed using
xml_outer_tag and xml_row_tag xml_outer_tag and xml_row_tag
""" """
doc = jaxml.XML_document(encoding=scu.SCO_ENCODING) doc = ElementTree.Element(
getattr(doc, self.xml_outer_tag)( self.xml_outer_tag,
id=self.table_id, origin=self.origin or "", caption=self.caption or "" id=self.table_id,
origin=self.origin or "",
caption=self.caption or "",
) )
doc._push()
for row in self.rows: for row in self.rows:
doc._push() x_row = ElementTree.Element(self.xml_row_tag)
row_title = row.get("row_title", "") row_title = row.get("row_title", "")
if row_title: if row_title:
getattr(doc, self.xml_row_tag)(title=row_title) x_row.set("title", row_title)
else: doc.append(x_row)
getattr(doc, self.xml_row_tag)()
for cid in self.columns_ids: for cid in self.columns_ids:
doc._push()
v = row.get(cid, "") v = row.get(cid, "")
if v is None: if v is None:
v = "" v = ""
getattr(doc, cid)(value=str(v)) x_cell = ElementTree.Element(cid, value=str(v))
doc._pop() x_row.append(x_cell)
doc._pop() return sco_xml.XML_HEADER + ElementTree.tostring(doc)
doc._pop()
return repr(doc)
def json(self): def json(self):
"""JSON representation of the table.""" """JSON representation of the table."""

View File

@ -102,7 +102,7 @@ import app.scodoc.notesdb as ndb
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
from app.scodoc.sco_exceptions import ScoValueError, FormatError from app.scodoc.sco_exceptions import ScoValueError, FormatError
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_formsemestre import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_codes_parcours import code_semestre_validant from app.scodoc.sco_codes_parcours import code_semestre_validant
from app.scodoc.sco_codes_parcours import ( from app.scodoc.sco_codes_parcours import (
ADC, ADC,

View File

@ -38,7 +38,7 @@ from app.scodoc import sco_users
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID from app.scodoc.sco_codes_parcours import NO_SEMESTRE_ID
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_vdi import ApoEtapeVDI
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
@ -385,100 +385,6 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename):
cnx.commit() cnx.commit()
# ------ Utilisé pour stocker le VDI avec le code étape (noms de fichiers maquettes et code semestres)
class ApoEtapeVDI(object):
_ETAPE_VDI_SEP = "!"
def __init__(self, etape_vdi=None, etape="", vdi=""):
"""Build from string representation, e.g. 'V1RT!111'"""
if etape_vdi:
self.etape_vdi = etape_vdi
self.etape, self.vdi = self.split_etape_vdi(etape_vdi)
elif etape:
if self._ETAPE_VDI_SEP in etape:
raise ScoValueError("valeur code etape invalide")
self.etape, self.vdi = etape, vdi
self.etape_vdi = self.concat_etape_vdi(etape, vdi)
else:
self.etape_vdi, self.etape, self.vdi = "", "", ""
def __repr__(self):
return self.__class__.__name__ + "('" + str(self) + "')"
def __str__(self):
return self.etape_vdi
def _cmp(self, other):
"""Test égalité de deux codes étapes.
Si le VDI des deux est spécifié, on l'utilise. Sinon, seul le code étape est pris en compte.
Donc V1RT == V1RT!111, V1RT!110 == V1RT, V1RT!77 != V1RT!78, ...
Compare the two objects x (=self) and y and return an integer according to
the outcome. The return value is negative if x < y, zero if x == y
and strictly positive if x > y.
"""
if other is None:
return -1
if type(other) == str:
other = ApoEtapeVDI(other)
if self.vdi and other.vdi:
x = (self.etape, self.vdi)
y = (other.etape, other.vdi)
else:
x = self.etape
y = other.etape
return (x > y) - (x < y)
def __eq__(self, other):
return self._cmp(other) == 0
def __ne__(self, other):
return self._cmp(other) != 0
def __lt__(self, other):
return self._cmp(other) < 0
def __le__(self, other):
return self._cmp(other) <= 0
def __gt__(self, other):
return self._cmp(other) > 0
def __ge__(self, other):
return self._cmp(other) >= 0
def split_etape_vdi(self, etape_vdi):
"""Etape Apogee can be stored as 'V1RT' or, including the VDI version,
as 'V1RT!111'
Returns etape, VDI
"""
if etape_vdi:
t = etape_vdi.split(self._ETAPE_VDI_SEP)
if len(t) == 1:
etape = etape_vdi
vdi = ""
elif len(t) == 2:
etape, vdi = t
else:
raise ValueError("invalid code etape")
return etape, vdi
else:
return etape_vdi, ""
def concat_etape_vdi(self, etape, vdi=""):
if vdi:
return self._ETAPE_VDI_SEP.join([etape, vdi])
else:
return etape
"""
[ ApoEtapeVDI('V1RT!111'), ApoEtapeVDI('V1RT!112'), ApoEtapeVDI('VCRT'), ApoEtapeVDI('V1RT') ]
"""
def sem_set_responsable_name(context, sem): def sem_set_responsable_name(context, sem):
"ajoute champs responsable_name" "ajoute champs responsable_name"
sem["responsable_name"] = ", ".join( sem["responsable_name"] = ", ".join(

View File

@ -37,8 +37,8 @@ from app.scodoc import sco_groups
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF from app.scodoc.TrivialFormulator import TrivialFormulator, TF
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_formsemestre import ApoEtapeVDI
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc import html_sco_header from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours from app.scodoc import sco_codes_parcours
from app.scodoc import sco_compute_moy from app.scodoc import sco_compute_moy

View File

@ -50,7 +50,7 @@ from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
from app.scodoc.sco_etape_bilan import EtapeBilan from app.scodoc.sco_etape_bilan import EtapeBilan
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_formsemestre import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu

View File

@ -43,10 +43,10 @@ import time
import types import types
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
import xml.sax.saxutils
# XML generation package (apt-get install jaxml) # XML generation package (apt-get install jaxml)
import jaxml import jaxml # XXX
try: try:
import six import six
@ -66,8 +66,11 @@ from config import Config
from app.scodoc.SuppressAccents import suppression_diacritics from app.scodoc.SuppressAccents import suppression_diacritics
from app.scodoc.notes_log import log from app.scodoc.notes_log import log
from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_xml import quote_xml_attr
from app.scodoc.sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL from app.scodoc.sco_codes_parcours import NOTES_TOLERANCE, CODES_EXPL
from app.scodoc import sco_exceptions from app.scodoc import sco_exceptions
from app.scodoc import sco_xml
from app.scodoc import VERSION from app.scodoc import VERSION
# ----- TEMPORAIRE POUR MIGRATION SCODOC7 -> SCODOC8 avant python3 # ----- TEMPORAIRE POUR MIGRATION SCODOC7 -> SCODOC8 avant python3
@ -445,74 +448,6 @@ def unescape_html_dict(d):
unescape_html_dict(v) unescape_html_dict(v)
def quote_xml_attr(data):
"""Escape &, <, >, quotes and double quotes"""
return xml.sax.saxutils.escape(str(data), {"'": "&apos;", '"': "&quot;"})
def dict_quote_xml_attr(d, fromhtml=False):
"""Quote XML entities in dict values.
Non recursive (but probbaly should be...).
Returns a new dict.
"""
if fromhtml:
# passe d'un code HTML a un code XML
return dict([(k, quote_xml_attr(unescape_html(v))) for (k, v) in d.items()])
else:
# passe d'une chaine non quotée a du XML
return dict([(k, quote_xml_attr(v)) for (k, v) in d.items()])
def simple_dictlist2xml(dictlist, doc=None, tagname=None, quote=False):
"""Represent a dict as XML data.
All keys with string or numeric values are attributes (numbers converted to strings).
All list values converted to list of childs (recursively).
*** all other values are ignored ! ***
Values (xml entities) are not quoted, except if requested by quote argument.
Exemple:
simple_dictlist2xml([ { 'id' : 1, 'ues' : [{'note':10},{}] } ], tagname='infos')
<?xml version="1.0" encoding="utf-8"?>
<infos id="1">
<ues note="10" />
<ues />
</infos>
"""
if not tagname:
raise ValueError("invalid empty tagname !")
if not doc:
doc = jaxml.XML_document(encoding=SCO_ENCODING)
scalar_types = [bytes, str, int, float]
for d in dictlist:
doc._push()
if (
type(d) == types.InstanceType or type(d) in scalar_types
): # pour ApoEtapeVDI et listes de chaines
getattr(doc, tagname)(code=str(d))
else:
if quote:
d_scalar = dict(
[
(k, quote_xml_attr(v))
for (k, v) in d.items()
if type(v) in scalar_types
]
)
else:
d_scalar = dict(
[(k, v) for (k, v) in d.items() if type(v) in scalar_types]
)
getattr(doc, tagname)(**d_scalar)
d_list = dict([(k, v) for (k, v) in d.items() if type(v) == list])
if d_list:
for (k, v) in d_list.items():
simple_dictlist2xml(v, doc=doc, tagname=k, quote=quote)
doc._pop()
return doc
# Expressions used to check noms/prenoms # Expressions used to check noms/prenoms
FORBIDDEN_CHARS_EXP = re.compile(r"[*\|~\(\)\\]") FORBIDDEN_CHARS_EXP = re.compile(r"[*\|~\(\)\\]")
ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE) ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE)
@ -615,15 +550,13 @@ def sendPDFFile(REQUEST, data, filename):
class ScoDocJSONEncoder(json.JSONEncoder): class ScoDocJSONEncoder(json.JSONEncoder):
def default(self, o): # pylint: disable=E0202 def default(self, o): # pylint: disable=E0202
from app.scodoc import sco_formsemestre
# ScoDoc 7.22 n'utilise plus mx: # ScoDoc 7.22 n'utilise plus mx:
if str(type(o)) == "<type 'mx.DateTime.DateTime'>": if str(type(o)) == "<type 'mx.DateTime.DateTime'>":
log("Warning: mx.DateTime object detected !") log("Warning: mx.DateTime object detected !")
return o.strftime("%Y-%m-%dT%H:%M:%S") return o.strftime("%Y-%m-%dT%H:%M:%S")
if isinstance(o, (datetime.date, datetime.datetime)): if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat() return o.isoformat()
elif isinstance(o, sco_formsemestre.ApoEtapeVDI): elif isinstance(o, ApoEtapeVDI):
return str(o) return str(o)
else: else:
return json.JSONEncoder.default(self, o) return json.JSONEncoder.default(self, o)
@ -641,14 +574,9 @@ def sendXML(REQUEST, data, tagname=None, force_outer_xml_tag=True):
data = [data] # always list-of-dicts data = [data] # always list-of-dicts
if force_outer_xml_tag: if force_outer_xml_tag:
root_tagname = tagname + "_list" root_tagname = tagname + "_list"
doc = jaxml.XML_document(encoding=SCO_ENCODING) data = [{root_tagname: data}]
getattr(doc, root_tagname)() doc = sco_xml.simple_dictlist2xml(data, tagname=tagname)
doc._push()
else:
doc = None
doc = simple_dictlist2xml(data, doc=doc, tagname=tagname)
if force_outer_xml_tag:
doc._pop()
if REQUEST: if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
return repr(doc) return repr(doc)

121
app/scodoc/sco_vdi.py Normal file
View File

@ -0,0 +1,121 @@
# -*- 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
#
##############################################################################
"""Classe stockant le VDI avec le code étape (noms de fichiers maquettes et code semestres)
"""
from app.scodoc.sco_exceptions import ScoValueError
class ApoEtapeVDI(object):
_ETAPE_VDI_SEP = "!"
def __init__(self, etape_vdi=None, etape="", vdi=""):
"""Build from string representation, e.g. 'V1RT!111'"""
if etape_vdi:
self.etape_vdi = etape_vdi
self.etape, self.vdi = self.split_etape_vdi(etape_vdi)
elif etape:
if self._ETAPE_VDI_SEP in etape:
raise ScoValueError("valeur code etape invalide")
self.etape, self.vdi = etape, vdi
self.etape_vdi = self.concat_etape_vdi(etape, vdi)
else:
self.etape_vdi, self.etape, self.vdi = "", "", ""
def __repr__(self):
return self.__class__.__name__ + "('" + str(self) + "')"
def __str__(self):
return self.etape_vdi
def _cmp(self, other):
"""Test égalité de deux codes étapes.
Si le VDI des deux est spécifié, on l'utilise. Sinon, seul le code étape est pris en compte.
Donc V1RT == V1RT!111, V1RT!110 == V1RT, V1RT!77 != V1RT!78, ...
Compare the two objects x (=self) and y and return an integer according to
the outcome. The return value is negative if x < y, zero if x == y
and strictly positive if x > y.
"""
if other is None:
return -1
if type(other) == str:
other = ApoEtapeVDI(other)
if self.vdi and other.vdi:
x = (self.etape, self.vdi)
y = (other.etape, other.vdi)
else:
x = self.etape
y = other.etape
return (x > y) - (x < y)
def __eq__(self, other):
return self._cmp(other) == 0
def __ne__(self, other):
return self._cmp(other) != 0
def __lt__(self, other):
return self._cmp(other) < 0
def __le__(self, other):
return self._cmp(other) <= 0
def __gt__(self, other):
return self._cmp(other) > 0
def __ge__(self, other):
return self._cmp(other) >= 0
def split_etape_vdi(self, etape_vdi):
"""Etape Apogee can be stored as 'V1RT' or, including the VDI version,
as 'V1RT!111'
Returns etape, VDI
"""
if etape_vdi:
t = etape_vdi.split(self._ETAPE_VDI_SEP)
if len(t) == 1:
etape = etape_vdi
vdi = ""
elif len(t) == 2:
etape, vdi = t
else:
raise ValueError("invalid code etape")
return etape, vdi
else:
return etape_vdi, ""
def concat_etape_vdi(self, etape, vdi=""):
if vdi:
return self._ETAPE_VDI_SEP.join([etape, vdi])
else:
return etape
# [ ApoEtapeVDI('V1RT!111'), ApoEtapeVDI('V1RT!112'), ApoEtapeVDI('VCRT'), ApoEtapeVDI('V1RT') ]

95
app/scodoc/sco_xml.py Normal file
View File

@ -0,0 +1,95 @@
# -*- 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
#
##############################################################################
""" Exports XML
"""
from xml.etree import ElementTree
import xml.sax.saxutils
from app.scodoc.sco_vdi import ApoEtapeVDI
XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>\n"""
def quote_xml_attr(data):
"""Escape &, <, >, quotes and double quotes"""
return xml.sax.saxutils.escape(str(data), {"'": "&apos;", '"': "&quot;"})
# ScoDoc7 legacy function:
def simple_dictlist2xml(dictlist, doc=None, tagname=None, quote=False):
"""Represent a dict as XML data.
All keys with string or numeric values are attributes (numbers converted to strings).
All list values converted to list of childs (recursively).
*** all other values are ignored ! ***
Values (xml entities) are not quoted, except if requested by quote argument.
Exemple:
simple_dictlist2xml([ { 'id' : 1, 'ues' : [{'note':10},{}] } ], tagname='infos')
<?xml version="1.0" encoding="utf-8"?>
<infos id="1">
<ues note="10" />
<ues />
</infos>
"""
if not tagname:
raise ValueError("invalid empty tagname !")
elements = _dictlist2xml(dictlist, root=[], tagname=tagname, quote=quote)
return XML_HEADER + "\n".join([ElementTree.tostring(x) for x in elements])
def _dictlist2xml(dictlist, root=None, tagname=None, quote=False):
scalar_types = (bytes, str, int, float)
for d in dictlist:
elem = ElementTree.Element(tagname)
root.append(elem)
if isinstance(d, scalar_types) or isinstance(d, ApoEtapeVDI):
elem.set("code", str(d))
else:
if quote:
d_scalar = dict(
[
(k, quote_xml_attr(v))
for (k, v) in d.items()
if isinstance(v, scalar_types)
]
)
else:
d_scalar = dict(
[(k, str(v)) for (k, v) in d.items() if isinstance(v, scalar_types)]
)
for k in d_scalar:
elem.set(k, d_scalar[k])
d_list = dict([(k, v) for (k, v) in d.items() if isinstance(v, list)])
if d_list:
for (k, v) in d_list.items():
_dictlist2xml(v, tagname=k, root=elem, quote=quote)
return root