From af557f9c934aedc005deb2ac22e934d411e636bb Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet
Date: Tue, 18 Jun 2024 20:40:13 +0200
Subject: [PATCH] Import admission: mode avec etudid
---
app/scodoc/sco_import_etuds.py | 152 +++++++++++++++++++++------------
app/views/scolar.py | 62 +++++++++-----
sco_version.py | 2 +-
3 files changed, 137 insertions(+), 79 deletions(-)
diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py
index 0a569047b..74362f922 100644
--- a/app/scodoc/sco_import_etuds.py
+++ b/app/scodoc/sco_import_etuds.py
@@ -28,7 +28,6 @@
""" Importation des étudiants à partir de fichiers CSV
"""
-import collections
import io
import os
import re
@@ -64,6 +63,7 @@ import app.scodoc.sco_utils as scu
FORMAT_FILE = "format_import_etudiants.txt"
# Champs modifiables via "Import données admission"
+# (nom/prénom modifiables en mode "avec etudid")
ADMISSION_MODIFIABLE_FIELDS = (
"code_nip",
"code_ine",
@@ -132,19 +132,27 @@ def sco_import_format(with_codesemestre=True):
return r
-def sco_import_format_dict(with_codesemestre=True):
+def sco_import_format_dict(with_codesemestre=True, use_etudid: bool = False):
"""Attribut: { 'type': , 'table', 'allow_nulls' , 'description' }"""
fmt = sco_import_format(with_codesemestre=with_codesemestre)
- R = collections.OrderedDict()
+ formats = {}
for l in fmt:
- R[l[0]] = {
+ formats[l[0]] = {
"type": l[1],
"table": l[2],
"allow_nulls": l[3],
"description": l[4],
"aliases": l[5],
}
- return R
+ if use_etudid:
+ formats["etudid"] = {
+ "type": "int",
+ "table": "identite",
+ "allow_nulls": False,
+ "description": "",
+ "aliases": ["etudid", "id"],
+ }
+ return formats
def sco_import_generate_excel_sample(
@@ -188,8 +196,7 @@ def sco_import_generate_excel_sample(
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
members = groups_infos.members
log(
- "sco_import_generate_excel_sample: group_ids=%s %d members"
- % (group_ids, len(members))
+ f"sco_import_generate_excel_sample: group_ids={group_ids}, {len(members)} members"
)
titles = ["etudid"] + titles
titles_styles = [style] + titles_styles
@@ -234,21 +241,26 @@ def students_import_excel(
exclude_cols=["photo_filename"],
)
if return_html:
- if formsemestre_id:
- dest = url_for(
+ dest_url = (
+ url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
- else:
- dest = url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
+ if formsemestre_id
+ else url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
+ )
H = [html_sco_header.sco_header(page_title="Import etudiants")]
H.append("")
for d in diag:
- H.append("- %s
" % d)
- H.append("
")
- H.append("Import terminé !
")
- H.append('Continuer
' % dest)
+ H.append(f"{d}")
+ H.append(
+ f"""
+ )
+ Import terminé !
+ Continuer
+ """
+ )
return "\n".join(H) + html_sco_header.sco_footer()
@@ -308,13 +320,13 @@ def scolars_import_excel_file(
titleslist = []
for t in fs:
if t not in titles:
- raise ScoValueError('Colonne invalide: "%s"' % t)
+ raise ScoValueError(f'Colonne invalide: "{t}"')
titleslist.append(t) #
# ok, same titles
# Start inserting data, abort whole transaction in case of error
created_etudids = []
np_imported_homonyms = 0
- GroupIdInferers = {}
+ group_id_inferer = {}
try: # --- begin DB transaction
linenum = 0
for line in data[1:]:
@@ -429,7 +441,7 @@ def scolars_import_excel_file(
_import_one_student(
formsemestre_id,
values,
- GroupIdInferers,
+ group_id_inferer,
annee_courante,
created_etudids,
linenum,
@@ -496,13 +508,14 @@ def scolars_import_excel_file(
def students_import_admission(
- csvfile, type_admission="", formsemestre_id=None, return_html=True
-):
+ csvfile, type_admission="", formsemestre_id=None, return_html=True, use_etudid=False
+) -> str:
"import donnees admission from Excel file (v2016)"
diag = scolars_import_admission(
csvfile,
formsemestre_id=formsemestre_id,
type_admission=type_admission,
+ use_etudid=use_etudid,
)
if return_html:
H = [html_sco_header.sco_header(page_title="Import données admissions")]
@@ -524,6 +537,7 @@ def students_import_admission(
)
return "\n".join(H) + html_sco_header.sco_footer()
+ return ""
def _import_one_student(
@@ -599,13 +613,15 @@ def _is_new_ine(cnx, code_ine):
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
-def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None):
+def scolars_import_admission(
+ datafile, formsemestre_id=None, type_admission=None, use_etudid=False
+):
"""Importe données admission depuis un fichier Excel quelconque
- par exemple ceux utilisés avec APB
+ par exemple ceux utilisés avec APB, avec ou sans etudid
Cherche dans ce fichier les étudiants qui correspondent à des inscrits du
semestre formsemestre_id.
- Le fichier n'a pas l'INE ni le NIP ni l'etudid, la correspondance se fait
+ Si le fichier n'a pas d'etudid (use_etudid faux), la correspondance se fait
via les noms/prénoms qui doivent être égaux (la casse, les accents et caractères spéciaux
étant ignorés).
@@ -617,23 +633,24 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
dans le fichier importé) du champ type_admission.
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
- TODO:
- - choix onglet du classeur
"""
-
log(f"scolars_import_admission: formsemestre_id={formsemestre_id}")
+ diag: list[str] = []
members = sco_groups.get_group_members(
sco_groups.get_default_group(formsemestre_id)
)
etuds_by_nomprenom = {} # { nomprenom : etud }
- diag = []
- for m in members:
- np = (adm_normalize_string(m["nom"]), adm_normalize_string(m["prenom"]))
- if np in etuds_by_nomprenom:
- msg = "Attention: hononymie pour %s %s" % (m["nom"], m["prenom"])
- log(msg)
- diag.append(msg)
- etuds_by_nomprenom[np] = m
+ etuds_by_etudid = {} # { etudid : etud }
+ if use_etudid:
+ etuds_by_etudid = {m["etudid"]: m for m in members}
+ else:
+ for m in members:
+ np = (adm_normalize_string(m["nom"]), adm_normalize_string(m["prenom"]))
+ if np in etuds_by_nomprenom:
+ msg = f"""Attention: hononymie pour {m["nom"]} {m["prenom"]}"""
+ log(msg)
+ diag.append(msg)
+ etuds_by_nomprenom[np] = m
exceldata = datafile.read()
diag2, data = sco_excel.excel_bytes_to_list(exceldata)
@@ -644,19 +661,29 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
titles = data[0]
# idx -> ('field', convertor)
- fields = adm_get_fields(titles, formsemestre_id)
- idx_nom = None
- idx_prenom = None
+ fields = adm_get_fields(titles, formsemestre_id, use_etudid=use_etudid)
+ idx_nom = idx_prenom = idx_etudid = None
for idx, field in fields.items():
- if field[0] == "nom":
- idx_nom = idx
- if field[0] == "prenom":
- idx_prenom = idx
- if (idx_nom is None) or (idx_prenom is None):
+ match field[0]:
+ case "nom":
+ idx_nom = idx
+ case "prenom":
+ idx_prenom = idx
+ case "etudid":
+ idx_etudid = idx
+
+ if (not use_etudid and ((idx_nom is None) or (idx_prenom is None))) or (
+ use_etudid and idx_etudid is None
+ ):
log("fields indices=" + ", ".join([str(x) for x in fields]))
- log("fields titles =" + ", ".join([fields[x][0] for x in fields]))
+ log("fields titles =" + ", ".join([x[0] for x in fields.values()]))
raise ScoFormatError(
- "scolars_import_admission: colonnes nom et prenom requises",
+ (
+ """colonne etudid requise
+ (si l'option "Utiliser l'identifiant d'étudiant ScoDoc" est cochée)"""
+ if use_etudid
+ else "colonnes nom et prenom requises"
+ ),
dest_url=url_for(
"scolar.form_students_import_infos_admissions",
scodoc_dept=g.scodoc_dept,
@@ -665,18 +692,31 @@ def scolars_import_admission(datafile, formsemestre_id=None, type_admission=None
)
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
+ if use_etudid:
+ modifiable_fields |= {"nom", "prenom"}
nline = 2 # la premiere ligne de donnees du fichier excel est 2
n_import = 0
for line in data[1:]:
- # Retrouve l'étudiant parmi ceux du semestre par (nom, prenom)
- nom = adm_normalize_string(line[idx_nom])
- prenom = adm_normalize_string(line[idx_prenom])
- if (nom, prenom) not in etuds_by_nomprenom:
- msg = f"""Étudiant {line[idx_nom]} {line[idx_prenom]} inexistant"""
- diag.append(msg)
+ if use_etudid:
+ try:
+ etud = etuds_by_etudid.get(int(line[idx_etudid]))
+ except ValueError:
+ etud = None
+ if not etud:
+ msg = f"""Étudiant avec code etudid={line[idx_etudid]} inexistant"""
+ diag.append(msg)
else:
- etud = etuds_by_nomprenom[(nom, prenom)]
+ # Retrouve l'étudiant parmi ceux du semestre par (nom, prenom)
+ nom = adm_normalize_string(line[idx_nom])
+ prenom = adm_normalize_string(line[idx_prenom])
+ etud = etuds_by_nomprenom.get((nom, prenom))
+ if not etud:
+ msg = (
+ f"""Étudiant {line[idx_nom]} {line[idx_prenom]} inexistant"""
+ )
+ diag.append(msg)
+ if etud:
cur_adm = sco_etud.admission_list(cnx, args={"id": etud["admission_id"]})[0]
# peuple les champs presents dans le tableau
args = {}
@@ -758,19 +798,19 @@ def adm_normalize_string(s):
)
-def adm_get_fields(titles, formsemestre_id):
+def adm_get_fields(titles, formsemestre_id: int, use_etudid: bool = False):
"""Cherche les colonnes importables dans les titres (ligne 1) du fichier excel
return: { idx : (field_name, convertor) }
"""
- format_dict = sco_import_format_dict()
+ format_dict = sco_import_format_dict(use_etudid=use_etudid)
fields = {}
idx = 0
for title in titles:
title_n = adm_normalize_string(title)
- for k in format_dict:
- for v in format_dict[k]["aliases"]:
+ for k, fmt in format_dict.items():
+ for v in fmt["aliases"]:
if adm_normalize_string(v) == title_n:
- typ = format_dict[k]["type"]
+ typ = fmt["type"]
if typ == "real":
convertor = adm_convert_real
elif typ == "integer" or typ == "int":
diff --git a/app/views/scolar.py b/app/views/scolar.py
index 0cb128aed..34e7384e1 100644
--- a/app/views/scolar.py
+++ b/app/views/scolar.py
@@ -2365,28 +2365,36 @@ def form_students_import_infos_admissions(formsemestre_id=None):
Les données sont affichées sur les fiches individuelles des étudiants.
-
- Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup.
- Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
- les autres lignes de la feuille seront ignorées.
- Et seules les colonnes intéressant ScoDoc
- seront importées: il est inutile d'éliminer les autres.
-
- Seules les données "admission" seront modifiées
- (et pas l'identité de l'étudiant).
-
- Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid".
-
-
- Avant d'importer vos données, il est recommandé d'enregistrer
- les informations actuelles:
- exporter les données actuelles de ScoDoc
- (ce fichier peut être ré-importé après d'éventuelles modifications)
-
- """,
+
+
+ Vous pouvez importer ici la feuille excel utilisée pour envoyer
+ le classement Parcoursup.
+ Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés,
+ les autres lignes de la feuille seront ignorées.
+ Et seules les colonnes intéressant ScoDoc
+ seront importées: il est inutile d'éliminer les autres.
+
+
+ Seules les données "admission" seront modifiées
+ (et pas l'identité de l'étudiant).
+
+
+ Les colonnes "nom" et "prenom" sont requises,
+ ou bien une colonne "etudid" si la case
+ "Utiliser l'identifiant d'étudiant ScoDoc" est cochée.
+
+
+
+ Avant d'importer vos données, il est recommandé d'enregistrer
+ les informations actuelles:
+ exporter les données actuelles de ScoDoc
+ (ce fichier peut être ré-importé après d'éventuelles modifications)
+
+
+ """,
]
tf = TrivialFormulator(
@@ -2397,6 +2405,15 @@ def form_students_import_infos_admissions(formsemestre_id=None):
"csvfile",
{"title": "Fichier Excel:", "input_type": "file", "size": 40},
),
+ (
+ "use_etudid",
+ {
+ "input_type": "boolcheckbox",
+ "title": "Utiliser l'identifiant d'étudiant ScoDoc (etudid)",
+ "explanation": """si cochée, utilise le code pour retrouver dans ScoDoc
+ les étudiants du fichier excel. Sinon, utilise les noms/prénoms.""",
+ },
+ ),
(
"type_admission",
{
@@ -2436,6 +2453,7 @@ def form_students_import_infos_admissions(formsemestre_id=None):
tf[2]["csvfile"],
type_admission=tf[2]["type_admission"],
formsemestre_id=formsemestre_id,
+ use_etudid=tf[2]["use_etudid"],
)
diff --git a/sco_version.py b/sco_version.py
index 2d37b22ba..694261910 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.6.976"
+SCOVERSION = "9.6.977"
SCONAME = "ScoDoc"