2020-09-26 16:19:37 +02:00
|
|
|
# -*- mode: python -*-
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# Gestion scolarite IUT
|
|
|
|
#
|
2023-12-31 23:04:06 +01:00
|
|
|
# Copyright (c) 1999 - 2024 Emmanuel Viennet. All rights reserved.
|
2020-09-26 16:19:37 +02:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
#
|
|
|
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
2021-09-29 20:08:18 +02:00
|
|
|
""" Importation des étudiants à partir de fichiers CSV
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
|
2021-09-29 20:08:18 +02:00
|
|
|
import io
|
2021-07-25 09:51:09 +02:00
|
|
|
import os
|
2021-02-04 20:02:44 +01:00
|
|
|
import re
|
2021-07-25 09:51:09 +02:00
|
|
|
import time
|
2021-08-14 10:12:40 +02:00
|
|
|
|
2021-08-15 21:33:47 +02:00
|
|
|
from flask import g, url_for
|
2020-09-26 16:19:37 +02:00
|
|
|
|
2023-07-11 06:57:38 +02:00
|
|
|
from app import db, log
|
2023-10-15 14:42:03 +02:00
|
|
|
from app.models import Identite, GroupDescr, ScolarNews
|
2023-10-17 22:12:28 +02:00
|
|
|
from app.models.etudiants import input_civilite, input_civilite_etat_civil
|
2023-07-11 06:57:38 +02:00
|
|
|
|
2021-06-19 23:21:37 +02:00
|
|
|
from app.scodoc.gen_tables import GenTable
|
2023-07-11 06:57:38 +02:00
|
|
|
from app.scodoc.sco_excel import COLORS
|
2021-06-19 23:21:37 +02:00
|
|
|
from app.scodoc.sco_exceptions import (
|
2021-12-03 14:13:49 +01:00
|
|
|
ScoFormatError,
|
2021-02-03 22:00:41 +01:00
|
|
|
ScoException,
|
|
|
|
ScoValueError,
|
|
|
|
)
|
2021-06-19 23:21:37 +02:00
|
|
|
from app.scodoc import html_sco_header
|
2021-07-19 19:53:01 +02:00
|
|
|
from app.scodoc import sco_cache
|
2021-06-19 23:21:37 +02:00
|
|
|
from app.scodoc import sco_etud
|
|
|
|
from app.scodoc import sco_groups
|
|
|
|
from app.scodoc import sco_excel
|
|
|
|
from app.scodoc import sco_groups_view
|
|
|
|
from app.scodoc import sco_preferences
|
2023-07-11 06:57:38 +02:00
|
|
|
import app.scodoc.notesdb as ndb
|
|
|
|
from app.scodoc.sco_formsemestre_inscriptions import (
|
|
|
|
do_formsemestre_inscription_with_modules,
|
|
|
|
)
|
|
|
|
import app.scodoc.sco_utils as scu
|
2020-09-26 16:19:37 +02:00
|
|
|
|
2021-07-25 09:51:09 +02:00
|
|
|
# format description (in tools/)
|
|
|
|
FORMAT_FILE = "format_import_etudiants.txt"
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
# Champs modifiables via "Import données admission"
|
2024-06-18 20:40:13 +02:00
|
|
|
# (nom/prénom modifiables en mode "avec etudid")
|
2020-09-26 16:19:37 +02:00
|
|
|
ADMISSION_MODIFIABLE_FIELDS = (
|
|
|
|
"code_nip",
|
|
|
|
"code_ine",
|
2023-03-13 06:39:36 +01:00
|
|
|
"prenom_etat_civil",
|
|
|
|
"civilite_etat_civil",
|
2020-09-26 16:19:37 +02:00
|
|
|
"date_naissance",
|
|
|
|
"lieu_naissance",
|
|
|
|
"bac",
|
|
|
|
"specialite",
|
|
|
|
"annee_bac",
|
|
|
|
"math",
|
|
|
|
"physique",
|
|
|
|
"anglais",
|
|
|
|
"francais",
|
|
|
|
"type_admission",
|
|
|
|
"boursier_prec",
|
|
|
|
"qualite",
|
|
|
|
"rapporteur",
|
|
|
|
"score",
|
|
|
|
"commentaire",
|
|
|
|
"classement",
|
|
|
|
"apb_groupe",
|
|
|
|
"apb_classement_gr",
|
|
|
|
"nomlycee",
|
|
|
|
"villelycee",
|
|
|
|
"codepostallycee",
|
|
|
|
"codelycee",
|
|
|
|
# Adresse:
|
|
|
|
"email",
|
|
|
|
"emailperso",
|
|
|
|
"domicile",
|
|
|
|
"codepostaldomicile",
|
|
|
|
"villedomicile",
|
|
|
|
"paysdomicile",
|
|
|
|
"telephone",
|
|
|
|
"telephonemobile",
|
|
|
|
# Groupes
|
|
|
|
"groupes",
|
|
|
|
)
|
|
|
|
|
|
|
|
# ----
|
|
|
|
|
|
|
|
|
|
|
|
def sco_import_format(with_codesemestre=True):
|
|
|
|
"returns tuples (Attribut, Type, Table, AllowNulls, Description)"
|
|
|
|
r = []
|
2021-07-25 09:51:09 +02:00
|
|
|
for l in open(os.path.join(scu.SCO_TOOLS_DIR, FORMAT_FILE)):
|
2020-09-26 16:19:37 +02:00
|
|
|
l = l.strip()
|
|
|
|
if l and l[0] != "#":
|
|
|
|
fs = l.split(";")
|
|
|
|
if len(fs) < 5:
|
|
|
|
# Bug: invalid format file (fatal)
|
|
|
|
raise ScoException(
|
|
|
|
"file %s has invalid format (expected %d fields, got %d) (%s)"
|
|
|
|
% (FORMAT_FILE, 5, len(fs), l)
|
|
|
|
)
|
|
|
|
fieldname = (
|
|
|
|
fs[0].strip().lower().split()[0]
|
|
|
|
) # titre attribut: normalize, 1er mot seulement (nom du champ en BD)
|
|
|
|
typ, table, allow_nulls, description = [x.strip() for x in fs[1:5]]
|
|
|
|
aliases = [x.strip() for x in fs[5:] if x.strip()]
|
|
|
|
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))
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
2024-06-18 20:40:13 +02:00
|
|
|
def sco_import_format_dict(with_codesemestre=True, use_etudid: bool = False):
|
2020-12-02 01:00:23 +01:00
|
|
|
"""Attribut: { 'type': , 'table', 'allow_nulls' , 'description' }"""
|
2020-09-26 16:19:37 +02:00
|
|
|
fmt = sco_import_format(with_codesemestre=with_codesemestre)
|
2024-06-18 20:40:13 +02:00
|
|
|
formats = {}
|
2020-09-26 16:19:37 +02:00
|
|
|
for l in fmt:
|
2024-06-18 20:40:13 +02:00
|
|
|
formats[l[0]] = {
|
2020-09-26 16:19:37 +02:00
|
|
|
"type": l[1],
|
|
|
|
"table": l[2],
|
|
|
|
"allow_nulls": l[3],
|
|
|
|
"description": l[4],
|
|
|
|
"aliases": l[5],
|
|
|
|
}
|
2024-06-18 20:40:13 +02:00
|
|
|
if use_etudid:
|
|
|
|
formats["etudid"] = {
|
|
|
|
"type": "int",
|
|
|
|
"table": "identite",
|
|
|
|
"allow_nulls": False,
|
|
|
|
"description": "",
|
|
|
|
"aliases": ["etudid", "id"],
|
|
|
|
}
|
|
|
|
return formats
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def sco_import_generate_excel_sample(
|
|
|
|
fmt,
|
|
|
|
with_codesemestre=True,
|
|
|
|
only_tables=None,
|
|
|
|
with_groups=True,
|
2022-03-18 19:45:55 +01:00
|
|
|
exclude_cols=(),
|
|
|
|
extra_cols=(),
|
|
|
|
group_ids=(),
|
2020-09-26 16:19:37 +02:00
|
|
|
):
|
|
|
|
"""Generates an excel document based on format fmt
|
|
|
|
(format is the result of sco_import_format())
|
|
|
|
If not None, only_tables can specify a list of sql table names
|
|
|
|
(only columns from these tables will be generated)
|
|
|
|
If group_ids, liste les etudiants de ces groupes
|
|
|
|
"""
|
2021-08-12 14:55:25 +02:00
|
|
|
style = sco_excel.excel_make_style(bold=True)
|
2021-08-14 10:12:40 +02:00
|
|
|
style_required = sco_excel.excel_make_style(bold=True, color=COLORS.RED)
|
2020-09-26 16:19:37 +02:00
|
|
|
titles = []
|
2022-03-18 19:45:55 +01:00
|
|
|
titles_styles = []
|
2020-09-26 16:19:37 +02:00
|
|
|
for l in fmt:
|
2021-08-21 00:24:51 +02:00
|
|
|
name = l[0].lower()
|
2020-09-26 16:19:37 +02:00
|
|
|
if (not with_codesemestre) and name == "codesemestre":
|
|
|
|
continue # pas de colonne codesemestre
|
2021-08-21 00:24:51 +02:00
|
|
|
if only_tables is not None and l[2].lower() not in only_tables:
|
2020-09-26 16:19:37 +02:00
|
|
|
continue # table non demandée
|
|
|
|
if name in exclude_cols:
|
|
|
|
continue # colonne exclue
|
|
|
|
if int(l[3]):
|
2022-03-18 19:45:55 +01:00
|
|
|
titles_styles.append(style)
|
2020-09-26 16:19:37 +02:00
|
|
|
else:
|
2022-03-18 19:45:55 +01:00
|
|
|
titles_styles.append(style_required)
|
2020-09-26 16:19:37 +02:00
|
|
|
titles.append(name)
|
|
|
|
if with_groups and "groupes" not in titles:
|
|
|
|
titles.append("groupes")
|
2022-03-18 19:45:55 +01:00
|
|
|
titles_styles.append(style)
|
2020-09-26 16:19:37 +02:00
|
|
|
titles += extra_cols
|
2022-03-18 19:45:55 +01:00
|
|
|
titles_styles += [style] * len(extra_cols)
|
2021-08-21 00:24:51 +02:00
|
|
|
if group_ids:
|
2021-09-08 00:34:45 +02:00
|
|
|
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
2020-09-26 16:19:37 +02:00
|
|
|
members = groups_infos.members
|
|
|
|
log(
|
2024-06-18 20:40:13 +02:00
|
|
|
f"sco_import_generate_excel_sample: group_ids={group_ids}, {len(members)} members"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
titles = ["etudid"] + titles
|
2022-03-18 19:45:55 +01:00
|
|
|
titles_styles = [style] + titles_styles
|
2020-09-26 16:19:37 +02:00
|
|
|
# rempli table avec données actuelles
|
|
|
|
lines = []
|
|
|
|
for i in members:
|
2021-06-19 23:21:37 +02:00
|
|
|
etud = sco_etud.get_etud_info(etudid=i["etudid"], filled=True)[0]
|
2020-09-26 16:19:37 +02:00
|
|
|
l = []
|
|
|
|
for field in titles:
|
|
|
|
if field == "groupes":
|
|
|
|
sco_groups.etud_add_group_infos(
|
2023-03-01 14:39:36 +01:00
|
|
|
etud,
|
|
|
|
groups_infos.formsemestre_id,
|
|
|
|
sep=";",
|
|
|
|
with_default_partition=False,
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
l.append(etud["partitionsgroupes"])
|
|
|
|
else:
|
2021-08-21 00:24:51 +02:00
|
|
|
key = field.lower().split()[0]
|
2020-09-26 16:19:37 +02:00
|
|
|
l.append(etud.get(key, ""))
|
|
|
|
lines.append(l)
|
|
|
|
else:
|
|
|
|
lines = [[]] # empty content, titles only
|
2021-08-12 14:55:25 +02:00
|
|
|
return sco_excel.excel_simple_table(
|
2022-09-03 11:41:56 +02:00
|
|
|
titles=titles, titles_styles=titles_styles, sheet_name="Étudiants", lines=lines
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def students_import_excel(
|
|
|
|
csvfile,
|
|
|
|
formsemestre_id=None,
|
|
|
|
check_homonyms=True,
|
|
|
|
require_ine=False,
|
2021-09-27 10:20:10 +02:00
|
|
|
return_html=True,
|
2020-09-26 16:19:37 +02:00
|
|
|
):
|
|
|
|
"import students from Excel file"
|
|
|
|
diag = scolars_import_excel_file(
|
|
|
|
csvfile,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
check_homonyms=check_homonyms,
|
|
|
|
require_ine=require_ine,
|
|
|
|
exclude_cols=["photo_filename"],
|
|
|
|
)
|
2021-09-27 10:20:10 +02:00
|
|
|
if return_html:
|
2024-06-18 20:40:13 +02:00
|
|
|
dest_url = (
|
|
|
|
url_for(
|
2021-08-15 21:33:47 +02:00
|
|
|
"notes.formsemestre_status",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
)
|
2024-06-18 20:40:13 +02:00
|
|
|
if formsemestre_id
|
|
|
|
else url_for("notes.index_html", scodoc_dept=g.scodoc_dept)
|
|
|
|
)
|
2021-07-29 16:31:15 +02:00
|
|
|
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
2020-09-26 16:19:37 +02:00
|
|
|
H.append("<ul>")
|
|
|
|
for d in diag:
|
2024-06-18 20:40:13 +02:00
|
|
|
H.append(f"<li>{d}</li>")
|
|
|
|
H.append(
|
|
|
|
f"""
|
|
|
|
</ul>)
|
|
|
|
<p>Import terminé !</p>
|
|
|
|
<p><a class="stdlink" href="{dest_url}">Continuer</a></p>
|
|
|
|
"""
|
|
|
|
)
|
2021-07-29 10:19:00 +02:00
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
def scolars_import_excel_file(
|
2021-09-29 20:08:18 +02:00
|
|
|
datafile: io.BytesIO,
|
2020-09-26 16:19:37 +02:00
|
|
|
formsemestre_id=None,
|
|
|
|
check_homonyms=True,
|
|
|
|
require_ine=False,
|
2022-03-18 19:45:55 +01:00
|
|
|
exclude_cols=(),
|
2020-09-26 16:19:37 +02:00
|
|
|
):
|
|
|
|
"""Importe etudiants depuis fichier Excel
|
|
|
|
et les inscrit dans le semestre indiqué (et à TOUS ses modules)
|
|
|
|
"""
|
2023-03-02 22:55:25 +01:00
|
|
|
log(f"scolars_import_excel_file: {formsemestre_id}")
|
2021-11-12 22:17:46 +01:00
|
|
|
cnx = ndb.GetDBConnexion()
|
2021-02-03 22:00:41 +01:00
|
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
2020-09-26 16:19:37 +02:00
|
|
|
annee_courante = time.localtime()[0]
|
2021-07-28 17:03:54 +02:00
|
|
|
always_require_ine = sco_preferences.get_preference("always_require_ine")
|
2020-09-26 16:19:37 +02:00
|
|
|
exceldata = datafile.read()
|
|
|
|
if not exceldata:
|
|
|
|
raise ScoValueError("Ficher excel vide ou invalide")
|
2021-08-14 10:12:40 +02:00
|
|
|
diag, data = sco_excel.excel_bytes_to_list(exceldata)
|
2020-09-26 16:19:37 +02:00
|
|
|
if not data: # probably a bug
|
|
|
|
raise ScoException("scolars_import_excel_file: empty file !")
|
|
|
|
|
2020-12-02 01:00:23 +01:00
|
|
|
formsemestre_to_invalidate = set()
|
2020-09-26 16:19:37 +02:00
|
|
|
# 1- --- check title line
|
|
|
|
titles = {}
|
|
|
|
fmt = sco_import_format()
|
|
|
|
for l in fmt:
|
2021-08-21 00:24:51 +02:00
|
|
|
tit = l[0].lower().split()[0] # titles in lowercase, and take 1st word
|
2020-09-26 16:19:37 +02:00
|
|
|
if (
|
|
|
|
(not formsemestre_id) or (tit != "codesemestre")
|
|
|
|
) and tit not in exclude_cols:
|
|
|
|
titles[tit] = l[1:] # title : (Type, Table, AllowNulls, Description)
|
|
|
|
|
|
|
|
# remove quotes, downcase and keep only 1st word
|
|
|
|
try:
|
2021-08-21 00:24:51 +02:00
|
|
|
fs = [scu.stripquotes(s).lower().split()[0] for s in data[0]]
|
2023-03-02 22:55:25 +01:00
|
|
|
except Exception as exc:
|
|
|
|
raise ScoValueError("Titres de colonnes invalides (ou vides ?)") from exc
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
# check columns titles
|
|
|
|
if len(fs) != len(titles):
|
2021-07-09 17:47:06 +02:00
|
|
|
missing = {}.fromkeys(list(titles.keys()))
|
2020-09-26 16:19:37 +02:00
|
|
|
unknown = []
|
|
|
|
for f in fs:
|
2021-07-09 17:47:06 +02:00
|
|
|
if f in missing:
|
2020-09-26 16:19:37 +02:00
|
|
|
del missing[f]
|
|
|
|
else:
|
|
|
|
unknown.append(f)
|
|
|
|
raise ScoValueError(
|
2022-10-02 23:43:29 +02:00
|
|
|
"""Nombre de colonnes incorrect (devrait être %d, et non %d)<br>
|
2022-03-18 19:45:55 +01:00
|
|
|
(colonnes manquantes: %s, colonnes invalides: %s)"""
|
2021-07-09 17:47:06 +02:00
|
|
|
% (len(titles), len(fs), list(missing.keys()), unknown)
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
titleslist = []
|
|
|
|
for t in fs:
|
2021-07-09 17:47:06 +02:00
|
|
|
if t not in titles:
|
2024-06-18 20:40:13 +02:00
|
|
|
raise ScoValueError(f'Colonne invalide: "{t}"')
|
2020-09-26 16:19:37 +02:00
|
|
|
titleslist.append(t) #
|
|
|
|
# ok, same titles
|
|
|
|
# Start inserting data, abort whole transaction in case of error
|
|
|
|
created_etudids = []
|
2022-03-18 19:45:55 +01:00
|
|
|
np_imported_homonyms = 0
|
2024-06-18 20:40:13 +02:00
|
|
|
group_id_inferer = {}
|
2020-09-26 16:19:37 +02:00
|
|
|
try: # --- begin DB transaction
|
|
|
|
linenum = 0
|
|
|
|
for line in data[1:]:
|
|
|
|
linenum += 1
|
|
|
|
# Read fields, check and convert type
|
|
|
|
values = {}
|
|
|
|
fs = line
|
|
|
|
# remove quotes
|
2023-10-15 14:42:03 +02:00
|
|
|
for i, field in enumerate(fs):
|
|
|
|
if field and (
|
|
|
|
(field[0] == '"' and field[-1] == '"')
|
|
|
|
or (field[0] == "'" and field[-1] == "'")
|
2020-09-26 16:19:37 +02:00
|
|
|
):
|
2023-10-15 14:42:03 +02:00
|
|
|
fs[i] = field[1:-1]
|
|
|
|
for i, field in enumerate(fs):
|
|
|
|
val = field.strip()
|
|
|
|
typ, table, allow_nulls, descr, aliases = tuple(titles[titleslist[i]])
|
|
|
|
if not val and not allow_nulls:
|
2020-09-26 16:19:37 +02:00
|
|
|
raise ScoValueError(
|
2023-10-15 14:42:03 +02:00
|
|
|
f"line {linenum}: null value not allowed in column {titleslist[i]}"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
if val == "":
|
|
|
|
val = None
|
|
|
|
else:
|
|
|
|
if typ == "real":
|
|
|
|
val = val.replace(",", ".") # si virgule a la française
|
|
|
|
try:
|
|
|
|
val = float(val)
|
2023-10-15 14:42:03 +02:00
|
|
|
except (ValueError, TypeError) as exc:
|
2020-09-26 16:19:37 +02:00
|
|
|
raise ScoValueError(
|
2023-10-15 14:42:03 +02:00
|
|
|
f"""valeur nombre reel invalide ({
|
|
|
|
val}) sur ligne {linenum}, colonne {titleslist[i]}"""
|
|
|
|
) from exc
|
2020-09-26 16:19:37 +02:00
|
|
|
elif typ == "integer":
|
|
|
|
try:
|
|
|
|
# on doit accepter des valeurs comme "2006.0"
|
|
|
|
val = val.replace(",", ".") # si virgule a la française
|
|
|
|
val = float(val)
|
|
|
|
if val % 1.0 > 1e-4:
|
|
|
|
raise ValueError()
|
|
|
|
val = int(val)
|
2023-10-15 14:42:03 +02:00
|
|
|
except (ValueError, TypeError) as exc:
|
2020-09-26 16:19:37 +02:00
|
|
|
raise ScoValueError(
|
2023-10-15 14:42:03 +02:00
|
|
|
f"""valeur nombre entier invalide ({
|
|
|
|
val}) sur ligne {linenum}, colonne {titleslist[i]}"""
|
|
|
|
) from exc
|
|
|
|
# Ad-hoc checks (should be in format description)
|
|
|
|
if titleslist[i].lower() == "civilite":
|
2020-09-26 16:19:37 +02:00
|
|
|
try:
|
2023-05-15 19:50:11 +02:00
|
|
|
val = input_civilite(val)
|
2023-10-15 14:42:03 +02:00
|
|
|
except ScoValueError as exc:
|
2020-09-26 16:19:37 +02:00
|
|
|
raise ScoValueError(
|
2023-10-25 23:07:34 +02:00
|
|
|
f"""valeur invalide pour 'civilite'
|
2023-10-15 14:42:03 +02:00
|
|
|
(doit etre 'M', 'F', ou 'MME', 'H', 'X' mais pas '{
|
|
|
|
val}') ligne {linenum}, colonne {titleslist[i]}"""
|
|
|
|
) from exc
|
2023-10-17 22:12:28 +02:00
|
|
|
if titleslist[i].lower() == "civilite_etat_civil":
|
|
|
|
try:
|
|
|
|
val = input_civilite_etat_civil(val)
|
|
|
|
except ScoValueError as exc:
|
|
|
|
raise ScoValueError(
|
2023-10-25 23:07:34 +02:00
|
|
|
f"""valeur invalide pour 'civilite'
|
2023-10-17 22:12:28 +02:00
|
|
|
(doit etre 'M', 'F', vide ou 'MME', 'H', 'X' mais pas '{
|
|
|
|
val}') ligne {linenum}, colonne {titleslist[i]}"""
|
|
|
|
) from exc
|
2023-10-15 14:42:03 +02:00
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
# Excel date conversion:
|
2021-08-21 00:24:51 +02:00
|
|
|
if titleslist[i].lower() == "date_naissance":
|
2020-09-26 16:19:37 +02:00
|
|
|
if val:
|
2021-09-08 00:10:36 +02:00
|
|
|
try:
|
|
|
|
val = sco_excel.xldate_as_datetime(val)
|
2022-03-18 19:45:55 +01:00
|
|
|
except ValueError as exc:
|
2021-09-08 00:10:36 +02:00
|
|
|
raise ScoValueError(
|
2023-10-15 14:42:03 +02:00
|
|
|
f"""date invalide ({val}) sur ligne {
|
|
|
|
linenum}, colonne {titleslist[i]}"""
|
2022-03-18 19:45:55 +01:00
|
|
|
) from exc
|
2020-09-26 16:19:37 +02:00
|
|
|
# INE
|
|
|
|
if (
|
2021-08-21 00:24:51 +02:00
|
|
|
titleslist[i].lower() == "code_ine"
|
2020-09-26 16:19:37 +02:00
|
|
|
and always_require_ine
|
|
|
|
and not val
|
|
|
|
):
|
|
|
|
raise ScoValueError(
|
2023-10-15 14:42:03 +02:00
|
|
|
"Code INE manquant sur ligne {linenum}, colonne {titleslist[i]}"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# --
|
|
|
|
values[titleslist[i]] = val
|
|
|
|
skip = False
|
|
|
|
is_new_ine = values["code_ine"] and _is_new_ine(cnx, values["code_ine"])
|
|
|
|
if require_ine and not is_new_ine:
|
|
|
|
log("skipping %s (code_ine=%s)" % (values["nom"], values["code_ine"]))
|
|
|
|
skip = True
|
|
|
|
|
|
|
|
if not skip:
|
|
|
|
if values["code_ine"] and not is_new_ine:
|
|
|
|
raise ScoValueError("Code INE dupliqué (%s)" % values["code_ine"])
|
|
|
|
# Check nom/prenom
|
2022-03-18 19:45:55 +01:00
|
|
|
ok = False
|
2023-10-08 10:01:23 +02:00
|
|
|
homonyms = []
|
2022-03-18 19:45:55 +01:00
|
|
|
if "nom" in values and "prenom" in values:
|
2023-10-08 10:01:23 +02:00
|
|
|
ok, homonyms = sco_etud.check_nom_prenom_homonyms(
|
|
|
|
nom=values["nom"], prenom=values["prenom"]
|
2022-03-18 19:45:55 +01:00
|
|
|
)
|
2020-09-26 16:19:37 +02:00
|
|
|
if not ok:
|
|
|
|
raise ScoValueError(
|
2023-10-08 10:01:23 +02:00
|
|
|
f"nom ou prénom invalide sur la ligne {linenum}"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
2023-10-08 10:01:23 +02:00
|
|
|
if homonyms:
|
2022-03-18 19:45:55 +01:00
|
|
|
np_imported_homonyms += 1
|
2020-09-26 16:19:37 +02:00
|
|
|
# Insert in DB tables
|
2023-10-08 10:01:23 +02:00
|
|
|
_import_one_student(
|
2021-09-29 20:08:18 +02:00
|
|
|
formsemestre_id,
|
|
|
|
values,
|
2024-06-18 20:40:13 +02:00
|
|
|
group_id_inferer,
|
2021-09-29 20:08:18 +02:00
|
|
|
annee_courante,
|
|
|
|
created_etudids,
|
|
|
|
linenum,
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
# Verification proportion d'homonymes: si > 10%, abandonne
|
2023-10-08 10:01:23 +02:00
|
|
|
log(f"scolars_import_excel_file: detected {np_imported_homonyms} homonyms")
|
2022-03-18 19:45:55 +01:00
|
|
|
if check_homonyms and np_imported_homonyms > len(created_etudids) / 10:
|
2020-09-26 16:19:37 +02:00
|
|
|
log("scolars_import_excel_file: too many homonyms")
|
|
|
|
raise ScoValueError(
|
2023-10-08 10:01:23 +02:00
|
|
|
f"Il y a trop d'homonymes ({np_imported_homonyms} étudiants)"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
except:
|
|
|
|
cnx.rollback()
|
|
|
|
log("scolars_import_excel_file: aborting transaction !")
|
|
|
|
# Nota: db transaction is sometimes partly commited...
|
|
|
|
# here we try to remove all created students
|
2021-02-03 22:00:41 +01:00
|
|
|
cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor)
|
2020-09-26 16:19:37 +02:00
|
|
|
for etudid in created_etudids:
|
2023-10-08 10:01:23 +02:00
|
|
|
log(f"scolars_import_excel_file: deleting etudid={etudid}")
|
2020-09-26 16:19:37 +02:00
|
|
|
cursor.execute(
|
|
|
|
"delete from notes_moduleimpl_inscription where etudid=%(etudid)s",
|
|
|
|
{"etudid": etudid},
|
|
|
|
)
|
|
|
|
cursor.execute(
|
|
|
|
"delete from notes_formsemestre_inscription where etudid=%(etudid)s",
|
|
|
|
{"etudid": etudid},
|
|
|
|
)
|
|
|
|
cursor.execute(
|
|
|
|
"delete from scolar_events where etudid=%(etudid)s", {"etudid": etudid}
|
|
|
|
)
|
|
|
|
cursor.execute(
|
|
|
|
"delete from adresse where etudid=%(etudid)s", {"etudid": etudid}
|
|
|
|
)
|
|
|
|
cursor.execute(
|
|
|
|
"delete from group_membership where etudid=%(etudid)s",
|
|
|
|
{"etudid": etudid},
|
|
|
|
)
|
|
|
|
cursor.execute(
|
2021-08-15 21:33:47 +02:00
|
|
|
"delete from identite where id=%(etudid)s", {"etudid": etudid}
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
cnx.commit()
|
|
|
|
log("scolars_import_excel_file: re-raising exception")
|
|
|
|
raise
|
|
|
|
|
|
|
|
diag.append("Import et inscription de %s étudiants" % len(created_etudids))
|
|
|
|
|
2022-04-12 17:12:51 +02:00
|
|
|
ScolarNews.add(
|
|
|
|
typ=ScolarNews.NEWS_INSCR,
|
2020-09-26 16:19:37 +02:00
|
|
|
text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents
|
|
|
|
% len(created_etudids),
|
2022-04-12 17:12:51 +02:00
|
|
|
obj=formsemestre_id,
|
2023-06-23 10:37:32 +02:00
|
|
|
max_frequency=0,
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
log("scolars_import_excel_file: completing transaction")
|
|
|
|
cnx.commit()
|
|
|
|
|
|
|
|
# Invalide les caches des semestres dans lesquels on a inscrit des etudiants:
|
2021-07-19 19:53:01 +02:00
|
|
|
for formsemestre_id in formsemestre_to_invalidate:
|
|
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
return diag
|
|
|
|
|
|
|
|
|
2021-06-21 12:13:25 +02:00
|
|
|
def students_import_admission(
|
2024-06-18 20:40:13 +02:00
|
|
|
csvfile, type_admission="", formsemestre_id=None, return_html=True, use_etudid=False
|
|
|
|
) -> str:
|
2021-06-21 12:13:25 +02:00
|
|
|
"import donnees admission from Excel file (v2016)"
|
|
|
|
diag = scolars_import_admission(
|
|
|
|
csvfile,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
type_admission=type_admission,
|
2024-06-18 20:40:13 +02:00
|
|
|
use_etudid=use_etudid,
|
2021-06-21 12:13:25 +02:00
|
|
|
)
|
2021-09-27 10:20:10 +02:00
|
|
|
if return_html:
|
2021-07-29 16:31:15 +02:00
|
|
|
H = [html_sco_header.sco_header(page_title="Import données admissions")]
|
2021-06-21 12:13:25 +02:00
|
|
|
H.append("<p>Import terminé !</p>")
|
|
|
|
H.append(
|
2024-02-02 18:23:35 +01:00
|
|
|
f"""<p><a class="stdlink" href="{ url_for(
|
2021-08-15 21:33:47 +02:00
|
|
|
"notes.formsemestre_status",
|
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
)
|
2024-02-02 18:23:35 +01:00
|
|
|
}">Continuer</a></p>"""
|
2021-06-21 12:13:25 +02:00
|
|
|
)
|
|
|
|
if diag:
|
2024-02-02 18:23:35 +01:00
|
|
|
H.append(
|
|
|
|
f"""<p>Diagnostic: <ul><li>{
|
|
|
|
"</li><li>".join(diag)
|
|
|
|
}</li></ul></p>
|
|
|
|
"""
|
|
|
|
)
|
2021-06-21 12:13:25 +02:00
|
|
|
|
2021-07-29 10:19:00 +02:00
|
|
|
return "\n".join(H) + html_sco_header.sco_footer()
|
2024-06-18 20:40:13 +02:00
|
|
|
return ""
|
2021-06-21 12:13:25 +02:00
|
|
|
|
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
def _import_one_student(
|
|
|
|
formsemestre_id,
|
|
|
|
values,
|
|
|
|
GroupIdInferers,
|
|
|
|
annee_courante,
|
|
|
|
created_etudids,
|
|
|
|
linenum,
|
2021-09-29 20:08:18 +02:00
|
|
|
) -> int:
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
|
|
|
Import d'un étudiant et inscription dans le semestre.
|
|
|
|
Return: id du semestre dans lequel il a été inscrit.
|
|
|
|
"""
|
2023-10-15 14:42:03 +02:00
|
|
|
log(f"scolars_import_excel_file: formsemestre_id={formsemestre_id} values={values}")
|
2020-09-26 16:19:37 +02:00
|
|
|
# Identite
|
|
|
|
args = values.copy()
|
|
|
|
args["annee"] = annee_courante
|
2023-10-15 14:42:03 +02:00
|
|
|
etud: Identite = Identite.create_from_dict(args)
|
|
|
|
etud.admission.from_dict(args)
|
|
|
|
etudid = etud.id
|
|
|
|
created_etudids.append(etudid)
|
2020-09-26 16:19:37 +02:00
|
|
|
# Adresse
|
|
|
|
args["typeadresse"] = "domicile"
|
|
|
|
args["description"] = "(infos admission)"
|
2023-10-15 14:42:03 +02:00
|
|
|
adresse = etud.adresses.first()
|
|
|
|
adresse.from_dict(args)
|
|
|
|
db.session.add(etud)
|
|
|
|
db.session.commit()
|
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
# Inscription au semestre
|
2022-09-30 22:43:39 +02:00
|
|
|
args["etat"] = scu.INSCRIT # etat insc. semestre
|
2020-09-26 16:19:37 +02:00
|
|
|
if formsemestre_id:
|
|
|
|
args["formsemestre_id"] = formsemestre_id
|
|
|
|
else:
|
|
|
|
args["formsemestre_id"] = values["codesemestre"]
|
|
|
|
formsemestre_id = values["codesemestre"]
|
2021-10-07 22:24:43 +02:00
|
|
|
try:
|
|
|
|
formsemestre_id = int(formsemestre_id)
|
2022-01-15 14:59:59 +01:00
|
|
|
except (ValueError, TypeError) as exc:
|
2021-10-07 22:24:43 +02:00
|
|
|
raise ScoValueError(
|
2022-01-15 14:59:59 +01:00
|
|
|
f"valeur invalide ou manquante dans la colonne codesemestre, ligne {linenum+1}"
|
2021-10-07 22:24:43 +02:00
|
|
|
) from exc
|
2020-09-26 16:19:37 +02:00
|
|
|
# recupere liste des groupes:
|
|
|
|
if formsemestre_id not in GroupIdInferers:
|
2021-08-19 10:28:35 +02:00
|
|
|
GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer(formsemestre_id)
|
2020-09-26 16:19:37 +02:00
|
|
|
gi = GroupIdInferers[formsemestre_id]
|
|
|
|
if args["groupes"]:
|
|
|
|
groupes = args["groupes"].split(";")
|
|
|
|
else:
|
|
|
|
groupes = []
|
|
|
|
group_ids = [gi[group_name] for group_name in groupes]
|
2021-07-09 17:47:06 +02:00
|
|
|
group_ids = list({}.fromkeys(group_ids).keys()) # uniq
|
2020-09-26 16:19:37 +02:00
|
|
|
if None in group_ids:
|
|
|
|
raise ScoValueError(
|
2023-10-15 14:42:03 +02:00
|
|
|
f"groupe invalide sur la ligne {linenum} (groupe {groupes})"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
do_formsemestre_inscription_with_modules(
|
2021-09-29 20:08:18 +02:00
|
|
|
int(args["formsemestre_id"]),
|
2020-09-26 16:19:37 +02:00
|
|
|
etudid,
|
|
|
|
group_ids,
|
2022-09-30 22:43:39 +02:00
|
|
|
etat=scu.INSCRIT,
|
2020-09-26 16:19:37 +02:00
|
|
|
method="import_csv_file",
|
|
|
|
)
|
|
|
|
return args["formsemestre_id"]
|
|
|
|
|
|
|
|
|
|
|
|
def _is_new_ine(cnx, code_ine):
|
|
|
|
"True if this code is not in DB"
|
2021-06-19 23:21:37 +02:00
|
|
|
etuds = sco_etud.identite_list(cnx, {"code_ine": code_ine})
|
2020-09-26 16:19:37 +02:00
|
|
|
return not etuds
|
|
|
|
|
|
|
|
|
|
|
|
# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB)
|
2024-06-18 20:40:13 +02:00
|
|
|
def scolars_import_admission(
|
|
|
|
datafile, formsemestre_id=None, type_admission=None, use_etudid=False
|
|
|
|
):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Importe données admission depuis un fichier Excel quelconque
|
2024-06-18 20:40:13 +02:00
|
|
|
par exemple ceux utilisés avec APB, avec ou sans etudid
|
2020-09-26 16:19:37 +02:00
|
|
|
|
2020-12-02 01:00:23 +01:00
|
|
|
Cherche dans ce fichier les étudiants qui correspondent à des inscrits du
|
2020-09-26 16:19:37 +02:00
|
|
|
semestre formsemestre_id.
|
2024-06-18 20:40:13 +02:00
|
|
|
Si le fichier n'a pas d'etudid (use_etudid faux), la correspondance se fait
|
2020-12-02 01:00:23 +01:00
|
|
|
via les noms/prénoms qui doivent être égaux (la casse, les accents et caractères spéciaux
|
2020-09-26 16:19:37 +02:00
|
|
|
étant ignorés).
|
|
|
|
|
2020-12-02 01:00:23 +01:00
|
|
|
On tolère plusieurs variantes pour chaque nom de colonne (ici aussi, la casse, les espaces
|
2023-10-15 14:42:03 +02:00
|
|
|
et les caractères spéciaux sont ignorés.
|
|
|
|
Ainsi, la colonne "Prénom:" sera considéré comme "prenom".
|
2020-09-26 16:19:37 +02:00
|
|
|
|
2023-10-15 14:42:03 +02:00
|
|
|
Le parametre type_admission remplace les valeurs vides (dans la base ET
|
|
|
|
dans le fichier importé) du champ type_admission.
|
2020-09-26 16:19:37 +02:00
|
|
|
Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré.
|
2020-12-02 01:00:23 +01:00
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
"""
|
2023-10-15 14:42:03 +02:00
|
|
|
log(f"scolars_import_admission: formsemestre_id={formsemestre_id}")
|
2024-06-18 20:40:13 +02:00
|
|
|
diag: list[str] = []
|
2020-09-26 16:19:37 +02:00
|
|
|
members = sco_groups.get_group_members(
|
2021-08-19 10:28:35 +02:00
|
|
|
sco_groups.get_default_group(formsemestre_id)
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
etuds_by_nomprenom = {} # { nomprenom : etud }
|
2024-06-18 20:40:13 +02:00
|
|
|
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
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
exceldata = datafile.read()
|
2021-08-14 10:12:40 +02:00
|
|
|
diag2, data = sco_excel.excel_bytes_to_list(exceldata)
|
2020-09-26 16:19:37 +02:00
|
|
|
if not data:
|
|
|
|
raise ScoException("scolars_import_admission: empty file !")
|
|
|
|
diag += diag2
|
2021-06-15 13:59:56 +02:00
|
|
|
cnx = ndb.GetDBConnexion()
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
titles = data[0]
|
|
|
|
# idx -> ('field', convertor)
|
2024-06-18 20:40:13 +02:00
|
|
|
fields = adm_get_fields(titles, formsemestre_id, use_etudid=use_etudid)
|
|
|
|
idx_nom = idx_prenom = idx_etudid = None
|
2023-07-08 13:57:44 +02:00
|
|
|
for idx, field in fields.items():
|
2024-06-18 20:40:13 +02:00
|
|
|
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
|
|
|
|
):
|
2020-09-26 16:19:37 +02:00
|
|
|
log("fields indices=" + ", ".join([str(x) for x in fields]))
|
2024-06-18 20:40:13 +02:00
|
|
|
log("fields titles =" + ", ".join([x[0] for x in fields.values()]))
|
2021-12-03 14:13:49 +01:00
|
|
|
raise ScoFormatError(
|
2024-06-18 20:40:13 +02:00
|
|
|
(
|
|
|
|
"""colonne etudid requise
|
|
|
|
(si l'option "Utiliser l'identifiant d'étudiant ScoDoc" est cochée)"""
|
|
|
|
if use_etudid
|
|
|
|
else "colonnes nom et prenom requises"
|
|
|
|
),
|
2021-08-15 21:33:47 +02:00
|
|
|
dest_url=url_for(
|
2021-11-06 17:58:11 +01:00
|
|
|
"scolar.form_students_import_infos_admissions",
|
2021-08-15 21:33:47 +02:00
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
),
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
2020-12-02 01:00:23 +01:00
|
|
|
modifiable_fields = set(ADMISSION_MODIFIABLE_FIELDS)
|
2024-06-18 20:40:13 +02:00
|
|
|
if use_etudid:
|
|
|
|
modifiable_fields |= {"nom", "prenom"}
|
2020-09-26 16:19:37 +02:00
|
|
|
|
|
|
|
nline = 2 # la premiere ligne de donnees du fichier excel est 2
|
|
|
|
n_import = 0
|
|
|
|
for line in data[1:]:
|
2024-06-18 20:40:13 +02:00
|
|
|
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=<b>{line[idx_etudid]}</b> inexistant"""
|
|
|
|
diag.append(msg)
|
2020-09-26 16:19:37 +02:00
|
|
|
else:
|
2024-06-18 20:40:13 +02:00
|
|
|
# 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 <b>{line[idx_nom]} {line[idx_prenom]}</b> inexistant"""
|
|
|
|
)
|
|
|
|
diag.append(msg)
|
|
|
|
if etud:
|
2023-10-15 14:42:03 +02:00
|
|
|
cur_adm = sco_etud.admission_list(cnx, args={"id": etud["admission_id"]})[0]
|
2020-09-26 16:19:37 +02:00
|
|
|
# peuple les champs presents dans le tableau
|
|
|
|
args = {}
|
2023-07-08 13:57:44 +02:00
|
|
|
for idx, field in fields.items():
|
|
|
|
field_name, convertor = field
|
2020-09-26 16:19:37 +02:00
|
|
|
if field_name in modifiable_fields:
|
|
|
|
try:
|
|
|
|
val = convertor(line[idx])
|
2023-07-08 13:57:44 +02:00
|
|
|
except ValueError as exc:
|
2021-12-03 14:13:49 +01:00
|
|
|
raise ScoFormatError(
|
2023-10-15 14:42:03 +02:00
|
|
|
f"""scolars_import_admission: valeur invalide, ligne {
|
|
|
|
nline} colonne {field_name}: '{line[idx]}'""",
|
2021-08-15 21:33:47 +02:00
|
|
|
dest_url=url_for(
|
2021-11-06 17:58:11 +01:00
|
|
|
"scolar.form_students_import_infos_admissions",
|
2021-08-15 21:33:47 +02:00
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
),
|
2023-07-08 13:57:44 +02:00
|
|
|
) from exc
|
2020-09-26 16:19:37 +02:00
|
|
|
if val is not None: # note: ne peut jamais supprimer une valeur
|
|
|
|
args[field_name] = val
|
|
|
|
if args:
|
|
|
|
args["etudid"] = etud["etudid"]
|
|
|
|
args["adm_id"] = cur_adm["adm_id"]
|
|
|
|
# Type admission: traitement particulier
|
|
|
|
if not cur_adm["type_admission"] and not args.get("type_admission"):
|
|
|
|
args["type_admission"] = type_admission
|
2024-08-23 04:30:53 +02:00
|
|
|
sco_etud.etudident_edit( # TODO utiliser modèle
|
|
|
|
cnx, args, disable_notify=True
|
|
|
|
)
|
2021-06-19 23:21:37 +02:00
|
|
|
adr = sco_etud.adresse_list(cnx, args={"etudid": etud["etudid"]})
|
2020-09-26 16:19:37 +02:00
|
|
|
if adr:
|
|
|
|
args["adresse_id"] = adr[0]["adresse_id"]
|
2024-08-23 04:30:53 +02:00
|
|
|
sco_etud.adresse_edit( # TODO utiliser modèle
|
2021-08-21 00:24:51 +02:00
|
|
|
cnx, args, disable_notify=True
|
|
|
|
) # pas de notification ici
|
2020-09-26 16:19:37 +02:00
|
|
|
else:
|
|
|
|
args["typeadresse"] = "domicile"
|
|
|
|
args["description"] = "(infos admission)"
|
2024-08-23 04:30:53 +02:00
|
|
|
adresse_id = sco_etud.adresse_create( # TODO utiliser modèle
|
|
|
|
cnx, args
|
|
|
|
)
|
2020-09-26 16:19:37 +02:00
|
|
|
# log('import_adm: %s' % args )
|
|
|
|
# Change les groupes si nécessaire:
|
2022-09-14 10:15:27 +02:00
|
|
|
if "groupes" in args:
|
2021-08-19 10:28:35 +02:00
|
|
|
gi = sco_groups.GroupIdInferer(formsemestre_id)
|
2020-09-26 16:19:37 +02:00
|
|
|
groupes = args["groupes"].split(";")
|
|
|
|
group_ids = [gi[group_name] for group_name in groupes]
|
2021-07-09 17:47:06 +02:00
|
|
|
group_ids = list({}.fromkeys(group_ids).keys()) # uniq
|
2020-09-26 16:19:37 +02:00
|
|
|
if None in group_ids:
|
|
|
|
raise ScoValueError(
|
2022-09-14 10:15:27 +02:00
|
|
|
f"groupe invalide sur la ligne {nline} (groupes {groupes})"
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
for group_id in group_ids:
|
2024-07-19 09:42:44 +02:00
|
|
|
group: GroupDescr = GroupDescr.get_instance(group_id)
|
2022-05-26 23:45:57 +02:00
|
|
|
if group.partition.groups_editable:
|
|
|
|
sco_groups.change_etud_group_in_partition(
|
2023-07-05 19:15:33 +02:00
|
|
|
args["etudid"], group
|
2022-05-26 23:45:57 +02:00
|
|
|
)
|
2024-07-19 09:42:44 +02:00
|
|
|
elif not group.partition.is_parcours:
|
2022-05-26 23:45:57 +02:00
|
|
|
log("scolars_import_admission: partition non editable")
|
|
|
|
diag.append(
|
2024-07-19 09:42:44 +02:00
|
|
|
f"""Attention: partition {
|
|
|
|
group.partition} (g{group.id}) non editable et ignorée"""
|
2022-05-26 23:45:57 +02:00
|
|
|
)
|
|
|
|
|
2020-09-26 16:19:37 +02:00
|
|
|
#
|
2023-10-15 14:42:03 +02:00
|
|
|
diag.append(f"import de {etud['nomprenom']}")
|
2020-09-26 16:19:37 +02:00
|
|
|
n_import += 1
|
|
|
|
nline += 1
|
2023-10-15 14:42:03 +02:00
|
|
|
diag.append(f"{n_import} lignes importées")
|
2020-09-26 16:19:37 +02:00
|
|
|
if n_import > 0:
|
2021-07-19 19:53:01 +02:00
|
|
|
sco_cache.invalidate_formsemestre(formsemestre_id=formsemestre_id)
|
2020-09-26 16:19:37 +02:00
|
|
|
return diag
|
|
|
|
|
|
|
|
|
|
|
|
_ADM_PATTERN = re.compile(r"[\W]+", re.UNICODE) # supprime tout sauf alphanum
|
|
|
|
|
|
|
|
|
2022-09-18 16:53:00 +02:00
|
|
|
def adm_normalize_string(s):
|
|
|
|
"normalize unicode title"
|
2021-07-12 11:54:04 +02:00
|
|
|
return scu.suppress_accents(_ADM_PATTERN.sub("", s.strip().lower())).replace(
|
2020-09-26 16:19:37 +02:00
|
|
|
"_", ""
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-06-18 20:40:13 +02:00
|
|
|
def adm_get_fields(titles, formsemestre_id: int, use_etudid: bool = False):
|
2020-09-26 16:19:37 +02:00
|
|
|
"""Cherche les colonnes importables dans les titres (ligne 1) du fichier excel
|
|
|
|
return: { idx : (field_name, convertor) }
|
|
|
|
"""
|
2024-06-18 20:40:13 +02:00
|
|
|
format_dict = sco_import_format_dict(use_etudid=use_etudid)
|
2020-09-26 16:19:37 +02:00
|
|
|
fields = {}
|
|
|
|
idx = 0
|
|
|
|
for title in titles:
|
|
|
|
title_n = adm_normalize_string(title)
|
2024-06-18 20:40:13 +02:00
|
|
|
for k, fmt in format_dict.items():
|
|
|
|
for v in fmt["aliases"]:
|
2020-09-26 16:19:37 +02:00
|
|
|
if adm_normalize_string(v) == title_n:
|
2024-06-18 20:40:13 +02:00
|
|
|
typ = fmt["type"]
|
2020-09-26 16:19:37 +02:00
|
|
|
if typ == "real":
|
|
|
|
convertor = adm_convert_real
|
|
|
|
elif typ == "integer" or typ == "int":
|
|
|
|
convertor = adm_convert_int
|
|
|
|
else:
|
|
|
|
convertor = adm_convert_text
|
|
|
|
# doublons ?
|
|
|
|
if k in [x[0] for x in fields.values()]:
|
2021-12-03 14:13:49 +01:00
|
|
|
raise ScoFormatError(
|
2022-09-18 16:53:00 +02:00
|
|
|
f"""scolars_import_admission: titre "{title}" en double (ligne 1)""",
|
2021-08-15 21:33:47 +02:00
|
|
|
dest_url=url_for(
|
2022-09-18 16:53:00 +02:00
|
|
|
"scolar.form_students_import_infos_admissions",
|
2021-08-15 21:33:47 +02:00
|
|
|
scodoc_dept=g.scodoc_dept,
|
|
|
|
formsemestre_id=formsemestre_id,
|
|
|
|
),
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
fields[idx] = (k, convertor)
|
|
|
|
idx += 1
|
|
|
|
|
|
|
|
return fields
|
|
|
|
|
|
|
|
|
|
|
|
def adm_convert_text(v):
|
2021-07-11 22:32:01 +02:00
|
|
|
if isinstance(v, float):
|
2020-09-26 16:19:37 +02:00
|
|
|
return "{:g}".format(v) # evite "1.0"
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
def adm_convert_int(v):
|
2021-07-09 17:47:06 +02:00
|
|
|
if type(v) != int and not v:
|
2020-09-26 16:19:37 +02:00
|
|
|
return None
|
|
|
|
return int(float(v)) # accept "10.0"
|
|
|
|
|
|
|
|
|
|
|
|
def adm_convert_real(v):
|
2021-07-09 17:47:06 +02:00
|
|
|
if type(v) != float and not v:
|
2020-09-26 16:19:37 +02:00
|
|
|
return None
|
|
|
|
return float(v)
|
|
|
|
|
|
|
|
|
2021-08-21 00:24:51 +02:00
|
|
|
def adm_table_description_format():
|
2020-12-02 01:00:23 +01:00
|
|
|
"""Table HTML (ou autre format) decrivant les donnees d'admissions importables"""
|
2020-09-26 16:19:37 +02:00
|
|
|
Fmt = sco_import_format_dict(with_codesemestre=False)
|
|
|
|
for k in Fmt:
|
|
|
|
Fmt[k]["attribute"] = k
|
|
|
|
Fmt[k]["aliases_str"] = ", ".join(Fmt[k]["aliases"])
|
|
|
|
if not Fmt[k]["allow_nulls"]:
|
|
|
|
Fmt[k]["required"] = "*"
|
|
|
|
if k in ADMISSION_MODIFIABLE_FIELDS:
|
|
|
|
Fmt[k]["writable"] = "oui"
|
|
|
|
else:
|
|
|
|
Fmt[k]["writable"] = "non"
|
|
|
|
titles = {
|
|
|
|
"attribute": "Attribut",
|
|
|
|
"type": "Type",
|
|
|
|
"required": "Requis",
|
|
|
|
"writable": "Modifiable",
|
|
|
|
"description": "Description",
|
|
|
|
"aliases_str": "Titres (variantes)",
|
|
|
|
}
|
|
|
|
columns_ids = ("attribute", "type", "writable", "description", "aliases_str")
|
|
|
|
|
|
|
|
tab = GenTable(
|
|
|
|
columns_ids=columns_ids,
|
|
|
|
html_class="table_leftalign",
|
2024-05-22 00:06:30 +02:00
|
|
|
html_sortable=True,
|
2021-07-28 17:03:54 +02:00
|
|
|
preferences=sco_preferences.SemPreferences(),
|
2024-05-22 00:06:30 +02:00
|
|
|
rows=list(Fmt.values()),
|
|
|
|
table_id="adm_table_description_format",
|
|
|
|
titles=titles,
|
2020-09-26 16:19:37 +02:00
|
|
|
)
|
|
|
|
return tab
|