forked from ScoDoc/ScoDoc
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 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>
|
||||
|
||||
<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()
|
||||
|
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 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.
|
||||
|
Loading…
Reference in New Issue
Block a user