diff --git a/app/forms/main/config_apo.py b/app/forms/main/config_apo.py index adac92c0eee63c9b430fa58f91a43b8f8c75df84..87e6d9fa65cbc53095f0920f8f90300807c4c8e4 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 613c3ccd06fc031082b5e16844ca3b716b81e0f6..fad76a36eae18609c9633f143557b008418cd871 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 c7b34d9067df0f7f9b8250b223f0c9a608222b43..f830942aab4f3d505927dd510bd27d928200c02e 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 6cd68fadc972685a6c4a3aa90f141b15e08e0e84..e3d1803314c55834c79862f0d86e3b4b5be84d37 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 32c7f872870371dfef96568d47cf8b0c6ed4c022..48d64cfe488e51a76153ed9095dded8e693e6723 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 e11e5713001ec9da1824b983998c2761a6b6d504..ea586aaf96790df39be4e25cc463567a9d0ecd10 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 0e7913fd2f0ebcae4a80c5516d2a647c4aad36e8..32e0ad62b6852755b504d332bf58053b3cca9061 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 7d6b34744016a52da1c1a1c0723beee62dd95d0d..53fffe2719a3cd7af00bdfd9a8913802e25be517 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 eb4b31bb84369008157662318ef2619653040059..0bbed0b765f4a8b65a80ba305600f4367d4906d6 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