# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. # # 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 """ from cStringIO import StringIO from zipfile import ZipFile, BadZipfile import xml import tempfile 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 from notes_log import log import sco_utils as scu import sco_pdf from sco_pdf import SU from sco_exceptions import ScoValueError from TrivialFormulator import TrivialFormulator import scolars import sco_photos import sco_formsemestre import sco_groups import sco_groups_view import sco_portal_apogee import htmlutils import ImportScolars import sco_excel def trombino( context, REQUEST=None, group_ids=[], # liste des groupes à afficher 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( context, group_ids, formsemestre_id=formsemestre_id, etat=etat, REQUEST=REQUEST ) # if format != "html" and not dialog_confirmed: ok, dialog = check_local_photos_availability( context, groups_infos, REQUEST, format=format ) if not ok: return dialog if format == "zip": return _trombino_zip(context, groups_infos, REQUEST) elif format == "pdf": return _trombino_pdf(context, groups_infos, REQUEST) elif format == "pdflist": return _listeappel_photos_pdf(context, groups_infos, REQUEST) else: raise Exception("invalid format") # return _trombino_html_header(context, REQUEST) + trombino_html(context, group, members, REQUEST=REQUEST) + context.sco_footer(REQUEST) def _trombino_html_header(context, REQUEST): return context.sco_header(REQUEST, javascripts=["js/trombino.js"]) def trombino_html(context, groups_infos, REQUEST=None): "HTML snippet for trombino (with title and menu)" args = groups_infos.groups_query_args menuTrombi = [ {"title": "Charger des photos...", "url": "photos_import_files_form?%s" % args}, { "title": "Obtenir archive Zip des photos", "url": "trombino?%s&format=zip" % args, }, { "title": "Recopier les photos depuis le portail", "url": "trombino_copy_photos?%s" % args, }, ] 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 = [ '
%s | ' % (ng) ] if groups_infos.members: H.append( "" + htmlutils.make_menu("Gérer les photos", menuTrombi, alone=True) + " | " ) H.append("
Attention: %d photos ne sont pas disponibles et ne peuvent pas être exportées.
Vous pouvez exporter seulement les photos existantes""" % ( nb_missing, groups_infos.base_url + "&dialog_confirmed=1&format=%s" % format, ), dest_url="trombino", OK="Exporter seulement les photos existantes", cancel_url="groups_view?curtab=tab-photos&" + groups_infos.groups_query_args, REQUEST=REQUEST, parameters=parameters, ), ) else: return True, "" def _trombino_zip(context, groups_infos, REQUEST): "Send photos as zip archive" data = StringIO() 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(context, t, size="orig") if not im_path: continue img = open(im_path).read() code_nip = t["code_nip"] if code_nip: filename = code_nip + ".jpg" else: filename = t["nom"] + "_" + t["prenom"] + "_" + t["etudid"] + ".jpg" Z.writestr(filename, img) Z.close() size = data.tell() log("trombino_zip: %d bytes" % size) content_type = "application/zip" REQUEST.RESPONSE.setHeader( "content-disposition", 'attachement; filename="trombi.zip"' ) REQUEST.RESPONSE.setHeader("content-type", content_type) REQUEST.RESPONSE.setHeader("content-length", size) return data.getvalue() # Copy photos from portal to ScoDoc def trombino_copy_photos(context, group_ids=[], REQUEST=None, dialog_confirmed=False): "Copy photos from portal to ScoDoc (overwriting local copy)" groups_infos = sco_groups_view.DisplayedGroupsInfos( context, group_ids, REQUEST=REQUEST ) back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args portal_url = sco_portal_apogee.get_portal_url(context) header = context.sco_header(REQUEST, page_title="Chargement des photos") footer = context.sco_footer(REQUEST) if not portal_url: return ( header + '
portail non configuré
' % back_url + footer ) if not dialog_confirmed: return scu.confirm_dialog( context, """Les photos du groupe %s présentes dans ScoDoc seront remplacées par celles du portail (si elles existent).
(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)
""" % (groups_infos.groups_titles), dest_url="", REQUEST=REQUEST, cancel_url=back_url, parameters={"group_ids": group_ids}, ) msg = [] nok = 0 for etud in groups_infos.members: path, diag = sco_photos.copy_portal_photo_to_fs(context, etud, REQUEST=REQUEST) msg.append(diag) if path: nok += 1 msg.append("%d photos correctement chargées" % nok) return ( header + "retour au trombinoscope' % back_url + footer ) def _get_etud_platypus_image(context, t, image_width=2 * cm): """Returns aplatypus object for the photo of student t""" try: path = sco_photos.photo_pathname(context, t, size="small") 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 def _trombino_pdf(context, groups_infos, REQUEST): "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() report = StringIO() # in-memory document, no disk file 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: img = _get_etud_platypus_image(context, t, image_width=PHOTOWIDTH) elem = Table( [ [img], [ Paragraph( SU(scolars.format_nomprenom(t)), 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( sco_pdf.ScolarsPageTemplate( document, context=context, preferences=context.get_preferences(sem["formsemestre_id"]), ) ) document.build(objects) data = report.getvalue() return scu.sendPDFFile(REQUEST, data, filename) # --------------------- Sur une idée de l'IUT d'Orléans: def _listeappel_photos_pdf(context, groups_infos, REQUEST): "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 # COLWIDTH = 3.6 * cm # ROWS_PER_PAGE = 26 # XXX should be in ScoDoc preferences StyleSheet = styles.getSampleStyleSheet() report = StringIO() # in-memory document, no disk file 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] img = _get_etud_platypus_image(context, t, image_width=PHOTOWIDTH) txt = Paragraph( SU(scolars.format_nomprenom(t)), 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( sco_pdf.ScolarsPageTemplate( document, context, preferences=context.get_preferences(sem["formsemestre_id"]), ) ) document.build(objects) data = report.getvalue() return scu.sendPDFFile(REQUEST, data, filename) # --------------------- Upload des photos de tout un groupe def photos_generate_excel_sample(context, group_ids=[], REQUEST=None): """Feuille excel pour import fichiers photos""" fmt = ImportScolars.sco_import_format() data = ImportScolars.sco_import_generate_excel_sample( fmt, context=context, group_ids=group_ids, only_tables=["identite"], exclude_cols=[ "date_naissance", "lieu_naissance", "nationalite", "statut", "photo_filename", ], extra_cols=["fichier_photo"], REQUEST=REQUEST, ) return sco_excel.sendExcelFile(REQUEST, data, "ImportPhotos.xls") def photos_import_files_form(context, group_ids=[], REQUEST=None): """Formulaire pour importation photos""" groups_infos = sco_groups_view.DisplayedGroupsInfos( context, group_ids, REQUEST=REQUEST ) back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args H = [ context.sco_header(REQUEST, page_title="Import des photos des étudiants"), """
Vous pouvez aussi charger les photos individuellement via la fiche de chaque étudiant (menu "Etudiant" / "Changer la photo").
Cette page permet de charger en une seule fois les photos de plusieurs étudiants.
Il faut d'abord remplir une feuille excel donnant les noms
des fichiers images (une image par étudiant).
Ensuite, réunir vos images dans un fichier zip, puis télécharger simultanément le fichier excel et le fichier zip.