2020-09-26 16:19:37 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 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
#
##############################################################################
""" Photos: trombinoscopes
"""
2021-07-26 17:11:00 +02:00
import io
2020-09-26 16:19:37 +02:00
from zipfile import ZipFile , BadZipfile
2021-10-20 16:47:41 +02:00
from flask . templating import render_template
2021-02-05 22:16:30 +01:00
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 . lib import styles
from reportlab . lib . colors import Color
from reportlab . lib import colors
from PIL import Image as PILImage
2020-09-26 16:19:37 +02:00
2021-08-01 10:16:16 +02:00
import flask
2021-09-18 10:10:02 +02:00
from flask import url_for , g , send_file , request
2021-07-11 17:37:12 +02:00
2021-08-29 19:57:32 +02:00
from app import log
2021-06-19 23:21:37 +02:00
import app . scodoc . sco_utils as scu
from app . scodoc . TrivialFormulator import TrivialFormulator
from app . scodoc . sco_exceptions import ScoValueError
from app . scodoc . sco_pdf import SU
from app . scodoc import html_sco_header
from app . scodoc import htmlutils
2021-06-21 10:17:16 +02:00
from app . scodoc import sco_import_etuds
2022-04-08 13:01:47 +02:00
from app . scodoc import sco_etud
2021-06-19 23:21:37 +02:00
from app . scodoc import sco_excel
from app . scodoc import sco_groups_view
from app . scodoc import sco_pdf
from app . scodoc import sco_photos
from app . scodoc import sco_portal_apogee
from app . scodoc import sco_preferences
2022-04-08 13:01:47 +02:00
from app . scodoc import sco_trombino_doc
2020-09-26 16:19:37 +02:00
def trombino (
2022-04-08 13:01:47 +02:00
group_ids = ( ) , # liste des groupes à afficher
2020-09-26 16:19:37 +02:00
formsemestre_id = None , # utilisé si pas de groupes selectionné
etat = None ,
format = " html " ,
dialog_confirmed = False ,
) :
""" Trombinoscope """
if not etat :
etat = None # may be passed as ''
# Informations sur les groupes à afficher:
groups_infos = sco_groups_view . DisplayedGroupsInfos (
2021-09-08 00:34:45 +02:00
group_ids , formsemestre_id = formsemestre_id , etat = etat
2020-09-26 16:19:37 +02:00
)
#
if format != " html " and not dialog_confirmed :
2021-09-27 10:20:10 +02:00
ok , dialog = check_local_photos_availability ( groups_infos , format = format )
2020-09-26 16:19:37 +02:00
if not ok :
return dialog
if format == " zip " :
2021-08-31 20:18:50 +02:00
return _trombino_zip ( groups_infos )
2020-09-26 16:19:37 +02:00
elif format == " pdf " :
2021-09-27 10:20:10 +02:00
return _trombino_pdf ( groups_infos )
2020-09-26 16:19:37 +02:00
elif format == " pdflist " :
2021-09-27 10:20:10 +02:00
return _listeappel_photos_pdf ( groups_infos )
2022-04-08 13:01:47 +02:00
elif format == " doc " :
return sco_trombino_doc . trombino_doc ( groups_infos )
2020-09-26 16:19:37 +02:00
else :
raise Exception ( " invalid format " )
2021-09-27 10:20:10 +02:00
# return _trombino_html_header() + trombino_html( group, members) + html_sco_header.sco_footer()
2020-09-26 16:19:37 +02:00
2021-08-21 00:24:51 +02:00
def _trombino_html_header ( ) :
2021-07-29 16:31:15 +02:00
return html_sco_header . sco_header ( javascripts = [ " js/trombino.js " ] )
2020-09-26 16:19:37 +02:00
2021-09-24 16:32:49 +02:00
def trombino_html ( groups_infos ) :
2020-09-26 16:19:37 +02:00
" HTML snippet for trombino (with title and menu) "
menuTrombi = [
2021-06-14 18:08:52 +02:00
{
" title " : " Charger des photos... " ,
" endpoint " : " scolar.photos_import_files_form " ,
" args " : { " group_ids " : groups_infos . group_ids } ,
} ,
2020-09-26 16:19:37 +02:00
{
" title " : " Obtenir archive Zip des photos " ,
2021-06-14 18:08:52 +02:00
" endpoint " : " scolar.trombino " ,
" args " : { " group_ids " : groups_infos . group_ids , " format " : " zip " } ,
2020-09-26 16:19:37 +02:00
} ,
{
" title " : " Recopier les photos depuis le portail " ,
2021-06-14 18:08:52 +02:00
" endpoint " : " scolar.trombino_copy_photos " ,
" args " : { " group_ids " : groups_infos . group_ids } ,
2020-09-26 16:19:37 +02:00
} ,
]
if groups_infos . members :
if groups_infos . tous_les_etuds_du_sem :
ng = " Tous les étudiants "
else :
ng = " Groupe %s " % groups_infos . groups_titles
else :
ng = " 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 )
]
if groups_infos . members :
H . append (
2021-01-17 22:31:28 +01:00
" <td> "
+ htmlutils . make_menu ( " Gérer les photos " , menuTrombi , alone = True )
+ " </td> "
2020-09-26 16:19:37 +02:00
)
H . append ( " </tr></table> " )
H . append ( " <div> " )
i = 0
for t in groups_infos . members :
H . append (
' <span class= " trombi_box " ><span class= " trombi-photo " id= " trombi- %s " > '
% t [ " etudid " ]
)
2021-08-21 00:24:51 +02:00
if sco_photos . etud_photo_is_local ( t , size = " small " ) :
2021-09-24 12:10:53 +02:00
foto = sco_photos . etud_photo_html ( t , title = " " )
2020-09-26 16:19:37 +02:00
else : # la photo n'est pas immédiatement dispo
foto = (
' <span class= " unloaded_img " id= " %s " ><img border= " 0 " height= " 90 " alt= " en cours " src= " /ScoDoc/static/icons/loading.jpg " /></span> '
% t [ " etudid " ]
)
2021-07-11 17:37:12 +02:00
H . append (
' <a href= " %s " > %s </a> '
% (
url_for (
" scolar.ficheEtud " , scodoc_dept = g . scodoc_dept , etudid = t [ " etudid " ]
) ,
foto ,
)
)
2020-09-26 16:19:37 +02:00
H . append ( " </span> " )
H . append (
' <span class= " trombi_legend " ><span class= " trombi_prenom " > '
2021-06-19 23:21:37 +02:00
+ sco_etud . format_prenom ( t [ " prenom " ] )
2020-09-26 16:19:37 +02:00
+ ' </span><span class= " trombi_nom " > '
2021-06-19 23:21:37 +02:00
+ sco_etud . format_nom ( t [ " nom " ] )
2021-11-06 17:24:11 +01:00
+ ( " <i>(dem.)</i> " if t [ " etat " ] == " D " else " " )
2020-09-26 16:19:37 +02:00
)
H . append ( " </span></span></span> " )
i + = 1
H . append ( " </div> " )
H . append (
2022-04-08 13:01:47 +02:00
f """ <div style= " margin-bottom:15px; " >
< a class = " stdlink " href = " { url_for( ' scolar.trombino ' , scodoc_dept=g.scodoc_dept,
format = ' pdf ' , group_ids = groups_infos . group_ids ) } " >Version PDF</a>
& nbsp ; & nbsp ;
< a class = " stdlink " href = " { url_for( ' scolar.trombino ' , scodoc_dept=g.scodoc_dept,
format = ' doc ' , group_ids = groups_infos . group_ids ) } " >Version doc</a>
< / div > """
2020-09-26 16:19:37 +02:00
)
return " \n " . join ( H )
2021-09-27 10:20:10 +02:00
def check_local_photos_availability ( groups_infos , format = " " ) :
2021-12-20 22:53:09 +01:00
""" 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 ) .
Si toutes ne sont pas dispo , retourne un dialogue d ' avertissement
pour l ' utilisateur.
2020-09-26 16:19:37 +02:00
"""
nb_missing = 0
for t in groups_infos . members :
2021-08-21 00:24:51 +02:00
_ = sco_photos . etud_photo_url ( t ) # -> copy distant files if needed
if not sco_photos . etud_photo_is_local ( t ) :
2020-09-26 16:19:37 +02:00
nb_missing + = 1
if nb_missing > 0 :
parameters = { " group_ids " : groups_infos . group_ids , " format " : format }
return (
False ,
2021-06-02 22:40:34 +02:00
scu . confirm_dialog (
2020-09-26 16:19:37 +02:00
""" <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 ,
2021-06-02 22:40:34 +02:00
groups_infos . base_url + " &dialog_confirmed=1&format= %s " % format ,
2020-09-26 16:19:37 +02:00
) ,
dest_url = " trombino " ,
OK = " Exporter seulement les photos existantes " ,
2021-05-11 11:48:32 +02:00
cancel_url = " groups_view?curtab=tab-photos& "
2020-09-26 16:19:37 +02:00
+ groups_infos . groups_query_args ,
parameters = parameters ,
) ,
)
else :
return True , " "
2021-08-31 20:18:50 +02:00
def _trombino_zip ( groups_infos ) :
2020-09-26 16:19:37 +02:00
" Send photos as zip archive "
2021-07-26 17:11:00 +02:00
data = io . BytesIO ( )
2020-09-26 16:19:37 +02:00
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 :
2021-12-20 22:53:09 +01:00
im_path = sco_photos . photo_pathname ( t [ " photo_filename " ] , size = " orig " )
2020-09-26 16:19:37 +02:00
if not im_path :
continue
2021-07-26 17:11:00 +02:00
img = open ( im_path , " rb " ) . read ( )
2020-09-26 16:19:37 +02:00
code_nip = t [ " code_nip " ]
if code_nip :
filename = code_nip + " .jpg "
else :
2021-12-13 11:10:53 +01:00
filename = f ' { t [ " nom " ] } _ { t [ " prenom " ] } _ { t [ " etudid " ] } .jpg '
2020-09-26 16:19:37 +02:00
Z . writestr ( filename , img )
Z . close ( )
size = data . tell ( )
2022-04-08 13:01:47 +02:00
log ( f " trombino_zip: { size } bytes " )
2021-08-31 20:18:50 +02:00
data . seek ( 0 )
return send_file (
data ,
mimetype = " application/zip " ,
download_name = " trombi.zip " ,
as_attachment = True ,
2020-09-26 16:19:37 +02:00
)
# Copy photos from portal to ScoDoc
2021-09-27 10:20:10 +02:00
def trombino_copy_photos ( group_ids = [ ] , dialog_confirmed = False ) :
2020-09-26 16:19:37 +02:00
" Copy photos from portal to ScoDoc (overwriting local copy) "
2021-09-08 00:34:45 +02:00
groups_infos = sco_groups_view . DisplayedGroupsInfos ( group_ids )
2021-05-11 11:48:32 +02:00
back_url = " groups_view? %s &curtab=tab-photos " % groups_infos . groups_query_args
2020-09-26 16:19:37 +02:00
2021-08-20 10:51:42 +02:00
portal_url = sco_portal_apogee . get_portal_url ( )
2021-07-29 16:31:15 +02:00
header = html_sco_header . sco_header ( page_title = " Chargement des photos " )
2021-07-29 10:19:00 +02:00
footer = html_sco_header . sco_footer ( )
2020-09-26 16:19:37 +02:00
if not portal_url :
return (
header
+ ' <p>portail non configuré</p><p><a href= " %s " >Retour au trombinoscope</a></p> '
% back_url
+ footer
)
if not dialog_confirmed :
2021-06-02 22:40:34 +02:00
return scu . confirm_dialog (
2020-09-26 16:19:37 +02:00
""" <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 ) ,
dest_url = " " ,
cancel_url = back_url ,
parameters = { " group_ids " : group_ids } ,
)
msg = [ ]
nok = 0
for etud in groups_infos . members :
2021-08-21 00:24:51 +02:00
path , diag = sco_photos . copy_portal_photo_to_fs ( etud )
2020-09-26 16:19:37 +02:00
msg . append ( diag )
if path :
nok + = 1
msg . append ( " <b> %d photos correctement chargées</b> " % nok )
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
)
2021-08-21 00:24:51 +02:00
def _get_etud_platypus_image ( t , image_width = 2 * cm ) :
2021-12-20 22:53:09 +01:00
""" Returns a platypus object for the photo of student t """
2020-09-26 16:19:37 +02:00
try :
2021-12-20 22:53:09 +01:00
path = sco_photos . photo_pathname ( t [ " photo_filename " ] , size = " small " )
2020-09-26 16:19:37 +02:00
if not path :
# log('> unknown')
path = sco_photos . UNKNOWN_IMAGE_PATH
im = PILImage . open ( path )
w0 , h0 = im . size [ 0 ] , im . size [ 1 ]
if w0 > h0 :
W = image_width
H = h0 * W / w0
else :
H = image_width
W = w0 * H / h0
return reportlab . platypus . Image ( path , width = W , height = H )
except :
log (
" *** exception while processing photo of %s ( %s ) (path= %s ) "
% ( t [ " nom " ] , t [ " etudid " ] , path )
)
raise
2021-09-27 10:20:10 +02:00
def _trombino_pdf ( groups_infos ) :
2020-09-26 16:19:37 +02:00
" Send photos as pdf page "
# Generate PDF page
filename = " trombino_ %s " % groups_infos . groups_filename + " .pdf "
sem = groups_infos . formsemestre # suppose 1 seul semestre
PHOTOWIDTH = 3 * cm
COLWIDTH = 3.6 * cm
N_PER_ROW = 5 # XXX should be in ScoDoc preferences
StyleSheet = styles . getSampleStyleSheet ( )
2021-07-26 17:11:00 +02:00
report = io . BytesIO ( ) # in-memory document, no disk file
2020-09-26 16:19:37 +02:00
objects = [
Paragraph (
SU ( " Trombinoscope " + sem [ " titreannee " ] + " " + groups_infos . groups_titles ) ,
StyleSheet [ " Heading3 " ] ,
)
]
L = [ ]
n = 0
currow = [ ]
log ( " _trombino_pdf %d elements " % len ( groups_infos . members ) )
for t in groups_infos . members :
2021-08-21 00:24:51 +02:00
img = _get_etud_platypus_image ( t , image_width = PHOTOWIDTH )
2020-09-26 16:19:37 +02:00
elem = Table (
[
[ img ] ,
[
Paragraph (
2021-06-19 23:21:37 +02:00
SU ( sco_etud . format_nomprenom ( t ) ) ,
2020-09-26 16:19:37 +02:00
StyleSheet [ " Normal " ] ,
)
] ,
] ,
colWidths = [ PHOTOWIDTH ] ,
)
currow . append ( elem )
if n == ( N_PER_ROW - 1 ) :
L . append ( currow )
currow = [ ]
n = ( n + 1 ) % N_PER_ROW
if currow :
currow + = [ " " ] * ( N_PER_ROW - len ( currow ) )
L . append ( currow )
if not L :
table = Paragraph ( SU ( " Aucune photo à exporter ! " ) , StyleSheet [ " Normal " ] )
else :
table = Table (
L ,
colWidths = [ COLWIDTH ] * N_PER_ROW ,
style = TableStyle (
[
# ('RIGHTPADDING', (0,0), (-1,-1), -5*mm),
( " VALIGN " , ( 0 , 0 ) , ( - 1 , - 1 ) , " TOP " ) ,
( " GRID " , ( 0 , 0 ) , ( - 1 , - 1 ) , 0.25 , colors . grey ) ,
]
) ,
)
objects . append ( table )
# Build document
document = BaseDocTemplate ( report )
document . addPageTemplates (
2022-03-12 09:40:48 +01:00
sco_pdf . ScoDocPageTemplate (
2020-10-21 22:56:25 +02:00
document ,
2021-07-28 17:03:54 +02:00
preferences = sco_preferences . SemPreferences ( sem [ " formsemestre_id " ] ) ,
2020-09-26 16:19:37 +02:00
)
)
document . build ( objects )
2021-08-31 20:18:50 +02:00
report . seek ( 0 )
return send_file (
report ,
mimetype = scu . PDF_MIMETYPE ,
download_name = scu . sanitize_filename ( filename ) ,
as_attachment = True ,
)
2020-09-26 16:19:37 +02:00
# --------------------- Sur une idée de l'IUT d'Orléans:
2021-09-27 10:20:10 +02:00
def _listeappel_photos_pdf ( groups_infos ) :
2020-09-26 16:19:37 +02:00
" Doc pdf pour liste d ' appel avec photos "
filename = " trombino_ %s " % groups_infos . groups_filename + " .pdf "
sem = groups_infos . formsemestre # suppose 1 seul semestre
PHOTOWIDTH = 2 * cm
2021-02-01 23:54:46 +01:00
# COLWIDTH = 3.6 * cm
# ROWS_PER_PAGE = 26 # XXX should be in ScoDoc preferences
2020-09-26 16:19:37 +02:00
StyleSheet = styles . getSampleStyleSheet ( )
2021-07-26 17:11:00 +02:00
report = io . BytesIO ( ) # in-memory document, no disk file
2020-09-26 16:19:37 +02:00
objects = [
Paragraph (
SU (
sem [ " titreannee " ]
+ " "
+ groups_infos . groups_titles
+ " ( %d ) " % len ( groups_infos . members )
) ,
StyleSheet [ " Heading3 " ] ,
)
]
L = [ ]
n = 0
currow = [ ]
log ( " _listeappel_photos_pdf %d elements " % len ( groups_infos . members ) )
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 ]
2021-08-21 00:24:51 +02:00
img = _get_etud_platypus_image ( t , image_width = PHOTOWIDTH )
2020-09-26 16:19:37 +02:00
txt = Paragraph (
2021-06-19 23:21:37 +02:00
SU ( sco_etud . format_nomprenom ( t ) ) ,
2020-09-26 16:19:37 +02:00
StyleSheet [ " Normal " ] ,
)
if currow :
currow + = [ " " ]
currow + = [ img , txt , " " ]
if i % 2 :
L . append ( currow )
currow = [ ]
if currow :
currow + = [ " " ] * 3
L . append ( currow )
if not L :
table = Paragraph ( SU ( " Aucune photo à exporter ! " ) , StyleSheet [ " Normal " ] )
else :
table = Table (
L ,
colWidths = [ 2 * cm , 4 * cm , 27 * mm , 5 * mm , 2 * cm , 4 * cm , 27 * mm ] ,
style = TableStyle (
[
# ('RIGHTPADDING', (0,0), (-1,-1), -5*mm),
( " VALIGN " , ( 0 , 0 ) , ( - 1 , - 1 ) , " TOP " ) ,
( " GRID " , ( 0 , 0 ) , ( 2 , - 1 ) , 0.25 , colors . grey ) ,
( " GRID " , ( 4 , 0 ) , ( - 1 , - 1 ) , 0.25 , colors . grey ) ,
]
) ,
)
objects . append ( table )
# Build document
document = BaseDocTemplate ( report )
document . addPageTemplates (
2022-03-12 09:40:48 +01:00
sco_pdf . ScoDocPageTemplate (
2020-10-21 22:56:25 +02:00
document ,
2021-07-28 17:03:54 +02:00
preferences = sco_preferences . SemPreferences ( sem [ " formsemestre_id " ] ) ,
2020-09-26 16:19:37 +02:00
)
)
document . build ( objects )
data = report . getvalue ( )
2021-09-21 15:53:33 +02:00
return scu . sendPDFFile ( data , filename )
2020-09-26 16:19:37 +02:00
# --------------------- Upload des photos de tout un groupe
2022-04-08 13:01:47 +02:00
def photos_generate_excel_sample ( group_ids = ( ) ) :
2020-10-21 22:56:25 +02:00
""" Feuille excel pour import fichiers photos """
2021-06-21 10:17:16 +02:00
fmt = sco_import_etuds . sco_import_format ( )
data = sco_import_etuds . sco_import_generate_excel_sample (
2020-09-26 16:19:37 +02:00
fmt ,
group_ids = group_ids ,
only_tables = [ " identite " ] ,
exclude_cols = [
" date_naissance " ,
" lieu_naissance " ,
" nationalite " ,
" statut " ,
" photo_filename " ,
] ,
extra_cols = [ " fichier_photo " ] ,
)
2021-09-18 10:11:46 +02:00
return scu . send_file (
data , " ImportPhotos " , scu . XLSX_SUFFIX , scu . XLSX_MIMETYPE , attached = True
)
2021-09-27 10:20:10 +02:00
# return sco_excel.send_excel_file(data, "ImportPhotos" + scu.XLSX_SUFFIX)
2020-09-26 16:19:37 +02:00
2022-04-08 13:01:47 +02:00
def photos_import_files_form ( group_ids = ( ) ) :
2020-10-21 22:56:25 +02:00
""" Formulaire pour importation photos """
2021-11-27 18:44:32 +01:00
if not group_ids :
raise ScoValueError ( " paramètre manquant ! " )
2021-09-08 00:34:45 +02:00
groups_infos = sco_groups_view . DisplayedGroupsInfos ( group_ids )
2022-04-08 13:01:47 +02:00
back_url = f " groups_view? { groups_infos . groups_query_args } &curtab=tab-photos "
2020-09-26 16:19:37 +02:00
H = [
2021-07-29 16:31:15 +02:00
html_sco_header . sco_header ( page_title = " Import des photos des étudiants " ) ,
2022-04-08 13:01:47 +02:00
f """ <h2 class= " formsemestre " >Téléchargement des photos des étudiants</h2>
< p > < b > Vous pouvez aussi charger les photos individuellement via la fiche
de chaque étudiant ( menu " Etudiant " / " Changer la photo " ) . < / b >
< / p >
< p class = " help " > Cette page permet de charger en une seule fois les photos
de plusieurs étudiants . < br / >
Il faut d ' abord remplir une feuille excel donnant les noms
2020-09-26 16:19:37 +02:00
des fichiers images ( une image par étudiant ) .
< / p >
2022-04-08 13:01:47 +02:00
< p class = " help " > Ensuite , réunir vos images dans un fichier zip , puis télécharger
2020-09-26 16:19:37 +02:00
simultanément le fichier excel et le fichier zip .
< / p >
< ol >
2022-04-08 13:01:47 +02:00
< li > < a class = " stdlink " href = " photos_generate_excel_sample? {groups_infos.groups_query_args} " >
2020-09-26 16:19:37 +02:00
Obtenir la feuille excel à remplir < / a >
< / li >
< li style = " padding-top: 2em; " >
2022-04-08 13:01:47 +02:00
""" ,
2020-09-26 16:19:37 +02:00
]
2021-07-29 10:19:00 +02:00
F = html_sco_header . sco_footer ( )
2021-09-28 09:14:04 +02:00
vals = scu . get_request_args ( )
2021-09-27 10:20:10 +02:00
vals [ " group_ids " ] = groups_infos . group_ids
2020-09-26 16:19:37 +02:00
tf = TrivialFormulator (
2021-09-18 10:10:02 +02:00
request . base_url ,
2021-09-27 10:20:10 +02:00
vals ,
2020-09-26 16:19:37 +02:00
(
( " xlsfile " , { " title " : " Fichier Excel: " , " input_type " : " file " , " size " : 40 } ) ,
( " zipfile " , { " title " : " Fichier zip: " , " input_type " : " file " , " size " : 40 } ) ,
( " group_ids " , { " input_type " : " hidden " , " type " : " list " } ) ,
) ,
)
if tf [ 0 ] == 0 :
return " \n " . join ( H ) + tf [ 1 ] + " </li></ol> " + F
elif tf [ 0 ] == - 1 :
2021-07-31 18:01:10 +02:00
return flask . redirect ( back_url )
2020-09-26 16:19:37 +02:00
else :
2021-10-20 16:47:41 +02:00
def callback ( etud , data , filename ) :
sco_photos . store_photo ( etud , data )
(
ignored_zipfiles ,
unmatched_files ,
stored_etud_filename ,
) = zip_excel_import_files (
2020-09-26 16:19:37 +02:00
xlsfile = tf [ 2 ] [ " xlsfile " ] ,
zipfile = tf [ 2 ] [ " zipfile " ] ,
2021-10-20 16:47:41 +02:00
callback = callback ,
filename_title = " fichier_photo " ,
)
return render_template (
" scolar/photos_import_files.html " ,
page_title = " Téléchargement des photos des étudiants " ,
ignored_zipfiles = ignored_zipfiles ,
unmatched_files = unmatched_files ,
stored_etud_filename = stored_etud_filename ,
next_page = url_for (
" scolar.groups_view " ,
scodoc_dept = g . scodoc_dept ,
formsemestre_id = groups_infos . formsemestre_id ,
curtab = " tab-photos " ,
) ,
2020-09-26 16:19:37 +02:00
)
def zip_excel_import_files (
xlsfile = None ,
zipfile = None ,
callback = None ,
filename_title = " " , # doit obligatoirement etre specifié
) :
""" Importation de fichiers à partir d ' un excel et d ' un zip
La fonction
callback ( )
2021-10-20 16:47:41 +02:00
est appelée pour chaque fichier trouvé .
Fonction utilisée pour les photos et les fichiers étudiants ( archives ) .
2020-09-26 16:19:37 +02:00
"""
# 1- build mapping etudid -> filename
exceldata = xlsfile . read ( )
if not exceldata :
raise ScoValueError ( " Fichier excel vide ou invalide " )
2021-08-20 09:17:44 +02:00
_ , data = sco_excel . excel_bytes_to_list ( exceldata )
2021-10-20 16:47:41 +02:00
if not data :
2020-09-26 16:19:37 +02:00
raise ScoValueError ( " Fichier excel vide ! " )
# on doit avoir une colonne etudid et une colonne filename_title ('fichier_photo')
titles = data [ 0 ]
try :
etudid_idx = titles . index ( " etudid " )
filename_idx = titles . index ( filename_title )
except :
raise ScoValueError (
" Fichier excel incorrect (il faut une colonne etudid et une colonne %s ) ! "
% filename_title
)
def normfilename ( fn , lowercase = True ) :
" normalisation used to match filenames "
fn = fn . replace ( " \\ " , " / " ) # not sure if this is necessary ?
fn = fn . strip ( )
if lowercase :
2021-08-21 00:24:51 +02:00
fn = fn . lower ( )
2020-09-26 16:19:37 +02:00
fn = fn . split ( " / " ) [ - 1 ] # use only last component, not directories
return fn
2021-10-20 16:47:41 +02:00
filename_to_etud = { } # filename : etudid
2020-09-26 16:19:37 +02:00
for l in data [ 1 : ] :
filename = l [ filename_idx ] . strip ( )
if filename :
2021-10-20 16:47:41 +02:00
filename_to_etud [ normfilename ( filename ) ] = l [ etudid_idx ]
2020-09-26 16:19:37 +02:00
# 2- Ouvre le zip et
try :
z = ZipFile ( zipfile )
except BadZipfile :
2021-10-20 16:47:41 +02:00
raise ScoValueError ( " Fichier ZIP incorrect ! " ) from BadZipfile
2020-09-26 16:19:37 +02:00
ignored_zipfiles = [ ]
2021-10-20 16:47:41 +02:00
stored_etud_filename = [ ] # [ (etud, filename) ]
2020-09-26 16:19:37 +02:00
for name in z . namelist ( ) :
if len ( name ) > 4 and name [ - 1 ] != " / " and " . " in name :
data = z . read ( name )
# match zip filename with name given in excel
normname = normfilename ( name )
2021-10-20 16:47:41 +02:00
if normname in filename_to_etud :
etudid = filename_to_etud [ normname ]
2020-09-26 16:19:37 +02:00
# ok, store photo
try :
2021-06-19 23:21:37 +02:00
etud = sco_etud . get_etud_info ( etudid = etudid , filled = True ) [ 0 ]
2021-10-20 16:47:41 +02:00
del filename_to_etud [ normname ]
2020-09-26 16:19:37 +02:00
except :
raise ScoValueError ( " ID étudiant invalide: %s " % etudid )
callback (
etud ,
data ,
normfilename ( name , lowercase = False ) ,
)
2021-10-20 16:47:41 +02:00
stored_etud_filename . append ( ( etud , name ) )
2020-09-26 16:19:37 +02:00
else :
log ( " zip: zip name %s not in excel ! " % name )
ignored_zipfiles . append ( name )
else :
if name [ - 1 ] != " / " :
ignored_zipfiles . append ( name )
log ( " zip: ignoring %s " % name )
2021-10-20 16:47:41 +02:00
if filename_to_etud :
2020-09-26 16:19:37 +02:00
# lignes excel non traitées
2021-10-20 16:47:41 +02:00
unmatched_files = list ( filename_to_etud . keys ( ) )
2020-09-26 16:19:37 +02:00
else :
unmatched_files = [ ]
2021-10-20 16:47:41 +02:00
return ignored_zipfiles , unmatched_files , stored_etud_filename