diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 9bb6cc56..18387a56 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -237,6 +237,8 @@ class Identite(models.ScoDocModel): Les clés adresses et admission ne SONT PAS utilisées. (added to session but not flushed nor commited) """ + check_etud_duplicate_code(args, "code_nip", dest_url=None) + check_etud_duplicate_code(args, "code_ine", dest_url=None) if not "dept_id" in args: if "dept" in args: departement = Departement.query.filter_by(acronym=args["dept"]).first() @@ -248,8 +250,19 @@ class Identite(models.ScoDocModel): if args.get("admission_id", None) is None: etud.admission = Admission() etud.adresses.append(Adresse(typeadresse="domicile")) - db.session.flush() - + try: + db.session.flush() + except sqlalchemy.exc.IntegrityError as e: + db.session.rollback() + if "unique_dept_nip_except_null" in str(e): + raise ScoValueError( + "Code NIP déjà utilisé pour un autre étudiant" + ) from e + if "unique_dept_ine_except_null" in str(e): + raise ScoValueError( + "Code INE déjà utilisé pour un autre étudiant" + ) from e + raise event = ScolarEvent(etud=etud, event_type="CREATION") db.session.add(event) log(f"Identite.create {etud}") @@ -796,9 +809,12 @@ class Identite(models.ScoDocModel): ) -def check_etud_duplicate_code(args, code_name, edit=True, etudid: int | None = None): +def check_etud_duplicate_code( + args, code_name, edit=True, etudid: int | None = None, dest_url: str | None = "" +): """Vérifie que le code n'est pas dupliqué. Raises ScoGenError si problème. + Si dest_url === None, pas de lien continuer/annuler. """ etudid = etudid or args.get("etudid", None) if not args.get(code_name, None): @@ -837,11 +853,17 @@ def check_etud_duplicate_code(args, code_name, edit=True, etudid: int | None = N + """ + err_page += ( + f"""

- {submit_label}

