forked from ScoDoc/ScoDoc
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:
parent
7372a953fa
commit
432831140c
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user