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)
|
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):
|
def identite_edit(cnx, args, context=None, REQUEST=None):
|
||||||
"""Modifie l'identite d'un étudiant.
|
"""Modifie l'identite d'un étudiant.
|
||||||
Si context et notification et difference, envoie message notification.
|
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 unique etudid, then create"
|
||||||
_check_duplicate_code(cnx, args, "code_nip", context, edit=False, REQUEST=REQUEST)
|
_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_duplicate_code(cnx, args, "code_ine", context, edit=False, REQUEST=REQUEST)
|
||||||
|
_check_civilite(args)
|
||||||
|
|
||||||
if "etudid" in args:
|
if "etudid" in args:
|
||||||
etudid = args["etudid"]
|
etudid = args["etudid"]
|
||||||
@ -755,7 +761,6 @@ _etud_annotationsEditor = ndb.EditableTable(
|
|||||||
output_formators={"comment": safehtml.html_to_safe_html, "date": ndb.DateISOtoDMY},
|
output_formators={"comment": safehtml.html_to_safe_html, "date": ndb.DateISOtoDMY},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
etud_annotations_create = _etud_annotationsEditor.create
|
etud_annotations_create = _etud_annotationsEditor.create
|
||||||
etud_annotations_delete = _etud_annotationsEditor.delete
|
etud_annotations_delete = _etud_annotationsEditor.delete
|
||||||
etud_annotations_list = _etud_annotationsEditor.list
|
etud_annotations_list = _etud_annotationsEditor.list
|
||||||
|
@ -34,6 +34,7 @@ import time
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
import openpyxl.utils.datetime
|
||||||
from openpyxl import Workbook, load_workbook
|
from openpyxl import Workbook, load_workbook
|
||||||
from openpyxl.cell import WriteOnlyCell
|
from openpyxl.cell import WriteOnlyCell
|
||||||
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
|
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.
|
# 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"
|
# <br>Note: 1904-01-01 is not regarded as a valid date in the datemode 1 system; its "serial number"
|
||||||
# is zero.
|
# 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):
|
def xldate_as_datetime(xldate, datemode=0):
|
||||||
if datemode not in (0, 1):
|
return openpyxl.utils.datetime.from_ISO8601(xldate)
|
||||||
raise ValueError("invalid mode %s" % datemode)
|
# if datemode not in (0, 1):
|
||||||
if xldate == 0.00:
|
# raise ValueError("invalid mode %s" % datemode)
|
||||||
return datetime.time(0, 0, 0)
|
# if xldate == 0.00:
|
||||||
if xldate < 0.00:
|
# return datetime.time(0, 0, 0)
|
||||||
raise ValueError("invalid date code %s" % xldate)
|
# if xldate < 0.00:
|
||||||
xldays = int(xldate)
|
# raise ValueError("invalid date code %s" % xldate)
|
||||||
frac = xldate - xldays
|
# xldays = int(xldate)
|
||||||
seconds = int(round(frac * 86400.0))
|
# frac = xldate - xldays
|
||||||
assert 0 <= seconds <= 86400
|
# seconds = int(round(frac * 86400.0))
|
||||||
if seconds == 86400:
|
# assert 0 <= seconds <= 86400
|
||||||
seconds = 0
|
# if seconds == 86400:
|
||||||
xldays += 1
|
# seconds = 0
|
||||||
if xldays >= _XLDAYS_TOO_LARGE[datemode]:
|
# xldays += 1
|
||||||
raise ValueError("date too large %s" % xldate)
|
# if xldays >= _XLDAYS_TOO_LARGE[datemode]:
|
||||||
|
# raise ValueError("date too large %s" % xldate)
|
||||||
if xldays == 0:
|
#
|
||||||
# second = seconds % 60; minutes = seconds // 60
|
# if xldays == 0:
|
||||||
minutes, second = divmod(seconds, 60)
|
# # second = seconds % 60; minutes = seconds // 60
|
||||||
# minute = minutes % 60; hour = minutes // 60
|
# minutes, second = divmod(seconds, 60)
|
||||||
hour, minute = divmod(minutes, 60)
|
# # minute = minutes % 60; hour = minutes // 60
|
||||||
return datetime.time(hour, minute, second)
|
# hour, minute = divmod(minutes, 60)
|
||||||
|
# return datetime.time(hour, minute, second)
|
||||||
if xldays < 61 and datemode == 0:
|
#
|
||||||
raise ValueError("ambiguous date %s" % xldate)
|
# if xldays < 61 and datemode == 0:
|
||||||
|
# raise ValueError("ambiguous date %s" % xldate)
|
||||||
return datetime.datetime.fromordinal(
|
#
|
||||||
xldays + 693594 + 1462 * datemode
|
# return datetime.datetime.fromordinal(
|
||||||
) + datetime.timedelta(seconds=seconds)
|
# xldays + 693594 + 1462 * datemode
|
||||||
|
# ) + datetime.timedelta(seconds=seconds)
|
||||||
|
|
||||||
|
|
||||||
class ScoExcelBook:
|
class ScoExcelBook:
|
||||||
|
@ -32,10 +32,14 @@ import collections
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
import flask
|
||||||
|
|
||||||
import app.scodoc.sco_utils as scu
|
import app.scodoc.sco_utils as scu
|
||||||
import app.scodoc.notesdb as ndb
|
import app.scodoc.notesdb as ndb
|
||||||
from app.scodoc.notes_log import log
|
from app.scodoc.notes_log import log
|
||||||
|
from app.scodoc.sco_excel import COLORS
|
||||||
from app.scodoc.sco_formsemestre_inscriptions import (
|
from app.scodoc.sco_formsemestre_inscriptions import (
|
||||||
do_formsemestre_inscription_with_modules,
|
do_formsemestre_inscription_with_modules,
|
||||||
)
|
)
|
||||||
@ -164,7 +168,7 @@ def sco_import_generate_excel_sample(
|
|||||||
If group_ids, liste les etudiants de ces groupes
|
If group_ids, liste les etudiants de ces groupes
|
||||||
"""
|
"""
|
||||||
style = sco_excel.excel_make_style(bold=True)
|
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 = []
|
titles = []
|
||||||
titlesStyles = []
|
titlesStyles = []
|
||||||
for l in fmt:
|
for l in fmt:
|
||||||
@ -214,7 +218,7 @@ def sco_import_generate_excel_sample(
|
|||||||
else:
|
else:
|
||||||
lines = [[]] # empty content, titles only
|
lines = [[]] # empty content, titles only
|
||||||
return sco_excel.excel_simple_table(
|
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 REQUEST:
|
||||||
if formsemestre_id:
|
if formsemestre_id:
|
||||||
dest = "formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
dest = "Notes/formsemestre_status?formsemestre_id=%s" % formsemestre_id
|
||||||
else:
|
else:
|
||||||
dest = scu.NotesURL()
|
dest = scu.NotesURL()
|
||||||
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
H = [html_sco_header.sco_header(page_title="Import etudiants")]
|
||||||
@ -271,12 +275,11 @@ def scolars_import_excel_file(
|
|||||||
exceldata = datafile.read()
|
exceldata = datafile.read()
|
||||||
if not exceldata:
|
if not exceldata:
|
||||||
raise ScoValueError("Ficher excel vide ou invalide")
|
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
|
if not data: # probably a bug
|
||||||
raise ScoException("scolars_import_excel_file: empty file !")
|
raise ScoException("scolars_import_excel_file: empty file !")
|
||||||
|
|
||||||
formsemestre_to_invalidate = set()
|
formsemestre_to_invalidate = set()
|
||||||
|
|
||||||
# 1- --- check title line
|
# 1- --- check title line
|
||||||
titles = {}
|
titles = {}
|
||||||
fmt = sco_import_format()
|
fmt = sco_import_format()
|
||||||
@ -378,8 +381,8 @@ def scolars_import_excel_file(
|
|||||||
# Excel date conversion:
|
# Excel date conversion:
|
||||||
if scu.strlower(titleslist[i]) == "date_naissance":
|
if scu.strlower(titleslist[i]) == "date_naissance":
|
||||||
if val:
|
if val:
|
||||||
if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
|
# if re.match(r"^[0-9]*\.?[0-9]*$", str(val)):
|
||||||
val = sco_excel.xldate_as_datetime(float(val))
|
val = sco_excel.xldate_as_datetime(val)
|
||||||
# INE
|
# INE
|
||||||
if (
|
if (
|
||||||
scu.strlower(titleslist[i]) == "code_ine"
|
scu.strlower(titleslist[i]) == "code_ine"
|
||||||
@ -625,7 +628,7 @@ def scolars_import_admission(
|
|||||||
etuds_by_nomprenom[np] = m
|
etuds_by_nomprenom[np] = m
|
||||||
|
|
||||||
exceldata = datafile.read()
|
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:
|
if not data:
|
||||||
raise ScoException("scolars_import_admission: empty file !")
|
raise ScoException("scolars_import_admission: empty file !")
|
||||||
diag += diag2
|
diag += diag2
|
||||||
|
@ -1699,7 +1699,7 @@ def check_group_apogee(
|
|||||||
return "\n".join(H) + html_sco_header.sco_footer()
|
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)
|
@permission_required(Permission.ScoEtudInscrit)
|
||||||
@scodoc7func(context)
|
@scodoc7func(context)
|
||||||
def form_students_import_excel(context, REQUEST, formsemestre_id=None):
|
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:
|
if formsemestre_id:
|
||||||
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
|
sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
|
||||||
dest_url = (
|
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:
|
else:
|
||||||
sem = None
|
sem = None
|
||||||
|
Loading…
Reference in New Issue
Block a user