From bcc9a78f66e237854a4f462ea8d21c0d534ddf86 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Mon, 17 Mar 2025 20:25:45 +0100
Subject: [PATCH] =?UTF-8?q?Export=20Apog=C3=A9e:=20nouvelles=20config:=20s?=
=?UTF-8?q?=C3=A9parateur=20d=C3=A9cimal,=20I/O=20encodings.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/forms/main/config_apo.py | 31 ++++++++++
app/models/config.py | 13 ++--
app/scodoc/sco_apogee_compare.py | 10 +--
app/scodoc/sco_apogee_csv.py | 30 +++++----
app/scodoc/sco_apogee_reader.py | 40 ++++++++++--
app/scodoc/sco_etape_apogee.py | 96 ++++++++++++++++-------------
app/scodoc/sco_etape_apogee_view.py | 42 ++++++++-----
app/templates/configuration.j2 | 2 +-
tests/unit/test_apogee_csv.py | 6 +-
9 files changed, 181 insertions(+), 89 deletions(-)
diff --git a/app/forms/main/config_apo.py b/app/forms/main/config_apo.py
index adac92c0e..87e6d9fa6 100644
--- a/app/forms/main/config_apo.py
+++ b/app/forms/main/config_apo.py
@@ -59,6 +59,7 @@ def _build_code_field(code):
class CodesDecisionsForm(FlaskForm):
"Formulaire code décisions Apogée"
+
ABAN = _build_code_field("ABAN")
ABL = _build_code_field("ABL")
ADC = _build_code_field("ADC")
@@ -92,5 +93,35 @@ class CodesDecisionsForm(FlaskForm):
validators.DataRequired("format requis"),
],
)
+ APO_DECIMAL_SEP = StringField(
+ label="Séparateur décimal",
+ description="Séparateur décimal pour les notes exportées (par défaut <tt>,</tt>)",
+ validators=[
+ validators.Length(
+ max=SHORT_STR_LEN,
+ message=f"Le séparateur ne doit pas dépasser {SHORT_STR_LEN} caractères",
+ ),
+ ],
+ )
+ APO_INPUT_ENCODING = StringField(
+ label="",
+ description="encodage des fichiers Apogee lus (par défaut <tt>ISO-8859-1</tt>)",
+ validators=[
+ validators.Length(
+ max=SHORT_STR_LEN,
+ message=f"Le code ne doit pas dépasser {SHORT_STR_LEN} caractères",
+ ),
+ ],
+ )
+ APO_OUTPUT_ENCODING = StringField(
+ label="",
+ description="encodage des fichiers Apogee générés (par défaut <tt>ISO-8859-1</tt>)",
+ validators=[
+ validators.Length(
+ max=SHORT_STR_LEN,
+ message=f"Le code ne doit pas dépasser {SHORT_STR_LEN} caractères",
+ ),
+ ],
+ )
submit = SubmitField("Valider")
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
diff --git a/app/models/config.py b/app/models/config.py
index 613c3ccd0..fad76a36e 100644
--- a/app/models/config.py
+++ b/app/models/config.py
@@ -1,7 +1,6 @@
# -*- coding: UTF-8 -*
-"""Model : site config WORK IN PROGRESS #WIP
-"""
+"""Model : site config WORK IN PROGRESS #WIP"""
import json
import re
@@ -57,7 +56,11 @@ CODES_SCODOC_TO_APO = {
PAS1NCI: "PAS1NCI",
RAT: "ATT",
RED: "RED",
+ # Paramètres de config (non des codes)
"NOTES_FMT": "%3.2f",
+ "APO_DECIMAL_SEP": ",",
+ "APO_INPUT_ENCODING": "ISO-8859-1",
+ "APO_OUTPUT_ENCODING": "ISO-8859-1",
}
@@ -282,7 +285,8 @@ class ScoDocSiteConfig(models.ScoDocModel):
@classmethod
def disable_passerelle(cls, disabled: bool = True) -> bool:
- """Désactive (ou active) les fonctions liées à la présence d'une passerelle. True si changement."""
+ """Désactive (ou active) les fonctions liées à la présence d'une passerelle.
+ True si changement."""
return cls.set("disable_passerelle", "on" if disabled else "")
@classmethod
@@ -486,7 +490,8 @@ class PersonalizedLink:
self.url = str(url or "")
self.with_args = bool(with_args)
- def get_url(self, params: dict = {}) -> str:
+ def get_url(self, params: dict = None) -> str:
+ params = params or {}
if not self.with_args:
return self.url
query_string = urllib.parse.urlencode(params)
diff --git a/app/scodoc/sco_apogee_compare.py b/app/scodoc/sco_apogee_compare.py
index c7b34d906..f830942aa 100644
--- a/app/scodoc/sco_apogee_compare.py
+++ b/app/scodoc/sco_apogee_compare.py
@@ -47,6 +47,7 @@ from flask import g, render_template, url_for
from app import log
from app.scodoc import sco_apogee_csv, sco_apogee_reader
+from app.scodoc.sco_apogee_reader import ApoIOConfig
from app.scodoc.sco_apogee_csv import ApoData
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError
@@ -55,6 +56,7 @@ from app.scodoc import sco_preferences
def apo_compare_csv(file_a, file_b, autodetect=True):
"""Page comparing 2 Apogee CSV files"""
+ cfg = ApoIOConfig()
try:
apo_data_a = _load_apo_data(file_a, autodetect=autodetect)
apo_data_b = _load_apo_data(file_b, autodetect=autodetect)
@@ -73,7 +75,7 @@ def apo_compare_csv(file_a, file_b, autodetect=True):
raise ScoValueError(
f"""
Erreur: l'encodage de l'un des fichiers est incorrect.
- Vérifiez qu'il est bien en {sco_apogee_reader.APO_INPUT_ENCODING}
+ Vérifiez qu'il est bien en {cfg.input_encoding}.
""",
dest_url=dest_url,
) from exc
@@ -85,16 +87,16 @@ def apo_compare_csv(file_a, file_b, autodetect=True):
)
-def _load_apo_data(csvfile, autodetect=True):
+def _load_apo_data(csvfile, autodetect=True, cfg: ApoIOConfig = None):
"Read data from request variable and build ApoData"
data_b = csvfile.read()
if autodetect:
- data_b, message = sco_apogee_reader.fix_data_encoding(data_b)
+ data_b, message = sco_apogee_reader.fix_data_encoding(data_b, cfg=cfg)
if message:
log(f"apo_compare_csv: {message}")
if not data_b:
raise ScoValueError("fichier vide ? (apo_compare_csv: no data)")
- data = data_b.decode(sco_apogee_reader.APO_INPUT_ENCODING)
+ data = data_b.decode(cfg.input_encoding)
apo_data = sco_apogee_csv.ApoData(data, orig_filename=csvfile.filename)
return apo_data
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index 6cd68fadc..e3d180331 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -60,9 +60,9 @@ from app.models import (
from app.models.config import ScoDocSiteConfig
from app.scodoc.sco_apogee_reader import (
- APO_DECIMAL_SEP,
ApoCSVReadWrite,
ApoEtudTuple,
+ ApoIOConfig,
)
import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
@@ -80,7 +80,7 @@ from app.scodoc import sco_cursus
from app.scodoc import sco_formsemestre
-def _apo_fmt_note(note, fmt="%3.2f"):
+def _apo_fmt_note(note, cfg: ApoIOConfig = None):
"Formatte une note pour Apogée (séparateur décimal: ',')"
# if not note and isinstance(note, float): changé le 31/1/2022, étrange ?
# return ""
@@ -90,7 +90,7 @@ def _apo_fmt_note(note, fmt="%3.2f"):
return ""
if np.isnan(val):
return ""
- return (fmt % val).replace(".", APO_DECIMAL_SEP)
+ return (cfg.notes_fmt % val).replace(".", cfg.decimal_sep)
class EtuCol:
@@ -113,6 +113,7 @@ class ApoEtud(dict):
def __init__(
self,
apo_etud_tuple: ApoEtudTuple,
+ cfg: ApoIOConfig = None,
export_res_etape=True,
export_res_sem=True,
export_res_ues=True,
@@ -120,6 +121,7 @@ class ApoEtud(dict):
export_res_sdj=True,
export_res_rat=True,
):
+ self.cfg = cfg # config i/o, formats nombres etc
self["nip"] = apo_etud_tuple.nip
self["nom"] = apo_etud_tuple.nom
self["prenom"] = apo_etud_tuple.prenom
@@ -144,9 +146,7 @@ class ApoEtud(dict):
self.export_res_sdj = export_res_sdj
"export meme si pas de decision de jury"
self.export_res_rat = export_res_rat
- self.fmt_note = functools.partial(
- _apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
- )
+ self.fmt_note = functools.partial(_apo_fmt_note, cfg=self.cfg)
# Initialisés par associate_sco:
self.autre_formsemestre: FormSemestre = None
self.autre_res: NotesTableCompat = None
@@ -692,6 +692,13 @@ class ApoEtud(dict):
class ApoData:
+ """Lecture du fichier CSV Apogée
+ Regroupe les élements importants d'un fichier CSV Apogée
+ periode = 1 (sept-jan) ou 2 (fev-jul), mais cette info n'est pas
+ (toujours) présente dans les CSV Apogée et doit être indiquée par l'utilisateur
+ Laisser periode à None si etape en 1 semestre (LP, décalés, ...)
+ """
+
def __init__(
self,
data: str,
@@ -703,13 +710,9 @@ class ApoData:
export_res_sdj=True,
export_res_rat=True,
orig_filename=None,
+ cfg: ApoIOConfig | None = None,
):
- """Lecture du fichier CSV Apogée
- Regroupe les élements importants d'un fichier CSV Apogée
- periode = 1 (sept-jan) ou 2 (fev-jul), mais cette info n'est pas
- (toujours) présente dans les CSV Apogée et doit être indiquée par l'utilisateur
- Laisser periode à None si etape en 1 semestre (LP, décalés, ...)
- """
+ self.cfg = cfg or ApoIOConfig()
self.export_res_etape = export_res_etape # VET, ...
self.export_res_sem = export_res_sem # elt_sem_apo
self.export_res_ues = export_res_ues
@@ -722,7 +725,7 @@ class ApoData:
self.is_apc = None
"Vrai si BUT"
try:
- self.apo_csv = ApoCSVReadWrite(data)
+ self.apo_csv = ApoCSVReadWrite(data, self.cfg)
except ScoFormatError as e:
# enrichit le message d'erreur
filename = self.orig_filename or e.filename
@@ -749,6 +752,7 @@ class ApoData:
export_res_modules=export_res_modules,
export_res_sdj=export_res_sdj,
export_res_rat=export_res_rat,
+ cfg=self.cfg,
)
for apo_etud_tuple in self.apo_csv.csv_etuds
]
diff --git a/app/scodoc/sco_apogee_reader.py b/app/scodoc/sco_apogee_reader.py
index 32c7f8728..48d64cfe4 100644
--- a/app/scodoc/sco_apogee_reader.py
+++ b/app/scodoc/sco_apogee_reader.py
@@ -77,6 +77,7 @@ import re
from chardet import detect as chardet_detect
from app import log
+from app.models.config import ScoDocSiteConfig
from app.scodoc.sco_exceptions import ScoFormatError
from app.scodoc import sco_preferences
@@ -92,6 +93,33 @@ APO_NEWLINE = "\r\n"
ApoEtudTuple = namedtuple("ApoEtudTuple", ("nip", "nom", "prenom", "naissance", "cols"))
+class ApoIOConfig:
+ "Configuration pour la lecture/écriture de fichiers Apogée"
+
+ def __init__(self):
+ self.reload()
+
+ def reload(self):
+ "Load config from ScoDoc configuration"
+ self.notes_fmt = ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
+ self.input_encoding = (
+ ScoDocSiteConfig.get_code_apo("APO_INPUT_ENCODING") or APO_INPUT_ENCODING
+ )
+ self.output_encoding = (
+ ScoDocSiteConfig.get_code_apo("APO_OUTPUT_ENCODING") or APO_OUTPUT_ENCODING
+ )
+ self.decimal_sep = (
+ ScoDocSiteConfig.get_code_apo("APO_DECIMAL_SEP") or APO_DECIMAL_SEP
+ )
+ self.sep = ScoDocSiteConfig.get_code_apo("APO_SEP") or APO_SEP
+ self.newline = ScoDocSiteConfig.get_code_apo("APO_NEWLINE") or APO_NEWLINE
+
+ def __repr__(self):
+ return f"""ApoIOConfig(input_encoding={self.input_encoding}, output_encoding={
+ self.output_encoding}, decimal_sep={self.decimal_sep}, sep={self.sep}, newline={
+ self.newline}, notes_fmt={self.notes_fmt})"""
+
+
class DictCol(dict):
"A dict, where we can add attributes"
@@ -111,9 +139,10 @@ class StringIOWithLineNumber(io.StringIO):
class ApoCSVReadWrite:
"Gestion lecture/écriture de fichiers csv Apogée"
- def __init__(self, data: str):
+ def __init__(self, data: str, cfg: ApoIOConfig = None):
if not data:
raise ScoFormatError("Fichier Apogée vide !")
+ self.cfg = cfg
self.data = data
self._file = StringIOWithLineNumber(data) # pour traiter comme un fichier
self.apo_elts: dict = None
@@ -374,7 +403,7 @@ class ApoCSVReadWrite:
f = io.StringIO()
self._write_header(f)
self._write_etuds(f, apo_etuds)
- return f.getvalue().encode(APO_OUTPUT_ENCODING)
+ return f.getvalue().encode(self.cfg.output_encoding)
def _write_etuds(self, f, apo_etuds: list["ApoEtud"]):
"""write apo CSV etuds on f"""
@@ -451,8 +480,9 @@ def guess_data_encoding(text: bytes, threshold=0.6):
def fix_data_encoding(
text: bytes,
- default_source_encoding=APO_INPUT_ENCODING,
- dest_encoding=APO_INPUT_ENCODING,
+ default_source_encoding: str = None,
+ dest_encoding: str = None,
+ cfg: ApoIOConfig = None,
) -> tuple[bytes, str]:
"""Try to ensure that text is using dest_encoding
returns converted text, and a message describing the conversion.
@@ -460,6 +490,8 @@ def fix_data_encoding(
Raises UnicodeEncodeError en cas de problème, en général liée à
une auto-détection errornée.
"""
+ default_source_encoding = default_source_encoding or cfg.input_encoding
+ dest_encoding = dest_encoding or cfg.output_encoding
message = ""
detected_encoding = guess_data_encoding(text)
if not detected_encoding:
diff --git a/app/scodoc/sco_etape_apogee.py b/app/scodoc/sco_etape_apogee.py
index e11e57130..ea586aaf9 100644
--- a/app/scodoc/sco_etape_apogee.py
+++ b/app/scodoc/sco_etape_apogee.py
@@ -26,49 +26,49 @@
##############################################################################
"""ScoDoc : stockage et vérifications des "maquettes" Apogée
- (fichiers CSV pour l'export vers Apogée)
- associées aux années scolaires
+(fichiers CSV pour l'export vers Apogée)
+associées aux années scolaires
- Voir sco_apogee_csv.py pour la structure du fichier Apogée.
+Voir sco_apogee_csv.py pour la structure du fichier Apogée.
- Stockage: utilise sco_archive.py
- exemple:
- /opt/scodoc-data/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
- pour une maquette de l'étape V3ASR version VDI 111.
+Stockage: utilise sco_archive.py
+ exemple:
+/opt/scodoc-data/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
+pour une maquette de l'étape V3ASR version VDI 111.
- La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.
- apo_csv_get()
+La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.
+apo_csv_get()
- API:
- # apo_csv_store(csv_data, annee_scolaire, sem_id)
- store maq file (archive)
+API:
+# apo_csv_store(csv_data, annee_scolaire, sem_id)
+ store maq file (archive)
- apo_csv_get(etape_apo, annee_scolaire, sem_id, vdi_apo=None)
- get maq data (read stored file and returns string)
- if vdi_apo, get maq for this etape/vdi, else returns the first matching etape.
+apo_csv_get(etape_apo, annee_scolaire, sem_id, vdi_apo=None)
+ get maq data (read stored file and returns string)
+ if vdi_apo, get maq for this etape/vdi, else returns the first matching etape.
- apo_csv_delete(etape_apo, annee_scolaire, sem_id)
+apo_csv_delete(etape_apo, annee_scolaire, sem_id)
- apo_csv_list_stored_etapes(annee_scolaire=None, sem_id=None, etapes=None)
- returns: liste des codes etapes et version vdi stockés (pour l'annee_scolaire et le sem_id indiqués)
+apo_csv_list_stored_etapes(annee_scolaire=None, sem_id=None, etapes=None)
+ returns: liste des codes etapes et version vdi stockés (pour l'annee_scolaire et le sem_id indiqués)
- apo_csv_semset_check(semset)
- check students in stored maqs vs students in sem
- Cas à détecter:
- - etudiants ScoDoc sans code NIP
- - etudiants dans sem (ScoDoc) mais dans aucun CSV
- - etudiants dans un CSV mais pas dans sem ScoDoc
- - etudiants dans plusieurs CSV (argh!)
- detecte aussi si on a plusieurs années scolaires
+apo_csv_semset_check(semset)
+ check students in stored maqs vs students in sem
+ Cas à détecter:
+ - etudiants ScoDoc sans code NIP
+ - etudiants dans sem (ScoDoc) mais dans aucun CSV
+ - etudiants dans un CSV mais pas dans sem ScoDoc
+ - etudiants dans plusieurs CSV (argh!)
+ detecte aussi si on a plusieurs années scolaires
- returns: etuds_ok (in ScoDoc and CSVs)
- etuds_no_apo
- unknown_apo : liste de { 'NIP', 'nom', 'prenom' }
- dups_apo : liste de { 'NIP', 'nom', 'prenom', 'etapes_apo' }
- etapes_missing_csv : liste des étapes du semestre sans maquette CSV
+ returns: etuds_ok (in ScoDoc and CSVs)
+ etuds_no_apo
+ unknown_apo : liste de { 'NIP', 'nom', 'prenom' }
+ dups_apo : liste de { 'NIP', 'nom', 'prenom', 'etapes_apo' }
+ etapes_missing_csv : liste des étapes du semestre sans maquette CSV
- apo_csv_check_etape(semset, set_nips, etape_apo)
- check une etape
+apo_csv_check_etape(semset, set_nips, etape_apo)
+ check une etape
"""
@@ -77,6 +77,7 @@ import re
import app.scodoc.sco_utils as scu
from app.scodoc import sco_archives
from app.scodoc import sco_apogee_csv, sco_apogee_reader
+from app.scodoc.sco_apogee_reader import ApoIOConfig
from app.scodoc.sco_exceptions import ScoValueError
@@ -96,7 +97,7 @@ APO_CSV_ARCHIVER = ApoCSVArchiver()
# return archive_id
-def apo_csv_store(csv_data: str, annee_scolaire, sem_id):
+def apo_csv_store(csv_data: str, annee_scolaire, sem_id, cfg: ApoIOConfig):
"""
csv_data: maquette content (str))
annee_scolaire: int (2016)
@@ -127,7 +128,7 @@ def apo_csv_store(csv_data: str, annee_scolaire, sem_id):
oid = f"{annee_scolaire}-{sem_id}"
description = f"""{str(apo_data.etape)};{annee_scolaire};{sem_id}"""
archive_id = APO_CSV_ARCHIVER.create_obj_archive(oid, description)
- csv_data_bytes = csv_data.encode(sco_apogee_reader.APO_OUTPUT_ENCODING)
+ csv_data_bytes = csv_data.encode(cfg.output_encoding)
APO_CSV_ARCHIVER.store(archive_id, filename, csv_data_bytes)
return apo_data.etape
@@ -199,20 +200,22 @@ def apo_csv_get_archive(etape_apo, annee_scolaire="", sem_id=""):
return None
-def apo_csv_get(etape_apo="", annee_scolaire="", sem_id="") -> str:
+def apo_csv_get(
+ etape_apo="", annee_scolaire="", sem_id="", cfg: ApoIOConfig = None
+) -> str:
"""Get CSV data for given etape_apo
:return: CSV, as a data string
"""
info = apo_csv_get_archive(etape_apo, annee_scolaire, sem_id)
if not info:
raise ScoValueError(
- "Etape %s non enregistree (%s, %s)" % (etape_apo, annee_scolaire, sem_id)
+ f"Étape {etape_apo} non enregistree ({annee_scolaire}, {sem_id})"
)
archive_id = info["archive_id"]
data = APO_CSV_ARCHIVER.get(archive_id, etape_apo + ".csv")
# ce fichier a été archivé donc généré par ScoDoc
# son encodage est donc APO_OUTPUT_ENCODING
- return data.decode(sco_apogee_reader.APO_OUTPUT_ENCODING)
+ return data.decode(cfg.output_encoding)
# ------------------------------------------------------------------------
@@ -229,11 +232,13 @@ def apo_get_sem_etapes(sem):
return sem["etapes"]
-def apo_csv_check_etape(semset, set_nips, etape_apo):
+def apo_csv_check_etape(semset, set_nips, etape_apo, cfg: ApoIOConfig = None):
"""Check etape vs set of sems"""
# Etudiants dans la maquette CSV:
- csv_data = apo_csv_get(etape_apo, semset["annee_scolaire"], semset["sem_id"])
- apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
+ csv_data = apo_csv_get(
+ etape_apo, semset["annee_scolaire"], semset["sem_id"], cfg=cfg
+ )
+ apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"], cfg=cfg)
apo_nips = {e["nip"] for e in apo_data.etuds}
#
nips_ok = set_nips.intersection(apo_nips)
@@ -258,6 +263,7 @@ def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=Fals
- etudiants dans plusieurs CSV (argh!)
+ si plusieurs annees scolaires
"""
+ cfg = ApoIOConfig()
# Etapes du semestre sans maquette CSV:
etapes_apo = apo_csv_list_stored_etapes(
semset["annee_scolaire"], semset["sem_id"], etapes=semset.list_etapes()
@@ -286,7 +292,7 @@ def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=Fals
et_nips_no_sco,
et_maq_elems,
et_sem_elems,
- ) = apo_csv_check_etape(semset, set_nips, etape_apo)
+ ) = apo_csv_check_etape(semset, set_nips, etape_apo, cfg)
nips_ok |= et_nips_ok
nips_no_apo -= et_apo_nips
nips_no_sco |= et_nips_no_sco
@@ -324,7 +330,7 @@ def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=Fals
)
-def apo_csv_retreive_etuds_by_nip(semset, nips):
+def apo_csv_retreive_etuds_by_nip(semset, nips, cfg: ApoIOConfig = None):
"""
Search info about listed nips in stored CSV
:return: list [ { 'etape_apo', 'nip', 'nom', 'prenom' } ]
@@ -332,7 +338,9 @@ def apo_csv_retreive_etuds_by_nip(semset, nips):
apo_etuds_by_nips = {}
etapes_apo = apo_csv_list_stored_etapes(semset["annee_scolaire"], semset["sem_id"])
for etape_apo in etapes_apo:
- csv_data = apo_csv_get(etape_apo, semset["annee_scolaire"], semset["sem_id"])
+ csv_data = apo_csv_get(
+ etape_apo, semset["annee_scolaire"], semset["sem_id"], cfg=cfg
+ )
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
etape_apo = apo_data.etape_apogee
for e in apo_data.etuds:
diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py
index 0e7913fd2..32e0ad62b 100644
--- a/app/scodoc/sco_etape_apogee_view.py
+++ b/app/scodoc/sco_etape_apogee_view.py
@@ -25,8 +25,7 @@
#
##############################################################################
-"""ScoDoc : formulaires gestion maquettes Apogee / export resultats
-"""
+"""ScoDoc : formulaires gestion maquettes Apogee / export resultats"""
import io
from zipfile import ZipFile
@@ -45,7 +44,7 @@ from app.scodoc import sco_preferences
from app.scodoc import sco_semset
from app.scodoc import sco_etud
from app.scodoc.gen_tables import GenTable
-from app.scodoc.sco_apogee_reader import APO_INPUT_ENCODING, APO_OUTPUT_ENCODING
+from app.scodoc.sco_apogee_reader import ApoIOConfig
from app.scodoc.sco_exceptions import ScoValueError
@@ -63,6 +62,7 @@ def apo_semset_maq_status(
"""Page statut / tableau de bord"""
if not semset_id:
raise ScoValueError("invalid null semset_id")
+ cfg = ApoIOConfig()
semset = sco_semset.SemSet(semset_id=semset_id)
semset.fill_formsemestres()
# autorise export meme si etudiants Apo manquants:
@@ -384,7 +384,7 @@ def apo_semset_maq_status(
l'export des résultats après les jurys, puis de remplir et exporter ces fichiers.
</p>
<p>
- Les fichiers ("maquettes") Apogée sont de type CSV, du texte codé en {APO_INPUT_ENCODING}.
+ Les fichiers ("maquettes") Apogée sont de type CSV, du texte codé en {cfg.input_encoding}.
</p>
<p>On a un fichier par étape Apogée. Pour les obtenir, soit on peut les télécharger
directement (si votre ScoDoc est interfacé avec Apogée), soit se débrouiller pour
@@ -444,6 +444,7 @@ def apo_semset_maq_status(
def table_apo_csv_list(semset):
"""Table des archives (triée par date d'archivage)"""
annee_scolaire = semset["annee_scolaire"]
+ cfg = ApoIOConfig()
sem_id = semset["sem_id"]
rows = sco_etape_apogee.apo_csv_list_stored_archives(
@@ -452,7 +453,9 @@ def table_apo_csv_list(semset):
for t in rows:
# Ajoute qq infos pour affichage:
- csv_data = sco_etape_apogee.apo_csv_get(t["etape_apo"], annee_scolaire, sem_id)
+ csv_data = sco_etape_apogee.apo_csv_get(
+ t["etape_apo"], annee_scolaire, sem_id, cfg=cfg
+ )
apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
t["filename"] = apo_data.apo_csv.titles["apoC_Fichier_Exp"]
t["nb_etuds"] = len(apo_data.etuds)
@@ -507,12 +510,13 @@ def view_apo_etuds(semset_id, title="", nip_list="", fmt="html"):
if not semset_id:
raise ValueError("invalid null semset_id")
semset = sco_semset.SemSet(semset_id=semset_id)
+ cfg = ApoIOConfig()
# annee_scolaire = semset["annee_scolaire"]
# sem_id = semset["sem_id"]
if not isinstance(nip_list, str):
nip_list = str(nip_list)
nips = nip_list.split(",")
- etuds = sco_etape_apogee.apo_csv_retreive_etuds_by_nip(semset, nips)
+ etuds = sco_etape_apogee.apo_csv_retreive_etuds_by_nip(semset, nips, cfg=cfg)
# Ils sont parfois dans ScoDoc même si pas dans le semestre: essaie de les retrouver
for etud in etuds.values():
etud_sco = sco_etud.get_etud_info(code_nip=etud["nip"], filled=True)
@@ -617,6 +621,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
"""
if not semset_id:
raise ValueError("invalid null semset_id")
+ cfg = ApoIOConfig()
semset = sco_semset.SemSet(semset_id=semset_id)
try:
if csvfile:
@@ -624,7 +629,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
if autodetect:
# check encoding (although documentation states that users SHOULD upload LATIN1)
- data, message = sco_apogee_reader.fix_data_encoding(data)
+ data, message = sco_apogee_reader.fix_data_encoding(data, cfg=cfg)
if message:
log(f"view_apo_csv_store: {message}")
else:
@@ -632,7 +637,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
if not data:
raise ScoValueError("view_apo_csv_store: no data")
# data est du bytes, encodé en APO_INPUT_ENCODING
- data_str = data.decode(APO_INPUT_ENCODING)
+ data_str = data.decode(cfg.input_encoding)
except (UnicodeDecodeError, UnicodeEncodeError) as exc:
dest_url = url_for(
"notes.apo_semset_maq_status",
@@ -644,7 +649,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
f"""
Erreur: l'encodage du fichier est mal détecté.
Essayez sans auto-détection, ou vérifiez le codage et le contenu
- du fichier (qui doit être en {sco_apogee_reader.APO_INPUT_ENCODING}).
+ du fichier (qui doit être en {cfg.input_encoding}).
""",
dest_url=dest_url,
) from exc
@@ -652,7 +657,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
raise ScoValueError(
f"""
Erreur: l'encodage du fichier est incorrect.
- Vérifiez qu'il est bien en {sco_apogee_reader.APO_INPUT_ENCODING}
+ Vérifiez qu'il est bien en {cfg.input_encoding}
""",
dest_url=dest_url,
) from exc
@@ -672,7 +677,9 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
dest_url=dest_url,
)
- sco_etape_apogee.apo_csv_store(data_str, semset["annee_scolaire"], semset["sem_id"])
+ sco_etape_apogee.apo_csv_store(
+ data_str, semset["annee_scolaire"], semset["sem_id"], cfg=cfg
+ )
return flask.redirect(dest_url)
@@ -682,13 +689,14 @@ def view_apo_csv_download_and_store(etape_apo="", semset_id=""):
if not semset_id:
raise ValueError("invalid null semset_id")
semset = sco_semset.SemSet(semset_id=semset_id)
+ cfg = ApoIOConfig()
data = sco_portal_apogee.get_maquette_apogee(
etape=etape_apo, annee_scolaire=semset["annee_scolaire"]
)
# here, data is str
# but we store and generate latin1 files, to ease further import in Apogée
- data = data.encode(APO_OUTPUT_ENCODING)
+ data = data.encode(cfg.output_encoding)
return view_apo_csv_store(semset_id, data=data, autodetect=False)
@@ -727,11 +735,12 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
semset = sco_semset.SemSet(semset_id=semset_id)
annee_scolaire = semset["annee_scolaire"]
sem_id = semset["sem_id"]
- csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
+ cfg = ApoIOConfig()
+ csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id, cfg=cfg)
if fmt == "raw":
return scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
- apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
+ apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"], cfg=cfg)
(
ok_for_export,
@@ -868,6 +877,7 @@ def apo_csv_export_results(
raise ValueError("invalid null semset_id")
semset = sco_semset.SemSet(semset_id=semset_id)
annee_scolaire = semset["annee_scolaire"]
+ cfg = ApoIOConfig()
periode = semset["sem_id"]
data = io.BytesIO()
@@ -876,7 +886,9 @@ def apo_csv_export_results(
annee_scolaire, periode, etapes=semset.list_etapes()
)
for etape_apo in etapes_apo:
- apo_csv = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, periode)
+ apo_csv = sco_etape_apogee.apo_csv_get(
+ etape_apo, annee_scolaire, periode, cfg=cfg
+ )
sco_apogee_csv.export_csv_to_apogee(
apo_csv,
periode=periode,
diff --git a/app/templates/configuration.j2 b/app/templates/configuration.j2
index 7d6b34744..53fffe271 100644
--- a/app/templates/configuration.j2
+++ b/app/templates/configuration.j2
@@ -89,7 +89,7 @@ Heure: <b><tt>{{ time.strftime("%d/%m/%Y %H:%M") }}</tt></b>
<div class="scobox">
<div class="scobox-title">Exports Apogée</div>
- <p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">Configuration des codes de décision</a>
+ <p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">Configuration des codes de décision et paramétrage Apogée</a>
</p>
</div>
diff --git a/tests/unit/test_apogee_csv.py b/tests/unit/test_apogee_csv.py
index eb4b31bb8..0bbed0b76 100644
--- a/tests/unit/test_apogee_csv.py
+++ b/tests/unit/test_apogee_csv.py
@@ -4,9 +4,7 @@
# See LICENSE
##############################################################################
-""" Test lecture/érciture fichiers Apogée
-
-"""
+"""Test lecture/érciture fichiers Apogée"""
import pytest
from flask import g
@@ -45,7 +43,7 @@ def test_apogee_csv(test_client):
formsemestre.etapes.append(etape)
db.session.add(formsemestre)
db.session.commit()
- #
+ # utilise l'encodage par defaut, APO_INPUT_ENCODING, et non la config
with open(APO_CSV_FILE, encoding=sco_apogee_reader.APO_INPUT_ENCODING) as f:
data = f.read()
assert "ETUDIANT" in data
--
GitLab