diff --git a/app/models/formations.py b/app/models/formations.py index 5bd4a8d9..d4aac9ad 100644 --- a/app/models/formations.py +++ b/app/models/formations.py @@ -64,7 +64,7 @@ class Formation(db.Model): return f"""Formation {self.titre} ({self.acronyme}) [version {self.version}] code {self.formation_code}""" def to_dict(self, with_refcomp_attrs=False): - """ "as a dict. + """As a dict. Si with_refcomp_attrs, ajoute attributs permettant de retrouver le ref. de comp. """ e = dict(self.__dict__) diff --git a/app/scodoc/sco_apogee_compare.py b/app/scodoc/sco_apogee_compare.py index 6c967604..0c93c665 100644 --- a/app/scodoc/sco_apogee_compare.py +++ b/app/scodoc/sco_apogee_compare.py @@ -43,6 +43,7 @@ Pour chaque étudiant commun: comparer les résultats """ +from flask import g, url_for from app import log from app.scodoc import sco_apogee_csv @@ -72,11 +73,11 @@ def apo_compare_csv_form(): """
Fichier Apogée A: - +
Fichier Apogée B: - +
autodétecter encodage
@@ -88,17 +89,36 @@ def apo_compare_csv_form(): return "\n".join(H) -def apo_compare_csv(A_file, B_file, autodetect=True): +def apo_compare_csv(file_a, file_b, autodetect=True): """Page comparing 2 Apogee CSV files""" - A = _load_apo_data(A_file, autodetect=autodetect) - B = _load_apo_data(B_file, autodetect=autodetect) - + try: + apo_data_a = _load_apo_data(file_a, autodetect=autodetect) + apo_data_b = _load_apo_data(file_b, autodetect=autodetect) + except (UnicodeDecodeError, UnicodeEncodeError) as exc: + dest_url = url_for("notes.semset_page", scodoc_dept=g.scodoc_dept) + if autodetect: + raise ScoValueError( + """ + Erreur: l'encodage de l'un des fichiers est mal détecté. + Essayez sans auto-détection, ou vérifiez le codage et le contenu + des fichiers. + """, + dest_url=dest_url, + ) from exc + else: + raise ScoValueError( + f""" + Erreur: l'encodage de l'un des fichiers est incorrect. + Vérifiez qu'il est bien en {sco_apogee_csv.APO_INPUT_ENCODING} + """, + dest_url=dest_url, + ) from exc H = [ html_sco_header.sco_header(page_title="Comparaison de fichiers Apogée"), "

Comparaison de fichiers Apogée

", _help_txt, '
', - _apo_compare_csv(A, B), + _apo_compare_csv(apo_data_a, apo_data_b), "
", """

Autre comparaison

