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.
|
Les clés adresses et admission ne SONT PAS utilisées.
|
||||||
(added to session but not flushed nor commited)
|
(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 not "dept_id" in args:
|
||||||
if "dept" in args:
|
if "dept" in args:
|
||||||
departement = Departement.query.filter_by(acronym=args["dept"]).first()
|
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:
|
if args.get("admission_id", None) is None:
|
||||||
etud.admission = Admission()
|
etud.admission = Admission()
|
||||||
etud.adresses.append(Adresse(typeadresse="domicile"))
|
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")
|
event = ScolarEvent(etud=etud, event_type="CREATION")
|
||||||
db.session.add(event)
|
db.session.add(event)
|
||||||
log(f"Identite.create {etud}")
|
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é.
|
"""Vérifie que le code n'est pas dupliqué.
|
||||||
Raises ScoGenError si problème.
|
Raises ScoGenError si problème.
|
||||||
|
Si dest_url === None, pas de lien continuer/annuler.
|
||||||
"""
|
"""
|
||||||
etudid = etudid or args.get("etudid", None)
|
etudid = etudid or args.get("etudid", None)
|
||||||
if not args.get(code_name, 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>
|
<ul><li>
|
||||||
{ '</li><li>'.join(listh) }
|
{ '</li><li>'.join(listh) }
|
||||||
</li></ul>
|
</li></ul>
|
||||||
|
"""
|
||||||
|
err_page += (
|
||||||
|
f"""
|
||||||
<p>
|
<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>
|
">{submit_label}</a>
|
||||||
</p>
|
</p>
|
||||||
"""
|
"""
|
||||||
|
if dest_url is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
|
||||||
log(f"*** error: code {code_name} duplique: {args[code_name]}")
|
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:
|
if fieldname not in aliases:
|
||||||
aliases.insert(0, fieldname) # prepend
|
aliases.insert(0, fieldname) # prepend
|
||||||
if with_codesemestre or fs[0] != "codesemestre":
|
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
|
return r
|
||||||
|
|
||||||
|
|
||||||
@ -249,18 +258,17 @@ def students_import_excel(
|
|||||||
if formsemestre_id
|
if formsemestre_id
|
||||||
else url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
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(
|
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 ""
|
return ""
|
||||||
|
|
||||||
@ -488,12 +496,12 @@ def scolars_import_excel_file(
|
|||||||
log("scolars_import_excel_file: re-raising exception")
|
log("scolars_import_excel_file: re-raising exception")
|
||||||
raise
|
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(
|
ScolarNews.add(
|
||||||
typ=ScolarNews.NEWS_INSCR,
|
typ=ScolarNews.NEWS_INSCR,
|
||||||
text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents
|
text=f"Inscription de {len(created_etudids)} étudiants",
|
||||||
% len(created_etudids),
|
# peuvent avoir ete inscrits a des semestres differents
|
||||||
obj=formsemestre_id,
|
obj=formsemestre_id,
|
||||||
max_frequency=0,
|
max_frequency=0,
|
||||||
)
|
)
|
||||||
@ -502,8 +510,8 @@ def scolars_import_excel_file(
|
|||||||
cnx.commit()
|
cnx.commit()
|
||||||
|
|
||||||
# Invalide les caches des semestres dans lesquels on a inscrit des etudiants:
|
# Invalide les caches des semestres dans lesquels on a inscrit des etudiants:
|
||||||
for formsemestre_id in formsemestre_to_invalidate:
|
for fid in formsemestre_to_invalidate:
|
||||||
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
sco_cache.invalidate_formsemestre(formsemestre_id=fid)
|
||||||
|
|
||||||
return diag
|
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):
|
def form_students_import_excel(formsemestre_id=None):
|
||||||
"formulaire import xls"
|
"formulaire import xls"
|
||||||
formsemestre_id = int(formsemestre_id) if formsemestre_id else None
|
formsemestre_id = int(formsemestre_id) if formsemestre_id else None
|
||||||
if formsemestre_id:
|
dest_url = (
|
||||||
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
|
url_for(
|
||||||
dest_url = url_for(
|
|
||||||
"notes.formsemestre_status",
|
"notes.formsemestre_status",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=formsemestre_id,
|
formsemestre_id=formsemestre_id,
|
||||||
)
|
)
|
||||||
else:
|
if formsemestre_id is not None
|
||||||
sem = None
|
else url_for("scolar.index_html", scodoc_dept=g.scodoc_dept)
|
||||||
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>"""
|
|
||||||
)
|
)
|
||||||
|
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(
|
tf = TrivialFormulator(
|
||||||
request.base_url,
|
request.base_url,
|
||||||
@ -2260,46 +2210,27 @@ def form_students_import_excel(formsemestre_id=None):
|
|||||||
initvalues={"check_homonyms": True, "require_ine": False},
|
initvalues={"check_homonyms": True, "require_ine": False},
|
||||||
submitlabel="Télécharger",
|
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:
|
if tf[0] == 0:
|
||||||
return render_template(
|
return render_template(
|
||||||
"sco_page.j2",
|
"scolar/students_import_excel.j2",
|
||||||
title="Import etudiants",
|
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)
|
return flask.redirect(dest_url)
|
||||||
else:
|
|
||||||
return sco_import_etuds.students_import_excel(
|
return sco_import_etuds.students_import_excel(
|
||||||
tf[2]["csvfile"],
|
tf[2]["csvfile"],
|
||||||
formsemestre_id=int(formsemestre_id) if formsemestre_id else None,
|
formsemestre_id=int(formsemestre_id) if formsemestre_id else None,
|
||||||
check_homonyms=tf[2]["check_homonyms"],
|
check_homonyms=tf[2]["check_homonyms"],
|
||||||
require_ine=tf[2]["require_ine"],
|
require_ine=tf[2]["require_ine"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/import_generate_excel_sample")
|
@bp.route("/import_generate_excel_sample")
|
||||||
|
Loading…
Reference in New Issue
Block a user