diff --git a/README.md b/README.md
index a5998e16..86b5d3a3 100644
--- a/README.md
+++ b/README.md
@@ -58,11 +58,28 @@ Installation:
donc utiliser:
- pip install -r requirements.txt
+ pip install -r requirements-2.7.txt
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
ScoDoc8 utilise les bases de département de ScoDoc7, mais une nouvelle base
diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py
index 1931bad7..85da8e68 100644
--- a/app/scodoc/gen_tables.py
+++ b/app/scodoc/gen_tables.py
@@ -43,11 +43,10 @@ Par exemple, la clé '_css_row_class' spécifie le style CSS de la ligne.
from __future__ import print_function
import random
from collections import OrderedDict
-
-# XML generation package (apt-get install jaxml)
-import jaxml
+from xml.etree import ElementTree
import json
+from xml.etree import ElementTree
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
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_excel
from app.scodoc import sco_pdf
+from app.scodoc import sco_xml
from app.scodoc.sco_pdf import SU
from app.scodoc.notes_log import log
@@ -567,28 +567,25 @@ class GenTable(object):
The tag names
and can be changed using
xml_outer_tag and xml_row_tag
"""
- doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
- getattr(doc, self.xml_outer_tag)(
- id=self.table_id, origin=self.origin or "", caption=self.caption or ""
+ doc = ElementTree.Element(
+ self.xml_outer_tag,
+ id=self.table_id,
+ origin=self.origin or "",
+ caption=self.caption or "",
)
- doc._push()
for row in self.rows:
- doc._push()
+ x_row = ElementTree.Element(self.xml_row_tag)
row_title = row.get("row_title", "")
if row_title:
- getattr(doc, self.xml_row_tag)(title=row_title)
- else:
- getattr(doc, self.xml_row_tag)()
+ x_row.set("title", row_title)
+ doc.append(x_row)
for cid in self.columns_ids:
- doc._push()
v = row.get(cid, "")
if v is None:
v = ""
- getattr(doc, cid)(value=str(v))
- doc._pop()
- doc._pop()
- doc._pop()
- return repr(doc)
+ x_cell = ElementTree.Element(cid, value=str(v))
+ x_row.append(x_cell)
+ return sco_xml.XML_HEADER + ElementTree.tostring(doc)
def json(self):
"""JSON representation of the table."""
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index 974ce478..03af2d70 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -102,7 +102,7 @@ import app.scodoc.notesdb as ndb
from app.scodoc.notes_log import log
from app.scodoc.sco_exceptions import ScoValueError, FormatError
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 (
ADC,
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index fcfd857f..8acae1ba 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -38,7 +38,7 @@ from app.scodoc import sco_users
from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log
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.sco_utils as scu
@@ -385,100 +385,6 @@ def _write_formsemestre_aux(context, sem, fieldname, valuename):
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):
"ajoute champs responsable_name"
sem["responsable_name"] = ", ".join(
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 511ab7bb..3e8e6042 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -37,8 +37,8 @@ from app.scodoc import sco_groups
from app.scodoc.notes_log import log
from app.scodoc.TrivialFormulator import TrivialFormulator, TF
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_vdi import ApoEtapeVDI
from app.scodoc import html_sco_header
from app.scodoc import sco_codes_parcours
from app.scodoc import sco_compute_moy
diff --git a/app/scodoc/sco_semset.py b/app/scodoc/sco_semset.py
index 56407b56..608d96cf 100644
--- a/app/scodoc/sco_semset.py
+++ b/app/scodoc/sco_semset.py
@@ -50,7 +50,7 @@ from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log
from app.scodoc.sco_etape_bilan import EtapeBilan
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.sco_utils as scu
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index bac16cdf..aec0db8f 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -43,10 +43,10 @@ import time
import types
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 xml.sax.saxutils
+
# XML generation package (apt-get install jaxml)
-import jaxml
+import jaxml # XXX
try:
import six
@@ -66,8 +66,11 @@ from config import Config
from app.scodoc.SuppressAccents import suppression_diacritics
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 import sco_exceptions
+from app.scodoc import sco_xml
from app.scodoc import VERSION
# ----- TEMPORAIRE POUR MIGRATION SCODOC7 -> SCODOC8 avant python3
@@ -445,74 +448,6 @@ def unescape_html_dict(d):
unescape_html_dict(v)
-def quote_xml_attr(data):
- """Escape &, <, >, quotes and double quotes"""
- return xml.sax.saxutils.escape(str(data), {"'": "'", '"': """})
-
-
-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')
-
-
-
-
-
-
-
- """
- 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
FORBIDDEN_CHARS_EXP = re.compile(r"[*\|~\(\)\\]")
ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE)
@@ -615,15 +550,13 @@ def sendPDFFile(REQUEST, data, filename):
class ScoDocJSONEncoder(json.JSONEncoder):
def default(self, o): # pylint: disable=E0202
- from app.scodoc import sco_formsemestre
-
# ScoDoc 7.22 n'utilise plus mx:
if str(type(o)) == "":
log("Warning: mx.DateTime object detected !")
return o.strftime("%Y-%m-%dT%H:%M:%S")
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
- elif isinstance(o, sco_formsemestre.ApoEtapeVDI):
+ elif isinstance(o, ApoEtapeVDI):
return str(o)
else:
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
if force_outer_xml_tag:
root_tagname = tagname + "_list"
- doc = jaxml.XML_document(encoding=SCO_ENCODING)
- getattr(doc, root_tagname)()
- doc._push()
- else:
- doc = None
- doc = simple_dictlist2xml(data, doc=doc, tagname=tagname)
- if force_outer_xml_tag:
- doc._pop()
+ data = [{root_tagname: data}]
+ doc = sco_xml.simple_dictlist2xml(data, tagname=tagname)
+
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
return repr(doc)
diff --git a/app/scodoc/sco_vdi.py b/app/scodoc/sco_vdi.py
new file mode 100644
index 00000000..fd7c35df
--- /dev/null
+++ b/app/scodoc/sco_vdi.py
@@ -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') ]
diff --git a/app/scodoc/sco_xml.py b/app/scodoc/sco_xml.py
new file mode 100644
index 00000000..403c468e
--- /dev/null
+++ b/app/scodoc/sco_xml.py
@@ -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 = """\n"""
+
+
+def quote_xml_attr(data):
+ """Escape &, <, >, quotes and double quotes"""
+ return xml.sax.saxutils.escape(str(data), {"'": "'", '"': """})
+
+
+# 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')
+
+
+
+
+
+
+
+ """
+ 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