""", html_sco_header.sco_footer(), @@ -112,9 +132,9 @@ def _load_apo_data(csvfile, autodetect=True): if autodetect: data_b, message = sco_apogee_csv.fix_data_encoding(data_b) if message: - log("apo_compare_csv: %s" % message) + log(f"apo_compare_csv: {message}") if not data_b: - raise ScoValueError("apo_compare_csv: no data") + raise ScoValueError("fichier vide ? (apo_compare_csv: no data)") data = data_b.decode(sco_apogee_csv.APO_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 212bb133..8d21d579 100644 --- a/app/scodoc/sco_apogee_csv.py +++ b/app/scodoc/sco_apogee_csv.py @@ -155,28 +155,25 @@ def fix_data_encoding( text: bytes, default_source_encoding=APO_INPUT_ENCODING, dest_encoding=APO_INPUT_ENCODING, -) -> bytes: +) -> tuple[bytes, str]: """Try to ensure that text is using dest_encoding returns converted text, and a message describing the conversion. + + Raises UnicodeEncodeError en cas de problème, en général liée à + une auto-détection errornée. """ message = "" detected_encoding = guess_data_encoding(text) if not detected_encoding: if default_source_encoding != dest_encoding: - message = "converting from %s to %s" % ( - default_source_encoding, - dest_encoding, - ) - text = text.decode(default_source_encoding).encode( - dest_encoding - ) # XXX #py3 #sco8 à tester + message = f"converting from {default_source_encoding} to {dest_encoding}" + text = text.decode(default_source_encoding).encode(dest_encoding) else: if detected_encoding != dest_encoding: - message = "converting from detected %s to %s" % ( - detected_encoding, - dest_encoding, + message = ( + f"converting from detected {default_source_encoding} to {dest_encoding}" ) - text = text.decode(detected_encoding).encode(dest_encoding) # XXX + text = text.decode(detected_encoding).encode(dest_encoding) return text, message diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py index e37b0fd8..ac602873 100644 --- a/app/scodoc/sco_etape_apogee_view.py +++ b/app/scodoc/sco_etape_apogee_view.py @@ -591,19 +591,45 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect= if not semset_id: raise ValueError("invalid null semset_id") semset = sco_semset.SemSet(semset_id=semset_id) - if csvfile: - data = csvfile.read() # bytes + try: + if csvfile: + data = csvfile.read() # bytes + if autodetect: + # check encoding (although documentation states that users SHOULD upload LATIN1) + + data, message = sco_apogee_csv.fix_data_encoding(data) + if message: + log(f"view_apo_csv_store: {message}") + else: + log("view_apo_csv_store: autodetection of encoding disabled by user") + 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) + except (UnicodeDecodeError, UnicodeEncodeError) as exc: + dest_url = url_for( + "notes.apo_semset_maq_status", + scodoc_dept=g.scodoc_dept, + semset_id=semset_id, + ) if autodetect: - # check encoding (although documentation states that users SHOULD upload LATIN1) - data, message = sco_apogee_csv.fix_data_encoding(data) - if message: - log("view_apo_csv_store: %s" % message) + raise ScoValueError( + 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_csv.APO_INPUT_ENCODING}). + """, + dest_url=dest_url, + ) from exc else: - log("view_apo_csv_store: autodetection of encoding disabled by user") - 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) + raise ScoValueError( + f""" + Erreur: l'encodage du fichier est incorrect. + Vérifiez qu'il est bien en {sco_apogee_csv.APO_INPUT_ENCODING} + """, + dest_url=dest_url, + ) from exc + # check si etape maquette appartient bien au semset apo_data = sco_apogee_csv.ApoData( data_str, periode=semset["sem_id"] diff --git a/app/scodoc/sco_formations.py b/app/scodoc/sco_formations.py index 9be52db4..93f64734 100644 --- a/app/scodoc/sco_formations.py +++ b/app/scodoc/sco_formations.py @@ -119,6 +119,7 @@ def formation_export( formation: Formation = Formation.query.get_or_404(formation_id) f_dict = formation.to_dict(with_refcomp_attrs=True) if not export_ids: + del f_dict["id"] del f_dict["formation_id"] del f_dict["dept_id"] ues = formation.ues diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index d1b5fb79..cb5e0344 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -326,9 +326,9 @@ div.logo-logo img { box-sizing: content-box; margin-top: 10px; /* -10px */ - width: 80px; + width: 130px; /* adapter suivant image */ - padding-right: 5px; + /* padding-right: 5px; */ } div.sidebar-bottom { diff --git a/app/static/icons/scologo_img.png b/app/static/icons/scologo_img.png index 04a2c5c1..9bf587a8 100644 Binary files a/app/static/icons/scologo_img.png and b/app/static/icons/scologo_img.png differ diff --git a/app/templates/about.j2 b/app/templates/about.j2 index 23dc1ce5..3dff1ede 100644 --- a/app/templates/about.j2 +++ b/app/templates/about.j2 @@ -6,7 +6,7 @@

Système de gestion scolarité

-

© Emmanuel Viennet 2021

+

© Emmanuel Viennet 2023

Version {{ version }}

