Export/Import d'étudiant via fichiers xlsx.

Point délicats:
* Le message d'erreur pour une case vide était une exception python.
diagnostic: la création de l'étudiant dans la BDD se faisait avant le controle de la civilité et plantait quand None
correctif: ajout d'une methode _check_civilite (a cote des méthodes de contrôle d unicité de nip et d ine (sco_etud.py)
* Le format de date a changé entre pyExcelerator et openpyxl (réécriture de sco_excel.xldate_as_datetime)
le format xlxs d import précise qu'une date peut être spécifié soit en ISO soit sous forme d'un nombre.
c est testé avec des écriture de fichier xlsx depuis Excel 2019 et LibreOffice 7 (mais sans maitrise sur la forme de date utilisée)
par contre plantage si tentative de lire un fichier ods (fonction excel_bytes_to_list a fixer)
* Le renvoi vers la page de formation_id se faisait mal
correction: calcul de l'url (sco_import_etuds.py:245) et (scolar.py:1710 celle-ci peut être pas necessaire)
This commit is contained in:
Jean-Marie Place 2021-08-14 10:12:40 +02:00
parent 7372a953fa
commit 432831140c
4 changed files with 59 additions and 43 deletions

View File

@ -366,6 +366,11 @@ def _check_duplicate_code(cnx, args, code_name, context, edit=True, REQUEST=None
raise ScoGenError(err_page)
def _check_civilite(args):
civilite = args.get("civilite", "X") or "X"
args["civilite"] = input_civilite(civilite) # TODO: A faire valider
def identite_edit(cnx, args, context=None, REQUEST=None):
"""Modifie l'identite d'un étudiant.
Si context et notification et difference, envoie message notification.
@ -403,6 +408,7 @@ def identite_create(cnx, args, context=None, REQUEST=None):
"check unique etudid, then create"
_check_duplicate_code(cnx, args, "code_nip", context, edit=False, REQUEST=REQUEST)
_check_duplicate_code(cnx, args, "code_ine", context, edit=False, REQUEST=REQUEST)
_check_civilite(args)
if "etudid" in args:
etudid = args["etudid"]
@ -755,7 +761,6 @@ _etud_annotationsEditor = ndb.EditableTable(
output_formators={"comment": safehtml.html_to_safe_html, "date": ndb.DateISOtoDMY},
)
etud_annotations_create = _etud_annotationsEditor.create
etud_annotations_delete = _etud_annotationsEditor.delete
etud_annotations_list = _etud_annotationsEditor.list
@ -1049,4 +1054,4 @@ def descr_situation_etud(context, etudid, ne=""):
else:
date_dem = events[0]["event_date"]
situation += " le " + str(date_dem)
return situation
return situation

View File

@ -34,6 +34,7 @@ import time
from enum import Enum
from tempfile import NamedTemporaryFile
import openpyxl.utils.datetime
from openpyxl import Workbook, load_workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
@ -86,40 +87,42 @@ def send_excel_file(request, data, filename, mime=scu.XLSX_MIMETYPE):
# a datetime.time object will be returned.
# <br>Note: 1904-01-01 is not regarded as a valid date in the datemode 1 system; its "serial number"
# is zero.
_XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462) # This is equivalent to 10000-01-01
#
# _XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462) # This is equivalent to 10000-01-01
#
def xldate_as_datetime(xldate, datemode=0):
if datemode not in (0, 1):
raise ValueError("invalid mode %s" % datemode)
if xldate == 0.00:
return datetime.time(0, 0, 0)
if xldate < 0.00:
raise ValueError("invalid date code %s" % xldate)
xldays = int(xldate)
frac = xldate - xldays
seconds = int(round(frac * 86400.0))
assert 0 <= seconds <= 86400
if seconds == 86400:
seconds = 0
xldays += 1
if xldays >= _XLDAYS_TOO_LARGE[datemode]:
raise ValueError("date too large %s" % xldate)
if xldays == 0:
# second = seconds % 60; minutes = seconds // 60
minutes, second = divmod(seconds, 60)
# minute = minutes % 60; hour = minutes // 60
hour, minute = divmod(minutes, 60)
return datetime.time(hour, minute, second)
if xldays < 61 and datemode == 0:
raise ValueError("ambiguous date %s" % xldate)
return datetime.datetime.fromordinal(
xldays + 693594 + 1462 * datemode
) + datetime.timedelta(seconds=seconds)
return openpyxl.utils.datetime.from_ISO8601(xldate)
# if datemode not in (0, 1):
# raise ValueError("invalid mode %s" % datemode)
# if xldate == 0.00:
# return datetime.time(0, 0, 0)
# if xldate < 0.00:
# raise ValueError("invalid date code %s" % xldate)
# xldays = int(xldate)
# frac = xldate - xldays
# seconds = int(round(frac * 86400.0))
# assert 0 <= seconds <= 86400
# if seconds == 86400:
# seconds = 0
# xldays += 1
# if xldays >= _XLDAYS_TOO_LARGE[datemode]:
# raise ValueError("date too large %s" % xldate)
#
# if xldays == 0:
# # second = seconds % 60; minutes = seconds // 60
# minutes, second = divmod(seconds, 60)
# # minute = minutes % 60; hour = minutes // 60
# hour, minute = divmod(minutes, 60)
# return datetime.time(hour, minute, second)
#
# if xldays < 61 and datemode == 0:
# raise ValueError("ambiguous date %s" % xldate)
#
# return datetime.datetime.fromordinal(
# xldays + 693594 + 1462 * datemode
# ) + datetime.timedelta(seconds=seconds)
class ScoExcelBook:

View File

@ -32,10 +32,14 @@ import collections
import os
import re
import time
from datetime import date
import flask
import app.scodoc.sco_utils as scu
import app.scodoc.notesdb as ndb
from app.scodoc.notes_log import log
from app.scodoc.sco_excel import COLORS
from app.scodoc.sco_formsemestre_inscriptions import (
do_formsemestre_inscription_with_modules,
)
@ -164,7 +168,7 @@ def sco_import_generate_excel_sample(
If group_ids, liste les etudiants de ces groupes
"""
style = sco_excel.excel_make_style(bold=True)
style_required = sco_excel.excel_make_style(bold=True, color="red")
style_required = sco_excel.excel_make_style(bold=True, color=COLORS.RED)
titles = []
titlesStyles = []
for l in fmt:
@ -214,7 +218,7 @@ def sco_import_generate_excel_sample(
else:
lines = [[]] # empty content, titles only
return sco_excel.excel_simple_table(
titles=titles, titlesStyles=titlesStyles, sheet_name="Etudiants", lines=lines
titles=titles, titles_styles=titlesStyles, sheet_name="Etudiants", lines=lines
)
@ -238,7 +242,7 @@ def students_import_excel(
)
if REQUEST:
if formsemestre_id:
dest = "formsemestre_status?formsemestre_id=%s" % formsemestre_id
dest = "Notes/formsemestre_status?formsemestre_id=%s" % formsemestre_id
else:
dest = scu.NotesURL()
H = [html_sco_header.sco_header(page_title="Import etudiants")]
@ -271,12 +275,11 @@ def scolars_import_excel_file(
exceldata = datafile.read()
if not exceldata:
raise ScoValueError("Ficher excel vide ou invalide")
diag, data = sco_excel.Excel_to_list(exceldata)
diag, data = sco_excel.excel_bytes_to_list(exceldata)
if not data: # probably a bug
raise ScoException("scolars_import_excel_file: empty file !")
formsemestre_to_invalidate = set()
# 1- --- check title line
titles = {}
fmt = sco_import_format()
@ -378,8 +381,8 @@ def scolars_import_excel_file(
# Excel date conversion:
if scu.strlower(titleslist[i]) == "date_naissance":
if val:
if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
val = sco_excel.xldate_as_datetime(float(val))
# if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
val = sco_excel.xldate_as_datetime(val)
# INE
if (
scu.strlower(titleslist[i]) == "code_ine"
@ -625,7 +628,7 @@ def scolars_import_admission(
etuds_by_nomprenom[np] = m
exceldata = datafile.read()
diag2, data = sco_excel.Excel_to_list(exceldata, convert_to_string=False)
diag2, data = sco_excel.excel_bytes_to_list(exceldata)
if not data:
raise ScoException("scolars_import_admission: empty file !")
diag += diag2

View File

@ -1699,7 +1699,7 @@ def check_group_apogee(
return "\n".join(H) + html_sco_header.sco_footer()
@bp.route("/form_students_import_excel")
@bp.route("/form_students_import_excel", methods=["GET", "POST"])
@permission_required(Permission.ScoEtudInscrit)
@scodoc7func(context)
def form_students_import_excel(context, REQUEST, formsemestre_id=None):
@ -1707,7 +1707,12 @@ def form_students_import_excel(context, REQUEST, formsemestre_id=None):
if formsemestre_id:
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
dest_url = (
scu.ScoURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id
# scu.ScoURL() + "/formsemestre_status?formsemestre_id=%s" % formsemestre_id # TODO: Remplacer par for_url ?
url_for(
"notes.formsemestre_status",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
else:
sem = None