Trombino: export en doc. Closes #344
This commit is contained in:
parent
6ac096d29d
commit
1153bc9a7c
@ -55,19 +55,18 @@ from app.scodoc.sco_pdf import SU
|
|||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import htmlutils
|
from app.scodoc import htmlutils
|
||||||
from app.scodoc import sco_import_etuds
|
from app.scodoc import sco_import_etuds
|
||||||
|
from app.scodoc import sco_etud
|
||||||
from app.scodoc import sco_excel
|
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_groups_view
|
||||||
from app.scodoc import sco_pdf
|
from app.scodoc import sco_pdf
|
||||||
from app.scodoc import sco_photos
|
from app.scodoc import sco_photos
|
||||||
from app.scodoc import sco_portal_apogee
|
from app.scodoc import sco_portal_apogee
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_trombino_doc
|
||||||
|
|
||||||
|
|
||||||
def trombino(
|
def trombino(
|
||||||
group_ids=[], # liste des groupes à afficher
|
group_ids=(), # liste des groupes à afficher
|
||||||
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
||||||
etat=None,
|
etat=None,
|
||||||
format="html",
|
format="html",
|
||||||
@ -93,6 +92,8 @@ def trombino(
|
|||||||
return _trombino_pdf(groups_infos)
|
return _trombino_pdf(groups_infos)
|
||||||
elif format == "pdflist":
|
elif format == "pdflist":
|
||||||
return _listeappel_photos_pdf(groups_infos)
|
return _listeappel_photos_pdf(groups_infos)
|
||||||
|
elif format == "doc":
|
||||||
|
return sco_trombino_doc.trombino_doc(groups_infos)
|
||||||
else:
|
else:
|
||||||
raise Exception("invalid format")
|
raise Exception("invalid format")
|
||||||
# return _trombino_html_header() + trombino_html( group, members) + html_sco_header.sco_footer()
|
# 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>")
|
||||||
H.append(
|
H.append(
|
||||||
'<div style="margin-bottom:15px;"><a class="stdlink" href="trombino?format=pdf&%s">Version PDF</a></div>'
|
f"""<div style="margin-bottom:15px;">
|
||||||
% groups_infos.groups_query_args
|
<a class="stdlink" href="{url_for('scolar.trombino', scodoc_dept=g.scodoc_dept,
|
||||||
|
format='pdf', group_ids=groups_infos.group_ids)}">Version PDF</a>
|
||||||
|
|
||||||
|
<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)
|
return "\n".join(H)
|
||||||
|
|
||||||
@ -234,7 +240,7 @@ def _trombino_zip(groups_infos):
|
|||||||
Z.writestr(filename, img)
|
Z.writestr(filename, img)
|
||||||
Z.close()
|
Z.close()
|
||||||
size = data.tell()
|
size = data.tell()
|
||||||
log("trombino_zip: %d bytes" % size)
|
log(f"trombino_zip: {size} bytes")
|
||||||
data.seek(0)
|
data.seek(0)
|
||||||
return send_file(
|
return send_file(
|
||||||
data,
|
data,
|
||||||
@ -470,7 +476,7 @@ def _listeappel_photos_pdf(groups_infos):
|
|||||||
|
|
||||||
|
|
||||||
# --------------------- Upload des photos de tout un groupe
|
# --------------------- 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"""
|
"""Feuille excel pour import fichiers photos"""
|
||||||
fmt = sco_import_etuds.sco_import_format()
|
fmt = sco_import_etuds.sco_import_format()
|
||||||
data = sco_import_etuds.sco_import_generate_excel_sample(
|
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)
|
# 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"""
|
"""Formulaire pour importation photos"""
|
||||||
if not group_ids:
|
if not group_ids:
|
||||||
raise ScoValueError("paramètre manquant !")
|
raise ScoValueError("paramètre manquant !")
|
||||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(group_ids)
|
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 = [
|
H = [
|
||||||
html_sco_header.sco_header(page_title="Import des photos des étudiants"),
|
html_sco_header.sco_header(page_title="Import des photos des étudiants"),
|
||||||
"""<h2 class="formsemestre">Téléchargement des photos des étudiants</h2>
|
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><b>Vous pouvez aussi charger les photos individuellement via la fiche
|
||||||
<p class="help">Cette page permet de charger en une seule fois les photos de plusieurs étudiants.<br/>
|
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
|
Il faut d'abord remplir une feuille excel donnant les noms
|
||||||
des fichiers images (une image par étudiant).
|
des fichiers images (une image par étudiant).
|
||||||
</p>
|
</p>
|
||||||
@ -511,12 +520,11 @@ def photos_import_files_form(group_ids=[]):
|
|||||||
simultanément le fichier excel et le fichier zip.
|
simultanément le fichier excel et le fichier zip.
|
||||||
</p>
|
</p>
|
||||||
<ol>
|
<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>
|
Obtenir la feuille excel à remplir</a>
|
||||||
</li>
|
</li>
|
||||||
<li style="padding-top: 2em;">
|
<li style="padding-top: 2em;">
|
||||||
"""
|
""",
|
||||||
% groups_infos.groups_query_args,
|
|
||||||
]
|
]
|
||||||
F = html_sco_header.sco_footer()
|
F = html_sco_header.sco_footer()
|
||||||
vals = scu.get_request_args()
|
vals = scu.get_request_args()
|
||||||
|
76
app/scodoc/sco_trombino_doc.py
Normal file
76
app/scodoc/sco_trombino_doc.py
Normal 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
|
@ -33,6 +33,7 @@ import bisect
|
|||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
import numbers
|
import numbers
|
||||||
@ -49,6 +50,7 @@ from PIL import Image as PILImage
|
|||||||
import pydot
|
import pydot
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import flask
|
||||||
from flask import g, request
|
from flask import g, request
|
||||||
from flask import flash, url_for, make_response, jsonify
|
from flask import flash, url_for, make_response, jsonify
|
||||||
|
|
||||||
@ -379,6 +381,10 @@ CSV_FIELDSEP = ";"
|
|||||||
CSV_LINESEP = "\n"
|
CSV_LINESEP = "\n"
|
||||||
CSV_MIMETYPE = "text/comma-separated-values"
|
CSV_MIMETYPE = "text/comma-separated-values"
|
||||||
CSV_SUFFIX = ".csv"
|
CSV_SUFFIX = ".csv"
|
||||||
|
DOCX_MIMETYPE = (
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
)
|
||||||
|
DOCX_SUFFIX = ".docx"
|
||||||
JSON_MIMETYPE = "application/json"
|
JSON_MIMETYPE = "application/json"
|
||||||
JSON_SUFFIX = ".json"
|
JSON_SUFFIX = ".json"
|
||||||
PDF_MIMETYPE = "application/pdf"
|
PDF_MIMETYPE = "application/pdf"
|
||||||
@ -398,6 +404,7 @@ def get_mime_suffix(format_code: str) -> tuple[str, str]:
|
|||||||
"""
|
"""
|
||||||
d = {
|
d = {
|
||||||
"csv": (CSV_MIMETYPE, CSV_SUFFIX),
|
"csv": (CSV_MIMETYPE, CSV_SUFFIX),
|
||||||
|
"docx": (DOCX_MIMETYPE, DOCX_SUFFIX),
|
||||||
"xls": (XLSX_MIMETYPE, XLSX_SUFFIX),
|
"xls": (XLSX_MIMETYPE, XLSX_SUFFIX),
|
||||||
"xlsx": (XLSX_MIMETYPE, XLSX_SUFFIX),
|
"xlsx": (XLSX_MIMETYPE, XLSX_SUFFIX),
|
||||||
"pdf": (PDF_MIMETYPE, PDF_SUFFIX),
|
"pdf": (PDF_MIMETYPE, PDF_SUFFIX),
|
||||||
@ -740,6 +747,18 @@ def send_file(data, filename="", suffix="", mime=None, attached=None):
|
|||||||
return response
|
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():
|
def get_request_args():
|
||||||
"""returns a dict with request (POST or GET) arguments
|
"""returns a dict with request (POST or GET) arguments
|
||||||
converted to suit legacy Zope style (scodoc7) functions.
|
converted to suit legacy Zope style (scodoc7) functions.
|
||||||
|
Loading…
Reference in New Issue
Block a user