diff --git a/sco_version.py b/sco_version.py index c42cd066..16ec6156 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.4.35" +SCOVERSION = "9.4.36" SCONAME = "ScoDoc" diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py index 1869fc98..d4202f92 100644 --- a/tests/api/test_api_formations.py +++ b/tests/api/test_api_formations.py @@ -114,21 +114,18 @@ def test_formation_export(api_headers): assert r.status_code == 200 export_formation = r.json() assert verify_fields(export_formation, FORMATION_EXPORT_FIELDS) is True - assert isinstance(export_formation["dept_id"], int) assert isinstance(export_formation["acronyme"], str) assert isinstance(export_formation["titre_officiel"], str) assert isinstance(export_formation["formation_code"], str) assert export_formation["code_specialite"] is None or isinstance( export_formation["code_specialite"], str ) - assert isinstance(export_formation["id"], int) assert isinstance(export_formation["titre"], str) assert isinstance(export_formation["version"], int) assert isinstance(export_formation["type_parcours"], int) assert export_formation["referentiel_competence_id"] is None or isinstance( export_formation["referentiel_competence_id"], int ) - assert isinstance(export_formation["formation_id"], int) assert isinstance(export_formation["ue"], list) ues = export_formation["ue"] @@ -192,6 +189,23 @@ def test_formation_export(api_headers): assert r_error.status_code == 404 +def test_formation_export_with_ids(api_headers): + """ + Route: /formation//export_with_ids + """ + r = requests.get( + API_URL + "/formation/1/export_with_ids", + headers=api_headers, + verify=CHECK_CERTIFICATE, + ) + assert r.status_code == 200 + export_formation = r.json() + assert verify_fields(export_formation, FORMATION_EXPORT_FIELDS) is True + assert isinstance(export_formation["id"], int) + assert isinstance(export_formation["dept_id"], int) + assert isinstance(export_formation["formation_id"], int) + + def test_moduleimpl(api_headers): """ Route: /formation/moduleimpl/ diff --git a/tests/api/test_api_users.py b/tests/api/test_api_users.py index 8a347700..e0e0e579 100644 --- a/tests/api/test_api_users.py +++ b/tests/api/test_api_users.py @@ -78,10 +78,10 @@ def test_edit_users(api_admin_headers): nb_users = len(GET("/users/query", headers=admin_h)) user = POST_JSON( "/user/create", - {"user_name": "toto", "nom": "Toto"}, + {"user_name": "test_edit_users", "nom": "Toto"}, headers=admin_h, ) - assert user["user_name"] == "toto" + assert user["user_name"] == "test_edit_users" assert user["dept"] is None assert user["active"] is True assert (nb_users + 1) == len(GET("/users/query", headers=admin_h)) diff --git a/tests/api/tools_test_api.py b/tests/api/tools_test_api.py index 7201031b..b277dd28 100644 --- a/tests/api/tools_test_api.py +++ b/tests/api/tools_test_api.py @@ -77,18 +77,16 @@ FORMATION_FIELDS = { } FORMATION_EXPORT_FIELDS = { - "dept_id", + # "dept_id" "id" "formation_id", pas présents sauf si export_with_ids "acronyme", - "titre_officiel", - "formation_code", "code_specialite", - "id", - "titre", - "version", - "type_parcours", + "formation_code", "referentiel_competence_id", - "formation_id", + "titre_officiel", + "titre", + "type_parcours", "ue", + "version", } FORMATION_EXPORT_UE_FIELDS = { diff --git a/tests/unit/test_formations.py b/tests/unit/test_formations.py index c91a4cf8..38a40378 100644 --- a/tests/unit/test_formations.py +++ b/tests/unit/test_formations.py @@ -178,7 +178,7 @@ def test_formations(test_client): # --- Export de formation_id exp = sco_formations.formation_export( - formation_id=formation_id, format="json" + formation_id=formation_id, format="json", export_ids=True ).get_data(as_text=True) assert isinstance(exp, str) load_exp = json.loads(exp)