Trombino: export en doc. Closes #344

This commit is contained in:
Emmanuel Viennet 2022-04-08 13:01:47 +02:00
parent 6ac096d29d
commit 1153bc9a7c
3 changed files with 121 additions and 18 deletions

View File

@ -55,19 +55,18 @@ from app.scodoc.sco_pdf import SU
from app.scodoc import html_sco_header
from app.scodoc import htmlutils
from app.scodoc import sco_import_etuds
from app.scodoc import sco_etud
from app.scodoc import sco_excel
from app.scodoc import sco_formsemestre
from app.scodoc import sco_groups
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
from app.scodoc import sco_etud
from app.scodoc import sco_trombino_doc
def trombino(
group_ids=[], # liste des groupes à afficher
group_ids=(), # liste des groupes à afficher
formsemestre_id=None, # utilisé si pas de groupes selectionné
etat=None,
format="html",
@ -93,6 +92,8 @@ def trombino(
return _trombino_pdf(groups_infos)
elif format == "pdflist":
return _listeappel_photos_pdf(groups_infos)
elif format == "doc":
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()
@ -176,8 +177,13 @@ def trombino_html(groups_infos):
H.append("</div>")
H.append(
'<div style="margin-bottom:15px;"><a class="stdlink" href="trombino?format=pdf&%s">Version PDF</a></div>'
% groups_infos.groups_query_args
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>"""
)
return "\n".join(H)
@ -234,7 +240,7 @@ def _trombino_zip(groups_infos):
Z.writestr(filename, img)
Z.close()
size = data.tell()
log("trombino_zip: %d bytes" % size)
log(f"trombino_zip: {size} bytes")
data.seek(0)
return send_file(
data,
@ -470,7 +476,7 @@ def _listeappel_photos_pdf(groups_infos):
# --------------------- Upload des photos de tout un groupe
def photos_generate_excel_sample(group_ids=[]):
def photos_generate_excel_sample(group_ids=()):
"""Feuille excel pour import fichiers photos"""
fmt = sco_import_etuds.sco_import_format()
data = sco_import_etuds.sco_import_generate_excel_sample(
@ -492,18 +498,21 @@ def photos_generate_excel_sample(group_ids=[]):
# return sco_excel.send_excel_file(data, "ImportPhotos" + scu.XLSX_SUFFIX)
def photos_import_files_form(group_ids=[]):
def photos_import_files_form(group_ids=()):
"""Formulaire pour importation photos"""
if not group_ids:
raise ScoValueError("paramètre manquant !")
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
back_url = "groups_view?%s&curtab=tab-photos" % groups_infos.groups_query_args
back_url = f"groups_view?{groups_infos.groups_query_args}&curtab=tab-photos"
H = [
html_sco_header.sco_header(page_title="Import des photos des étudiants"),
"""<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/>
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
des fichiers images (une image par étudiant).
</p>
@ -511,12 +520,11 @@ def photos_import_files_form(group_ids=[]):
simultanément le fichier excel et le fichier zip.
</p>
<ol>
<li><a class="stdlink" href="photos_generate_excel_sample?%s">
<li><a class="stdlink" href="photos_generate_excel_sample?{groups_infos.groups_query_args}">
Obtenir la feuille excel à remplir</a>
</li>
<li style="padding-top: 2em;">
"""
% groups_infos.groups_query_args,
""",
]
F = html_sco_header.sco_footer()
vals = scu.get_request_args()

View File

@ -0,0 +1,76 @@
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
# See LICENSE
##############################################################################
"""Génération d'un trombinoscope en doc
"""
import docx
from docx.shared import Mm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_ALIGN_VERTICAL
from app.scodoc import sco_etud
from app.scodoc import sco_photos
import app.scodoc.sco_utils as scu
import sco_version
def trombino_doc(groups_infos):
"Send photos as docx document"
filename = f"trombino_{groups_infos.groups_filename}.docx"
sem = groups_infos.formsemestre # suppose 1 seul semestre
PHOTO_WIDTH = Mm(25)
N_PER_ROW = 5 # XXX should be in ScoDoc preferences
document = docx.Document()
document.add_heading(
f"Trombinoscope {sem['titreannee']} {groups_infos.groups_titles}", 1
)
section = document.sections[0]
footer = section.footer
footer.paragraphs[
0
].text = f"Généré par {sco_version.SCONAME} le {scu.timedate_human_repr()}"
nb_images = len(groups_infos.members)
table = document.add_table(rows=2 * (nb_images // N_PER_ROW + 1), cols=N_PER_ROW)
table.allow_autofit = False
for i, t in enumerate(groups_infos.members):
li = i // N_PER_ROW
co = i % N_PER_ROW
img_path = (
sco_photos.photo_pathname(t["photo_filename"], size="small")
or sco_photos.UNKNOWN_IMAGE_PATH
)
cell = table.rows[2 * li].cells[co]
cell.vertical_alignment = WD_ALIGN_VERTICAL.TOP
cell_p, cell_f, cell_r = _paragraph_format_run(cell)
cell_r.add_picture(img_path, width=PHOTO_WIDTH)
# le nom de l'étudiant: cellules de lignes impaires
cell = table.rows[2 * li + 1].cells[co]
cell.vertical_alignment = WD_ALIGN_VERTICAL.TOP
cell_p, cell_f, cell_r = _paragraph_format_run(cell)
cell_r.add_text(sco_etud.format_nomprenom(t))
cell_f.space_after = Mm(8)
return scu.send_docx(document, filename)
def _paragraph_format_run(cell):
"parag. dans cellule tableau"
# inspired by https://stackoverflow.com/questions/64218305/problem-with-python-docx-putting-pictures-in-a-table
paragraph = cell.paragraphs[0]
fmt = paragraph.paragraph_format
run = paragraph.add_run()
fmt.space_before = Mm(0)
fmt.space_after = Mm(0)
fmt.line_spacing = 1.0
fmt.alignment = WD_ALIGN_PARAGRAPH.CENTER
return paragraph, fmt, run

View File

@ -33,6 +33,7 @@ import bisect
import copy
import datetime
from enum import IntEnum
import io
import json
from hashlib import md5
import numbers
@ -49,6 +50,7 @@ from PIL import Image as PILImage
import pydot
import requests
import flask
from flask import g, request
from flask import flash, url_for, make_response, jsonify
@ -379,6 +381,10 @@ CSV_FIELDSEP = ";"
CSV_LINESEP = "\n"
CSV_MIMETYPE = "text/comma-separated-values"
CSV_SUFFIX = ".csv"
DOCX_MIMETYPE = (
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)
DOCX_SUFFIX = ".docx"
JSON_MIMETYPE = "application/json"
JSON_SUFFIX = ".json"
PDF_MIMETYPE = "application/pdf"
@ -398,6 +404,7 @@ def get_mime_suffix(format_code: str) -> tuple[str, str]:
"""
d = {
"csv": (CSV_MIMETYPE, CSV_SUFFIX),
"docx": (DOCX_MIMETYPE, DOCX_SUFFIX),
"xls": (XLSX_MIMETYPE, XLSX_SUFFIX),
"xlsx": (XLSX_MIMETYPE, XLSX_SUFFIX),
"pdf": (PDF_MIMETYPE, PDF_SUFFIX),
@ -740,6 +747,18 @@ def send_file(data, filename="", suffix="", mime=None, attached=None):
return response
def send_docx(document, filename):
"Send a python-docx document"
buffer = io.BytesIO() # in-memory document, no disk file
document.save(buffer)
buffer.seek(0)
return flask.send_file(
buffer,
attachment_filename=sanitize_filename(filename),
mimetype=DOCX_MIMETYPE,
)
def get_request_args():
"""returns a dict with request (POST or GET) arguments
converted to suit legacy Zope style (scodoc7) functions.