forked from ScoDoc/ScoDoc
Fix: traitement erreur si imports étudiants dupliqués. + présentation page d'import
This commit is contained in:
parent
ad754ebd24
commit
35692da422
@ -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
|
||||
<ul><li>
|
||||
{ '</li><li>'.join(listh) }
|
||||
</li></ul>
|
||||
"""
|
||||
err_page += (
|
||||
f"""
|
||||
<p>
|
||||
<a href="{ url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
|
||||
<a href="{ dest_url or url_for(dest_endpoint, scodoc_dept=g.scodoc_dept, **parameters) }
|
||||
">{submit_label}</a>
|
||||
</p>
|
||||
"""
|
||||
if dest_url is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
log(f"*** error: code {code_name} duplique: {args[code_name]}")
|
||||
|
||||
|
@ -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 = ["<ul>"]
|
||||
for d in diag:
|
||||
H.append(f"<li>{d}</li>")
|
||||
H.append(
|
||||
f"""
|
||||
</ul>)
|
||||
<p>Import terminé !</p>
|
||||
<p><a class="stdlink" href="{dest_url}">Continuer</a></p>
|
||||
"""
|
||||
)
|
||||
return render_template(
|
||||
"sco_page.j2", title="Import etudiants", content="\n".join(H)
|
||||
"sco_page.j2",
|
||||
title="Import etudiants",
|
||||
content=f"""
|
||||
<h2>Import etudiants</h2>
|
||||
<p>Import terminé !</p>
|
||||
<ul>
|
||||
{''.join('<li>' + d + '</li>' for d in diag)}
|
||||
</ul>
|
||||
<p><a class="stdlink" href="{dest_url}">Continuer</a></p>
|
||||
""",
|
||||
)
|
||||
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
|
||||
|
||||
|
103
app/templates/scolar/students_import_excel.j2
Normal file
103
app/templates/scolar/students_import_excel.j2
Normal file
@ -0,0 +1,103 @@
|
||||
{% extends "sco_page.j2" %}
|
||||
|
||||
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<style>
|
||||
table.import_format {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
table.import_format td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
<h2 class="formsemestre">Téléchargement d'une nouvelle liste d'etudiants</h2>
|
||||
|
||||
<div class="scobox help explanation">
|
||||
<p>A utiliser pour importer de <b>nouveaux</b> étudiants (typiquement au
|
||||
<b>premier semestre</b>).
|
||||
</p>
|
||||
<p class="fontred">Si les étudiants à inscrire sont déjà dans un autre
|
||||
semestre, utiliser le menu "<em>Inscriptions (passage des étudiants)
|
||||
depuis d'autres semestres</em> à partir du semestre destination.
|
||||
</p>
|
||||
<p class="fontred">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 "<em>Synchroniser avec étape Apogée</em>".
|
||||
</p>
|
||||
<p class="space-before-18">
|
||||
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.
|
||||
</p>
|
||||
|
||||
{% if formsemestre %}
|
||||
<p style="color: red">Les étudiants importés seront inscrits dans
|
||||
le semestre <b>{{formsemestre.html_link_status()|safe}}</b>
|
||||
</p>
|
||||
{% else %}
|
||||
<div class="warning">Cette fonction est réservé à certains cas particuliers.
|
||||
Pour importer et inscrire de nouveaux étudiants dans un semestre de
|
||||
formation, passer par le menu "<em>Inscriptions / Importer des étudiants</em>"
|
||||
du semestre visé.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Feuille excel à remplir</div>
|
||||
<div class="vspaced">
|
||||
|
||||
{% if formsemestre %}
|
||||
<a class="stdlink" href="{{
|
||||
url_for('scolar.import_generate_excel_sample', scodoc_dept=g.scodoc_dept, with_codesemestre=0)
|
||||
}}">
|
||||
{% else %}
|
||||
<a class="stdlink" href="{{
|
||||
url_for('scolar.import_generate_excel_sample', scodoc_dept=g.scodoc_dept)
|
||||
}}">
|
||||
{% endif -%}
|
||||
Obtenir la feuille excel vierge</a> (que vous importerez ci-dessous après l'avoir remplie)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scobox">
|
||||
<div class="scobox-title">Importation des données</div>
|
||||
{{ tf_form | safe }}
|
||||
</div>
|
||||
|
||||
<div class="scobox explanation">
|
||||
<p>Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes.</p>
|
||||
<p>Les colonnes peuvent être placées dans n'importe quel ordre, mais
|
||||
le <b>titre</b> exact (tel que ci-dessous) doit être sur la première ligne.
|
||||
</p>
|
||||
<p>
|
||||
Les champs avec un astérisque (*) doivent être présents (vides non autorisés).
|
||||
</p>
|
||||
|
||||
<table class="import_format">
|
||||
<tr>
|
||||
<td><b>Attribut</b></td>
|
||||
<td><b>Type</b></td>
|
||||
<td><b>Description</b></td>
|
||||
<td><b>Requis</b></td>
|
||||
</tr>
|
||||
{% for t in import_format %}
|
||||
<tr>
|
||||
<td>{{t[0]}}</td>
|
||||
<td>{{t[1]}}</td>
|
||||
<td>{{t[4]}}</td>
|
||||
<td>{{'*' if t[3] else ''}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -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 = [
|
||||
"""<h2 class="formsemestre">Téléchargement d\'une nouvelle liste d\'etudiants</h2>
|
||||
<div style="color: red">
|
||||
<p>A utiliser pour importer de <b>nouveaux</b> étudiants (typiquement au
|
||||
<b>premier semestre</b>).</p>
|
||||
<p>Si les étudiants à inscrire sont déjà dans un autre
|
||||
semestre, utiliser le menu "<em>Inscriptions (passage des étudiants)
|
||||
depuis d'autres semestres</em> à partir du semestre destination.
|
||||
</p>
|
||||
<p>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 "<em>Synchroniser avec étape Apogée</em>".
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
""",
|
||||
] # '
|
||||
if sem:
|
||||
H.append(
|
||||
"""<p style="color: red">Les étudiants importés seront inscrits dans
|
||||
le semestre <b>%s</b></p>"""
|
||||
% sem["titremois"]
|
||||
)
|
||||
else:
|
||||
H.append(
|
||||
f"""
|
||||
<p>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).
|
||||
<a class="stdlink" href="{
|
||||
url_for("scolar.index_html", showcodes=1, scodoc_dept=g.scodoc_dept)
|
||||
}">Cliquez ici pour afficher les codes</a>
|
||||
</p>
|
||||
"""
|
||||
)
|
||||
|
||||
H.append("""<ol><li>""")
|
||||
if formsemestre_id:
|
||||
H.append(
|
||||
"""
|
||||
<a class="stdlink" href="import_generate_excel_sample?with_codesemestre=0">
|
||||
"""
|
||||
)
|
||||
else:
|
||||
H.append("""<a class="stdlink" href="import_generate_excel_sample">""")
|
||||
H.append(
|
||||
"""Obtenir la feuille excel à remplir</a></li>
|
||||
<li>"""
|
||||
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 = [
|
||||
"""<hr/><p>Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes.
|
||||
<p>Les colonnes peuvent être placées dans n'importe quel ordre, mais
|
||||
le <b>titre</b> exact (tel que ci-dessous) doit être sur la première ligne.
|
||||
</p>
|
||||
<p>
|
||||
Les champs avec un astérisque (*) doivent être présents (nulls non autorisés).
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
<table>
|
||||
<tr><td><b>Attribut</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>"""
|
||||
]
|
||||
for t in sco_import_etuds.sco_import_format(
|
||||
with_codesemestre=(formsemestre_id is None)
|
||||
):
|
||||
if int(t[3]):
|
||||
ast = ""
|
||||
else:
|
||||
ast = "*"
|
||||
S.append(
|
||||
"<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>"
|
||||
% (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] + "</li></ol>" + "\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")
|
||||
|
Loading…
Reference in New Issue
Block a user