forked from ScoDoc/ScoDoc
Trombino: amélioration messages d'erreur. Relecture du code.
This commit is contained in:
parent
d93ce9e9be
commit
fb89823c7b
@ -190,20 +190,23 @@ def etud_upload_file_form(etudid):
|
||||
)
|
||||
|
||||
|
||||
def _store_etud_file_to_new_archive(etud_archive_id, data, filename, description=""):
|
||||
def _store_etud_file_to_new_archive(
|
||||
etud_archive_id, data, filename, description=""
|
||||
) -> tuple[bool, str]:
|
||||
"""Store data to new archive."""
|
||||
filesize = len(data)
|
||||
if filesize < 10 or filesize > scu.CONFIG.ETUD_MAX_FILE_SIZE:
|
||||
return 0, "Fichier image de taille invalide ! (%d)" % filesize
|
||||
return False, f"Fichier archive '{filename}' de taille invalide ! ({filesize})"
|
||||
archive_id = EtudsArchive.create_obj_archive(etud_archive_id, description)
|
||||
EtudsArchive.store(archive_id, filename, data)
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def etud_delete_archive(etudid, archive_name, dialog_confirmed=False):
|
||||
"""Delete an archive"""
|
||||
# check permission
|
||||
if not can_edit_etud_archive(current_user):
|
||||
raise AccessDenied("opération non autorisée pour %s" % str(current_user))
|
||||
raise AccessDenied(f"opération non autorisée pour {current_user}")
|
||||
etuds = sco_etud.get_etud_info(filled=True)
|
||||
if not etuds:
|
||||
raise ScoValueError("étudiant inexistant")
|
||||
@ -354,7 +357,9 @@ def etudarchive_import_files(
|
||||
"Importe des fichiers"
|
||||
|
||||
def callback(etud, data, filename):
|
||||
_store_etud_file_to_new_archive(etud["etudid"], data, filename, description)
|
||||
return _store_etud_file_to_new_archive(
|
||||
etud["etudid"], data, filename, description
|
||||
)
|
||||
|
||||
# Utilise la fontion developpée au depart pour les photos
|
||||
(
|
||||
|
@ -872,6 +872,7 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
|
||||
admission = (
|
||||
Admission.query.filter_by(etudid=etudid).first().to_dict(no_nulls=True)
|
||||
)
|
||||
del admission["id"] # pour garder id == etudid dans etud
|
||||
etud.update(admission)
|
||||
#
|
||||
adrs = adresse_list(cnx, {"etudid": etudid})
|
||||
@ -883,6 +884,7 @@ def fill_etuds_info(etuds: list[dict], add_admission=True):
|
||||
adr = adrs[0]
|
||||
if len(adrs) > 1:
|
||||
log("fill_etuds_info: etudid=%s a %d adresses" % (etudid, len(adrs)))
|
||||
adr.pop("id", None)
|
||||
etud.update(adr)
|
||||
format_etud_ident(etud)
|
||||
|
||||
|
@ -76,7 +76,7 @@ UNKNOWN_IMAGE_URL = "get_photo_image?etudid=" # with empty etudid => unknown fa
|
||||
IMAGE_EXT = ".jpg"
|
||||
JPG_QUALITY = 0.92
|
||||
REDUCED_HEIGHT = 90 # pixels
|
||||
MAX_FILE_SIZE = 1024 * 1024 # max allowed size for uploaded image, in bytes
|
||||
MAX_FILE_SIZE = 4 * 1024 * 1024 # max allowed size for uploaded image, in bytes
|
||||
H90 = ".h90" # suffix for reduced size images
|
||||
|
||||
|
||||
@ -244,34 +244,36 @@ def photo_pathname(photo_filename: str, size="orig"):
|
||||
return False
|
||||
|
||||
|
||||
def store_photo(etud, data):
|
||||
def store_photo(etud: dict, data, filename: str) -> tuple[bool, str]:
|
||||
"""Store image for this etud.
|
||||
If there is an existing photo, it is erased and replaced.
|
||||
data is a bytes string with image raw data.
|
||||
|
||||
Update database to store filename.
|
||||
|
||||
Returns (status, msg)
|
||||
Returns (status, err_msg)
|
||||
"""
|
||||
# basic checks
|
||||
filesize = len(data)
|
||||
if filesize < 10 or filesize > MAX_FILE_SIZE:
|
||||
return 0, "Fichier image de taille invalide ! (%d)" % filesize
|
||||
return False, f"Fichier image '{filename}' de taille invalide ! ({filesize})"
|
||||
try:
|
||||
filename = save_image(etud["etudid"], data)
|
||||
except PIL.UnidentifiedImageError:
|
||||
raise ScoGenError(msg="Fichier d'image invalide ou non format non supporté")
|
||||
saved_filename = save_image(etud["etudid"], data)
|
||||
except PIL.UnidentifiedImageError as exc:
|
||||
raise ScoGenError(
|
||||
msg="Fichier d'image '{filename}' invalide ou format non supporté"
|
||||
) from exc
|
||||
# update database:
|
||||
etud["photo_filename"] = filename
|
||||
etud["photo_filename"] = saved_filename
|
||||
etud["foto"] = None
|
||||
|
||||
cnx = ndb.GetDBConnexion()
|
||||
sco_etud.identite_edit_nocheck(cnx, etud)
|
||||
cnx.commit()
|
||||
#
|
||||
logdb(cnx, method="changePhoto", msg=filename, etudid=etud["etudid"])
|
||||
logdb(cnx, method="changePhoto", msg=saved_filename, etudid=etud["etudid"])
|
||||
#
|
||||
return 1, "ok"
|
||||
return True, "ok"
|
||||
|
||||
|
||||
def suppress_photo(etud: Identite) -> None:
|
||||
@ -366,9 +368,9 @@ def copy_portal_photo_to_fs(etud):
|
||||
portal_timeout = sco_preferences.get_preference("portal_timeout")
|
||||
f = None
|
||||
try:
|
||||
log("copy_portal_photo_to_fs: getting %s" % url)
|
||||
log(f"copy_portal_photo_to_fs: getting {url}")
|
||||
r = requests.get(url, timeout=portal_timeout)
|
||||
except:
|
||||
except Exception:
|
||||
# log("download failed: exception:\n%s" % traceback.format_exc())
|
||||
# log("called from:\n" + "".join(traceback.format_stack()))
|
||||
log("copy_portal_photo_to_fs: error.")
|
||||
@ -378,16 +380,16 @@ def copy_portal_photo_to_fs(etud):
|
||||
return None, "%s: erreur chargement de %s" % (etud["nomprenom"], url)
|
||||
data = r.content # image bytes
|
||||
try:
|
||||
status, diag = store_photo(etud, data)
|
||||
except:
|
||||
status = 0
|
||||
diag = "Erreur chargement photo du portail"
|
||||
status, err_msg = store_photo(etud, data, "(inconnue)")
|
||||
except Exception:
|
||||
status = False
|
||||
err_msg = "Erreur chargement photo du portail"
|
||||
log("copy_portal_photo_to_fs: failure (exception in store_photo)!")
|
||||
if status == 1:
|
||||
log("copy_portal_photo_to_fs: copied %s" % url)
|
||||
if status:
|
||||
log(f"copy_portal_photo_to_fs: copied {url}")
|
||||
return (
|
||||
photo_pathname(etud["photo_filename"]),
|
||||
f"{etud['nomprenom']}: photo chargée",
|
||||
)
|
||||
else:
|
||||
return None, "%s: <b>%s</b>" % (etud["nomprenom"], diag)
|
||||
return None, "%s: <b>%s</b>" % (etud["nomprenom"], err_msg)
|
||||
|
@ -33,14 +33,10 @@ from zipfile import ZipFile, BadZipfile
|
||||
from flask.templating import render_template
|
||||
import reportlab
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
from reportlab.platypus.flowables import Flowable
|
||||
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
|
||||
from reportlab.lib.pagesizes import A4, landscape
|
||||
from reportlab.platypus import Paragraph
|
||||
from reportlab.platypus import Table, TableStyle
|
||||
from reportlab.platypus.doctemplate import BaseDocTemplate
|
||||
from reportlab.lib import styles
|
||||
from reportlab.lib.colors import Color
|
||||
from reportlab.lib import colors
|
||||
from PIL import Image as PILImage
|
||||
|
||||
@ -82,7 +78,7 @@ def trombino(
|
||||
|
||||
#
|
||||
if format != "html" and not dialog_confirmed:
|
||||
ok, dialog = check_local_photos_availability(groups_infos, format=format)
|
||||
ok, dialog = check_local_photos_availability(groups_infos, fmt=format)
|
||||
if not ok:
|
||||
return dialog
|
||||
|
||||
@ -96,7 +92,6 @@ def trombino(
|
||||
return sco_trombino_doc.trombino_doc(groups_infos)
|
||||
else:
|
||||
raise Exception("invalid format")
|
||||
# return _trombino_html_header() + trombino_html( group, members) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
def _trombino_html_header():
|
||||
@ -105,7 +100,7 @@ def _trombino_html_header():
|
||||
|
||||
def trombino_html(groups_infos):
|
||||
"HTML snippet for trombino (with title and menu)"
|
||||
menuTrombi = [
|
||||
menu_trombi = [
|
||||
{
|
||||
"title": "Charger des photos...",
|
||||
"endpoint": "scolar.photos_import_files_form",
|
||||
@ -125,19 +120,22 @@ def trombino_html(groups_infos):
|
||||
|
||||
if groups_infos.members:
|
||||
if groups_infos.tous_les_etuds_du_sem:
|
||||
ng = "Tous les étudiants"
|
||||
group_txt = "Tous les étudiants"
|
||||
else:
|
||||
ng = "Groupe %s" % groups_infos.groups_titles
|
||||
group_txt = f"Groupe {groups_infos.groups_titles}"
|
||||
else:
|
||||
ng = "Aucun étudiant inscrit dans ce groupe !"
|
||||
group_txt = "Aucun étudiant inscrit dans ce groupe !"
|
||||
H = [
|
||||
'<table style="padding-top: 10px; padding-bottom: 10px;"><tr><td><span style="font-style: bold; font-size: 150%%; padding-right: 20px;">%s</span></td>'
|
||||
% (ng)
|
||||
f"""<table style="padding-top: 10px; padding-bottom: 10px;">
|
||||
<tr>
|
||||
<td><span
|
||||
style="font-style: bold; font-size: 150%%; padding-right: 20px;"
|
||||
>{group_txt}</span></td>"""
|
||||
]
|
||||
if groups_infos.members:
|
||||
H.append(
|
||||
"<td>"
|
||||
+ htmlutils.make_menu("Gérer les photos", menuTrombi, alone=True)
|
||||
+ htmlutils.make_menu("Gérer les photos", menu_trombi, alone=True)
|
||||
+ "</td>"
|
||||
)
|
||||
H.append("</tr></table>")
|
||||
@ -186,7 +184,7 @@ def trombino_html(groups_infos):
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
def check_local_photos_availability(groups_infos, format=""):
|
||||
def check_local_photos_availability(groups_infos, fmt=""):
|
||||
"""Vérifie que toutes les photos (des groupes indiqués) sont copiées
|
||||
localement dans ScoDoc (seules les photos dont nous disposons localement
|
||||
peuvent être exportées en pdf ou en zip).
|
||||
@ -199,15 +197,15 @@ def check_local_photos_availability(groups_infos, format=""):
|
||||
if not sco_photos.etud_photo_is_local(t):
|
||||
nb_missing += 1
|
||||
if nb_missing > 0:
|
||||
parameters = {"group_ids": groups_infos.group_ids, "format": format}
|
||||
parameters = {"group_ids": groups_infos.group_ids, "format": fmt}
|
||||
return (
|
||||
False,
|
||||
scu.confirm_dialog(
|
||||
"""<p>Attention: %d photos ne sont pas disponibles et ne peuvent pas être exportées.</p><p>Vous pouvez <a class="stdlink" href="%s">exporter seulement les photos existantes</a>"""
|
||||
% (
|
||||
nb_missing,
|
||||
groups_infos.base_url + "&dialog_confirmed=1&format=%s" % format,
|
||||
),
|
||||
f"""<p>Attention: {nb_missing} photos ne sont pas disponibles
|
||||
et ne peuvent pas être exportées.</p>
|
||||
<p>Vous pouvez <a class="stdlink"
|
||||
href="{groups_infos.base_url}&dialog_confirmed=1&format={fmt}"
|
||||
>exporter seulement les photos existantes</a>""",
|
||||
dest_url="trombino",
|
||||
OK="Exporter seulement les photos existantes",
|
||||
cancel_url="groups_view?curtab=tab-photos&"
|
||||
@ -215,28 +213,26 @@ def check_local_photos_availability(groups_infos, format=""):
|
||||
parameters=parameters,
|
||||
),
|
||||
)
|
||||
else:
|
||||
return True, ""
|
||||
return True, ""
|
||||
|
||||
|
||||
def _trombino_zip(groups_infos):
|
||||
"Send photos as zip archive"
|
||||
data = io.BytesIO()
|
||||
Z = ZipFile(data, "w")
|
||||
# assume we have the photos (or the user acknowledged the fact)
|
||||
# Archive originals (not reduced) images, in JPEG
|
||||
for t in groups_infos.members:
|
||||
im_path = sco_photos.photo_pathname(t["photo_filename"], size="orig")
|
||||
if not im_path:
|
||||
continue
|
||||
img = open(im_path, "rb").read()
|
||||
code_nip = t["code_nip"]
|
||||
if code_nip:
|
||||
filename = code_nip + ".jpg"
|
||||
else:
|
||||
filename = f'{t["nom"]}_{t["prenom"]}_{t["etudid"]}.jpg'
|
||||
Z.writestr(filename, img)
|
||||
Z.close()
|
||||
with ZipFile(data, "w") as zip_file:
|
||||
# assume we have the photos (or the user acknowledged the fact)
|
||||
# Archive originals (not reduced) images, in JPEG
|
||||
for t in groups_infos.members:
|
||||
im_path = sco_photos.photo_pathname(t["photo_filename"], size="orig")
|
||||
if not im_path:
|
||||
continue
|
||||
img = open(im_path, "rb").read()
|
||||
code_nip = t["code_nip"]
|
||||
if code_nip:
|
||||
filename = code_nip + ".jpg"
|
||||
else:
|
||||
filename = f'{t["nom"]}_{t["prenom"]}_{t["etudid"]}.jpg'
|
||||
zip_file.writestr(filename, img)
|
||||
size = data.tell()
|
||||
log(f"trombino_zip: {size} bytes")
|
||||
data.seek(0)
|
||||
@ -258,19 +254,22 @@ def trombino_copy_photos(group_ids=[], dialog_confirmed=False):
|
||||
header = html_sco_header.sco_header(page_title="Chargement des photos")
|
||||
footer = html_sco_header.sco_footer()
|
||||
if not portal_url:
|
||||
return (
|
||||
header
|
||||
+ '<p>portail non configuré</p><p><a href="%s">Retour au trombinoscope</a></p>'
|
||||
% back_url
|
||||
+ footer
|
||||
)
|
||||
return f"""{ header }
|
||||
<p>portail non configuré</p>
|
||||
<p><a href="{back_url}" class="stdlink">Retour au trombinoscope</a></p>
|
||||
{ footer }
|
||||
"""
|
||||
if not dialog_confirmed:
|
||||
return scu.confirm_dialog(
|
||||
"""<h2>Copier les photos du portail vers ScoDoc ?</h2>
|
||||
<p>Les photos du groupe %s présentes dans ScoDoc seront remplacées par celles du portail (si elles existent).</p>
|
||||
<p>(les photos sont normalement automatiquement copiées lors de leur première utilisation, l'usage de cette fonction n'est nécessaire que si les photos du portail ont été modifiées)</p>
|
||||
"""
|
||||
% (groups_infos.groups_titles),
|
||||
f"""<h2>Copier les photos du portail vers ScoDoc ?</h2>
|
||||
<p>Les photos du groupe {groups_infos.groups_titles} présentes
|
||||
dans ScoDoc seront remplacées par celles du portail (si elles existent).
|
||||
</p>
|
||||
<p>(les photos sont normalement automatiquement copiées
|
||||
lors de leur première utilisation, l'usage de cette fonction
|
||||
n'est nécessaire que si les photos du portail ont été modifiées)
|
||||
</p>
|
||||
""",
|
||||
dest_url="",
|
||||
cancel_url=back_url,
|
||||
parameters={"group_ids": group_ids},
|
||||
@ -284,16 +283,16 @@ def trombino_copy_photos(group_ids=[], dialog_confirmed=False):
|
||||
if path:
|
||||
nok += 1
|
||||
|
||||
msg.append("<b>%d photos correctement chargées</b>" % nok)
|
||||
msg.append(f"<b>{nok} photos correctement chargées</b>")
|
||||
|
||||
return (
|
||||
header
|
||||
+ "<h2>Chargement des photos depuis le portail</h2><ul><li>"
|
||||
+ "</li><li>".join(msg)
|
||||
+ "</li></ul>"
|
||||
+ '<p><a href="%s">retour au trombinoscope</a>' % back_url
|
||||
+ footer
|
||||
)
|
||||
return f"""{ header }
|
||||
<h2>Chargement des photos depuis le portail</h2>
|
||||
<ul><li>
|
||||
{ '</li><li>'.join(msg) }
|
||||
</li></ul>
|
||||
<p><a href="{back_url}">retour au trombinoscope</a>
|
||||
{ footer }
|
||||
"""
|
||||
|
||||
|
||||
def _get_etud_platypus_image(t, image_width=2 * cm):
|
||||
@ -323,38 +322,38 @@ def _get_etud_platypus_image(t, image_width=2 * cm):
|
||||
def _trombino_pdf(groups_infos):
|
||||
"Send photos as pdf page"
|
||||
# Generate PDF page
|
||||
filename = "trombino_%s" % groups_infos.groups_filename + ".pdf"
|
||||
filename = f"trombino_{groups_infos.groups_filename}.pdf"
|
||||
sem = groups_infos.formsemestre # suppose 1 seul semestre
|
||||
|
||||
PHOTOWIDTH = 3 * cm
|
||||
COLWIDTH = 3.6 * cm
|
||||
PHOTO_WIDTH = 3 * cm
|
||||
COL_WIDTH = 3.6 * cm
|
||||
N_PER_ROW = 5 # XXX should be in ScoDoc preferences
|
||||
|
||||
StyleSheet = styles.getSampleStyleSheet()
|
||||
style_sheet = styles.getSampleStyleSheet()
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
objects = [
|
||||
Paragraph(
|
||||
SU("Trombinoscope " + sem["titreannee"] + " " + groups_infos.groups_titles),
|
||||
StyleSheet["Heading3"],
|
||||
style_sheet["Heading3"],
|
||||
)
|
||||
]
|
||||
L = []
|
||||
n = 0
|
||||
currow = []
|
||||
log("_trombino_pdf %d elements" % len(groups_infos.members))
|
||||
log(f"_trombino_pdf {len(groups_infos.members)} elements")
|
||||
for t in groups_infos.members:
|
||||
img = _get_etud_platypus_image(t, image_width=PHOTOWIDTH)
|
||||
img = _get_etud_platypus_image(t, image_width=PHOTO_WIDTH)
|
||||
elem = Table(
|
||||
[
|
||||
[img],
|
||||
[
|
||||
Paragraph(
|
||||
SU(sco_etud.format_nomprenom(t)),
|
||||
StyleSheet["Normal"],
|
||||
style_sheet["Normal"],
|
||||
)
|
||||
],
|
||||
],
|
||||
colWidths=[PHOTOWIDTH],
|
||||
colWidths=[PHOTO_WIDTH],
|
||||
)
|
||||
currow.append(elem)
|
||||
if n == (N_PER_ROW - 1):
|
||||
@ -365,11 +364,11 @@ def _trombino_pdf(groups_infos):
|
||||
currow += [" "] * (N_PER_ROW - len(currow))
|
||||
L.append(currow)
|
||||
if not L:
|
||||
table = Paragraph(SU("Aucune photo à exporter !"), StyleSheet["Normal"])
|
||||
table = Paragraph(SU("Aucune photo à exporter !"), style_sheet["Normal"])
|
||||
else:
|
||||
table = Table(
|
||||
L,
|
||||
colWidths=[COLWIDTH] * N_PER_ROW,
|
||||
colWidths=[COL_WIDTH] * N_PER_ROW,
|
||||
style=TableStyle(
|
||||
[
|
||||
# ('RIGHTPADDING', (0,0), (-1,-1), -5*mm),
|
||||
@ -400,39 +399,36 @@ def _trombino_pdf(groups_infos):
|
||||
# --------------------- Sur une idée de l'IUT d'Orléans:
|
||||
def _listeappel_photos_pdf(groups_infos):
|
||||
"Doc pdf pour liste d'appel avec photos"
|
||||
filename = "trombino_%s" % groups_infos.groups_filename + ".pdf"
|
||||
filename = f"trombino_{groups_infos.groups_filename}.pdf"
|
||||
sem = groups_infos.formsemestre # suppose 1 seul semestre
|
||||
|
||||
PHOTOWIDTH = 2 * cm
|
||||
PHOTO_WIDTH = 2 * cm
|
||||
# COLWIDTH = 3.6 * cm
|
||||
# ROWS_PER_PAGE = 26 # XXX should be in ScoDoc preferences
|
||||
|
||||
StyleSheet = styles.getSampleStyleSheet()
|
||||
style_sheet = styles.getSampleStyleSheet()
|
||||
report = io.BytesIO() # in-memory document, no disk file
|
||||
objects = [
|
||||
Paragraph(
|
||||
SU(
|
||||
sem["titreannee"]
|
||||
+ " "
|
||||
+ groups_infos.groups_titles
|
||||
+ " (%d)" % len(groups_infos.members)
|
||||
f"""{sem["titreannee"]} {groups_infos.groups_titles} ({len(groups_infos.members)})"""
|
||||
),
|
||||
StyleSheet["Heading3"],
|
||||
style_sheet["Heading3"],
|
||||
)
|
||||
]
|
||||
L = []
|
||||
n = 0
|
||||
currow = []
|
||||
log("_listeappel_photos_pdf %d elements" % len(groups_infos.members))
|
||||
log(f"_listeappel_photos_pdf {len(groups_infos.members)} elements")
|
||||
n = len(groups_infos.members)
|
||||
# npages = n / 2*ROWS_PER_PAGE + 1 # nb de pages papier
|
||||
# for page in range(npages):
|
||||
for i in range(n): # page*2*ROWS_PER_PAGE, (page+1)*2*ROWS_PER_PAGE):
|
||||
t = groups_infos.members[i]
|
||||
img = _get_etud_platypus_image(t, image_width=PHOTOWIDTH)
|
||||
img = _get_etud_platypus_image(t, image_width=PHOTO_WIDTH)
|
||||
txt = Paragraph(
|
||||
SU(sco_etud.format_nomprenom(t)),
|
||||
StyleSheet["Normal"],
|
||||
style_sheet["Normal"],
|
||||
)
|
||||
if currow:
|
||||
currow += [""]
|
||||
@ -444,7 +440,7 @@ def _listeappel_photos_pdf(groups_infos):
|
||||
currow += [" "] * 3
|
||||
L.append(currow)
|
||||
if not L:
|
||||
table = Paragraph(SU("Aucune photo à exporter !"), StyleSheet["Normal"])
|
||||
table = Paragraph(SU("Aucune photo à exporter !"), style_sheet["Normal"])
|
||||
else:
|
||||
table = Table(
|
||||
L,
|
||||
@ -544,7 +540,7 @@ def photos_import_files_form(group_ids=()):
|
||||
else:
|
||||
|
||||
def callback(etud, data, filename):
|
||||
sco_photos.store_photo(etud, data)
|
||||
return sco_photos.store_photo(etud, data, filename)
|
||||
|
||||
(
|
||||
ignored_zipfiles,
|
||||
@ -555,6 +551,7 @@ def photos_import_files_form(group_ids=()):
|
||||
zipfile=tf[2]["zipfile"],
|
||||
callback=callback,
|
||||
filename_title="fichier_photo",
|
||||
back_url=back_url,
|
||||
)
|
||||
return render_template(
|
||||
"scolar/photos_import_files.html",
|
||||
@ -571,11 +568,22 @@ def photos_import_files_form(group_ids=()):
|
||||
)
|
||||
|
||||
|
||||
def _norm_zip_filename(fn, lowercase=True):
|
||||
"normalisation used to match filenames"
|
||||
fn = fn.replace("\\", "/") # not sure if this is necessary ?
|
||||
fn = fn.strip()
|
||||
if lowercase:
|
||||
fn = fn.lower()
|
||||
fn = fn.split("/")[-1] # use only last component, not directories
|
||||
return fn
|
||||
|
||||
|
||||
def zip_excel_import_files(
|
||||
xlsfile=None,
|
||||
zipfile=None,
|
||||
callback=None,
|
||||
filename_title="", # doit obligatoirement etre specifié
|
||||
back_url=None,
|
||||
):
|
||||
"""Importation de fichiers à partir d'un excel et d'un zip
|
||||
La fonction
|
||||
@ -595,26 +603,22 @@ def zip_excel_import_files(
|
||||
try:
|
||||
etudid_idx = titles.index("etudid")
|
||||
filename_idx = titles.index(filename_title)
|
||||
except:
|
||||
except Exception as exc:
|
||||
raise ScoValueError(
|
||||
"Fichier excel incorrect (il faut une colonne etudid et une colonne %s) !"
|
||||
% filename_title
|
||||
)
|
||||
f"Fichier excel incorrect (il faut une colonne etudid et une colonne {filename_title}) !"
|
||||
) from exc
|
||||
|
||||
def normfilename(fn, lowercase=True):
|
||||
"normalisation used to match filenames"
|
||||
fn = fn.replace("\\", "/") # not sure if this is necessary ?
|
||||
fn = fn.strip()
|
||||
if lowercase:
|
||||
fn = fn.lower()
|
||||
fn = fn.split("/")[-1] # use only last component, not directories
|
||||
return fn
|
||||
|
||||
filename_to_etud = {} # filename : etudid
|
||||
for l in data[1:]:
|
||||
filename_to_etudid = {} # filename : etudid
|
||||
for line_num, l in enumerate(data[1:]):
|
||||
filename = l[filename_idx].strip()
|
||||
if filename:
|
||||
filename_to_etud[normfilename(filename)] = l[etudid_idx]
|
||||
try:
|
||||
filename_to_etudid[_norm_zip_filename(filename)] = int(l[etudid_idx])
|
||||
except ValueError as exc:
|
||||
raise ScoValueError(
|
||||
f"etudid invalide ({l[etudid_idx]}) sur ligne {line_num+1} !",
|
||||
dest_url=back_url,
|
||||
) from exc
|
||||
|
||||
# 2- Ouvre le zip et
|
||||
try:
|
||||
@ -625,35 +629,43 @@ def zip_excel_import_files(
|
||||
stored_etud_filename = [] # [ (etud, filename) ]
|
||||
for name in z.namelist():
|
||||
if len(name) > 4 and name[-1] != "/" and "." in name:
|
||||
data = z.read(name)
|
||||
try:
|
||||
data = z.read(name)
|
||||
except BadZipfile as exc:
|
||||
raise ScoValueError(
|
||||
f"Fichier Zip incorrect: erreur sur {name}", dest_url=back_url
|
||||
) from exc
|
||||
# match zip filename with name given in excel
|
||||
normname = normfilename(name)
|
||||
if normname in filename_to_etud:
|
||||
etudid = filename_to_etud[normname]
|
||||
normname = _norm_zip_filename(name)
|
||||
if normname in filename_to_etudid:
|
||||
etudid = filename_to_etudid[normname]
|
||||
# ok, store photo
|
||||
try:
|
||||
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
|
||||
del filename_to_etud[normname]
|
||||
except:
|
||||
raise ScoValueError("ID étudiant invalide: %s" % etudid)
|
||||
del filename_to_etudid[normname]
|
||||
except Exception as exc:
|
||||
raise ScoValueError(
|
||||
f"ID étudiant invalide: {etudid}", dest_url=back_url
|
||||
) from exc
|
||||
|
||||
callback(
|
||||
status, err_msg = callback(
|
||||
etud,
|
||||
data,
|
||||
normfilename(name, lowercase=False),
|
||||
_norm_zip_filename(name, lowercase=False),
|
||||
)
|
||||
|
||||
if not status:
|
||||
raise ScoValueError(f"Erreur: {err_msg}", dest_url=back_url)
|
||||
stored_etud_filename.append((etud, name))
|
||||
else:
|
||||
log("zip: zip name %s not in excel !" % name)
|
||||
log(f"zip: zip name {name} not in excel !")
|
||||
ignored_zipfiles.append(name)
|
||||
else:
|
||||
if name[-1] != "/":
|
||||
ignored_zipfiles.append(name)
|
||||
log("zip: ignoring %s" % name)
|
||||
if filename_to_etud:
|
||||
log(f"zip: ignoring {name}")
|
||||
if filename_to_etudid:
|
||||
# lignes excel non traitées
|
||||
unmatched_files = list(filename_to_etud.keys())
|
||||
unmatched_files = list(filename_to_etudid.keys())
|
||||
else:
|
||||
unmatched_files = []
|
||||
return ignored_zipfiles, unmatched_files, stored_etud_filename
|
||||
|
@ -1020,11 +1020,13 @@ def formChangePhoto(etudid=None):
|
||||
return flask.redirect(dest_url)
|
||||
else:
|
||||
data = tf[2]["photofile"].read()
|
||||
status, diag = sco_photos.store_photo(etud, data)
|
||||
if status != 0:
|
||||
status, err_msg = sco_photos.store_photo(
|
||||
etud, data, tf[2]["photofile"].filename
|
||||
)
|
||||
if status:
|
||||
return flask.redirect(dest_url)
|
||||
else:
|
||||
H.append('<p class="warning">Erreur:' + diag + "</p>")
|
||||
H.append(f"""<p class="warning">Erreur: {err_msg}</p>""")
|
||||
return "\n".join(H) + html_sco_header.sco_footer()
|
||||
|
||||
|
||||
|
@ -473,7 +473,6 @@ def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
|
||||
"""Import des photos d'étudiants à partir d'une liste excel et d'un zip avec les images."""
|
||||
import app as mapp
|
||||
from app.scodoc import sco_trombino, sco_photos
|
||||
from app.scodoc import notesdb as ndb
|
||||
from flask_login import login_user
|
||||
from app.auth.models import get_super_admin
|
||||
|
||||
@ -488,7 +487,7 @@ def photos_import_files(formsemestre_id: int, xlsfile: str, zipfile: str):
|
||||
login_user(admin_user)
|
||||
|
||||
def callback(etud, data, filename):
|
||||
sco_photos.store_photo(etud, data)
|
||||
return sco_photos.store_photo(etud, data, filename)
|
||||
|
||||
(
|
||||
ignored_zipfiles,
|
||||
|
Loading…
Reference in New Issue
Block a user