diff --git a/app/scodoc/sco_trombino.py b/app/scodoc/sco_trombino.py
index e9102fc6..9534cfb2 100644
--- a/app/scodoc/sco_trombino.py
+++ b/app/scodoc/sco_trombino.py
@@ -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("")
H.append(
- '
'
- % groups_infos.groups_query_args
+ f""""""
)
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,31 +498,33 @@ 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"),
- """
- 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
+ f"""
+ 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
+
Ensuite, réunir vos images dans un fichier zip, puis télécharger
simultanément le fichier excel et le fichier zip.
- -
+
-
Obtenir la feuille excel à remplir
-
- """
- % groups_infos.groups_query_args,
+ """,
]
F = html_sco_header.sco_footer()
vals = scu.get_request_args()
diff --git a/app/scodoc/sco_trombino_doc.py b/app/scodoc/sco_trombino_doc.py
new file mode 100644
index 00000000..038832e5
--- /dev/null
+++ b/app/scodoc/sco_trombino_doc.py
@@ -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
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 0a4da781..8cba3a59 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -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.