""" + if dest_url is not None + else "" + ) log(f"*** error: code {code_name} duplique: {args[code_name]}") diff --git a/app/scodoc/sco_import_etuds.py b/app/scodoc/sco_import_etuds.py index 5e129d69..ceedd9a9 100644 --- a/app/scodoc/sco_import_etuds.py +++ b/app/scodoc/sco_import_etuds.py @@ -127,7 +127,16 @@ def sco_import_format(with_codesemestre=True): if fieldname not in aliases: aliases.insert(0, fieldname) # prepend if with_codesemestre or fs[0] != "codesemestre": - r.append((fieldname, typ, table, allow_nulls, description, aliases)) + r.append( + ( + fieldname, + typ, + table, + scu.to_bool(allow_nulls), + description, + aliases, + ) + ) return r @@ -249,18 +258,17 @@ def students_import_excel( if formsemestre_id else url_for("notes.index_html", scodoc_dept=g.scodoc_dept) ) - H = [") -

Import terminé !

-

Continuer

- """ - ) return render_template( - "sco_page.j2", title="Import etudiants", content="\n".join(H) + "sco_page.j2", + title="Import etudiants", + content=f""" +

Import etudiants

+

Import terminé !

+ +

Continuer

+ """, ) return "" @@ -488,12 +496,12 @@ def scolars_import_excel_file( log("scolars_import_excel_file: re-raising exception") raise - diag.append("Import et inscription de %s étudiants" % len(created_etudids)) + diag.append(f"Import et inscription de {len(created_etudids)} étudiants") ScolarNews.add( typ=ScolarNews.NEWS_INSCR, - text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents - % len(created_etudids), + text=f"Inscription de {len(created_etudids)} étudiants", + # peuvent avoir ete inscrits a des semestres differents obj=formsemestre_id, max_frequency=0, ) @@ -502,8 +510,8 @@ def scolars_import_excel_file( cnx.commit() # Invalide les caches des semestres dans lesquels on a inscrit des etudiants: - for formsemestre_id in formsemestre_to_invalidate: - sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id) + for fid in formsemestre_to_invalidate: + sco_cache.invalidate_formsemestre(formsemestre_id=fid) return diag diff --git a/app/templates/scolar/students_import_excel.j2 b/app/templates/scolar/students_import_excel.j2 new file mode 100644 index 00000000..db5378b5 --- /dev/null +++ b/app/templates/scolar/students_import_excel.j2 @@ -0,0 +1,103 @@ +{% extends "sco_page.j2" %} + + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block app_content %} + +

Téléchargement d'une nouvelle liste d'etudiants

+ +
+

A utiliser pour importer de nouveaux étudiants (typiquement au + premier semestre). +

+

Si les étudiants à inscrire sont déjà dans un autre + semestre, utiliser le menu "Inscriptions (passage des étudiants) + depuis d'autres semestres à partir du semestre destination. +

+

Si vous avez un portail Apogée, il est en général préférable d'importer les + étudiants depuis Apogée, via le menu "Synchroniser avec étape Apogée". +

+

+ L'opération se déroule en deux étapes. Dans un premier temps, + vous téléchargez une feuille Excel type. Vous devez remplir + cette feuille, une ligne décrivant chaque étudiant. Ensuite, + vous indiquez le nom de votre fichier dans la case "Fichier Excel" + ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur + votre liste. +

+ +{% if formsemestre %} +

Les étudiants importés seront inscrits dans + le semestre {{formsemestre.html_link_status()|safe}} +

+{% else %} +
Cette fonction est réservé à certains cas particuliers. + Pour importer et inscrire de nouveaux étudiants dans un semestre de + formation, passer par le menu "Inscriptions / Importer des étudiants" + du semestre visé. +
+{% endif %} +
+ +
+
Feuille excel à remplir
+
+ + {% if formsemestre %} + + {% else %} + + {% endif -%} + Obtenir la feuille excel vierge (que vous importerez ci-dessous après l'avoir remplie) +
+
+ +
+
Importation des données
+ {{ tf_form | safe }} +
+ +
+

Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes.

+

Les colonnes peuvent être placées dans n'importe quel ordre, mais + le titre exact (tel que ci-dessous) doit être sur la première ligne. +

+

+ Les champs avec un astérisque (*) doivent être présents (vides non autorisés). +

+ + + + + + + + + {% for t in import_format %} + + + + + + + {% endfor %} + + +{% endblock %} \ No newline at end of file diff --git a/app/views/scolar.py b/app/views/scolar.py index b5c8a126..688be452 100644 --- a/app/views/scolar.py +++ b/app/views/scolar.py @@ -2163,73 +2163,23 @@ def export_etudiants_courants(): def form_students_import_excel(formsemestre_id=None): "formulaire import xls" formsemestre_id = int(formsemestre_id) if formsemestre_id else None - if formsemestre_id: - sem = sco_formsemestre.get_formsemestre(formsemestre_id) - dest_url = url_for( + dest_url = ( + url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, ) - else: - sem = None - dest_url = url_for("scolar.index_html", scodoc_dept=g.scodoc_dept) - if sem and not sem["etat"]: - raise ScoValueError("Modification impossible: semestre verrouille") - H = [ - """

Téléchargement d\'une nouvelle liste d\'etudiants

-
-

A utiliser pour importer de nouveaux étudiants (typiquement au - premier semestre).

-

Si les étudiants à inscrire sont déjà dans un autre - semestre, utiliser le menu "Inscriptions (passage des étudiants) - depuis d'autres semestres à partir du semestre destination. -

-

Si vous avez un portail Apogée, il est en général préférable d'importer les - étudiants depuis Apogée, via le menu "Synchroniser avec étape Apogée". -

-
-

- L'opération se déroule en deux étapes. Dans un premier temps, - vous téléchargez une feuille Excel type. Vous devez remplir - cette feuille, une ligne décrivant chaque étudiant. Ensuite, - vous indiquez le nom de votre fichier dans la case "Fichier Excel" - ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur - votre liste. -

- """, - ] # ' - if sem: - H.append( - """

Les étudiants importés seront inscrits dans - le semestre %s

""" - % sem["titremois"] - ) - else: - H.append( - f""" -

Pour inscrire directement les étudiants dans un semestre de - formation, il suffit d'indiquer le code de ce semestre - (qui doit avoir été créé au préalable). - Cliquez ici pour afficher les codes -

- """ - ) - - H.append("""
  1. """) - if formsemestre_id: - H.append( - """ - - """ - ) - else: - H.append("""""") - H.append( - """Obtenir la feuille excel à remplir
  2. -
  3. """ + if formsemestre_id is not None + else url_for("scolar.index_html", scodoc_dept=g.scodoc_dept) ) + formsemestre = ( + FormSemestre.get_formsemestre(formsemestre_id) + if formsemestre_id is not None + else None + ) + + if formsemestre and not formsemestre.etat: + raise ScoValueError("Modification impossible: semestre verrouille") tf = TrivialFormulator( request.base_url, @@ -2260,46 +2210,27 @@ def form_students_import_excel(formsemestre_id=None): initvalues={"check_homonyms": True, "require_ine": False}, submitlabel="Télécharger", ) - S = [ - """

    Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes. -

    Les colonnes peuvent être placées dans n'importe quel ordre, mais -le titre exact (tel que ci-dessous) doit être sur la première ligne. -

    -

    -Les champs avec un astérisque (*) doivent être présents (nulls non autorisés). -

    - -

    -

AttributTypeDescriptionRequis
{{t[0]}}{{t[1]}}{{t[4]}}{{'*' if t[3] else ''}}
-""" - ] - for t in sco_import_etuds.sco_import_format( - with_codesemestre=(formsemestre_id is None) - ): - if int(t[3]): - ast = "" - else: - ast = "*" - S.append( - "" - % (t[0], t[1], t[4], ast) - ) if tf[0] == 0: return render_template( - "sco_page.j2", + "scolar/students_import_excel.j2", title="Import etudiants", - content="\n".join(H) + tf[1] + "" + "\n".join(S), + import_format=sco_import_etuds.sco_import_format( + with_codesemestre=(formsemestre_id is None) + ), + tf_form=tf[1], + formsemestre=formsemestre, + sco=ScoData(formsemestre=formsemestre), ) - elif tf[0] == -1: + if tf[0] == -1: return flask.redirect(dest_url) - else: - return sco_import_etuds.students_import_excel( - tf[2]["csvfile"], - formsemestre_id=int(formsemestre_id) if formsemestre_id else None, - check_homonyms=tf[2]["check_homonyms"], - require_ine=tf[2]["require_ine"], - ) + + return sco_import_etuds.students_import_excel( + tf[2]["csvfile"], + formsemestre_id=int(formsemestre_id) if formsemestre_id else None, + check_homonyms=tf[2]["check_homonyms"], + require_ine=tf[2]["require_ine"], + ) @bp.route("/import_generate_excel_sample")
AttributTypeDescription
%s%s%s%s