From 2f247d047bf6bb7943d2b0d52b2ebec914120646 Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Fri, 5 Nov 2021 09:47:23 +0100 Subject: [PATCH 1/8] refactoring sco_logos.py --- app/scodoc/sco_logos.py | 202 +++++++++++++++++++++++++++++++--------- app/scodoc/sco_utils.py | 6 ++ 2 files changed, 166 insertions(+), 42 deletions(-) diff --git a/app/scodoc/sco_logos.py b/app/scodoc/sco_logos.py index e29b5183..db3b1ff9 100644 --- a/app/scodoc/sco_logos.py +++ b/app/scodoc/sco_logos.py @@ -34,30 +34,173 @@ SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos """ import imghdr import os +import re +from pathlib import Path -from flask import abort, current_app +from flask import abort, current_app, url_for +from werkzeug.utils import secure_filename +from app import Departement, ScoValueError from app.scodoc import sco_utils as scu +import PIL +from PIL import Image as PILImage + +GLOBAL = "_SERVER" # category for server level logos -def get_logo_filename(logo_type: str, scodoc_dept: str) -> str: - """return full filename for this logo, or "" if not found - an existing file with extension. - logo_type: "header" or "footer" - scodoc-dept: acronym +def find_logo(logoname, dept_id=None, global_if_not_found=True, prefix=scu.LOGO_FILE_PREFIX): """ - # Search logos in dept specific dir (/opt/scodoc-data/config/logos/logos_), - # then in config dir /opt/scodoc-data/config/logos/ - for image_dir in ( - scu.SCODOC_LOGOS_DIR + "/logos_" + scodoc_dept, - scu.SCODOC_LOGOS_DIR, # global logos - ): - for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES: - filename = os.path.join(image_dir, f"logo_{logo_type}.{suffix}") - if os.path.isfile(filename) and os.access(filename, os.R_OK): - return filename + "Recherche un logo 'name' existant. + Deux strategies: + si global_if_not_found: + On recherche en local au dept d'abord puis si pas trouvé recherche globale + sinon + reherche uniquement dans le département puis si non trouvé au niveau global + quelquesoit la stratégie, retourne None si pas trouvé + :param logoname: le nom recherche + :param dept_id: l'id du département dans lequel se fait la recherche (None si global) + :param global_if_not_found: stratégie de recherche + :param prefix: le prefix utilisé (parmi scu.LOGO_FILE_PREFIX / scu.BACKGROUND_FILE_PREFIX) + :return: un objet Logo désignant le fichier image trouvé (ou None) + """ + try: + logo = Logo(logoname, dept_id, prefix).read() + except ScoValueError: + logo = None + if logo is None and global_if_not_found: + try: + logo = Logo(logoname=logoname, dept_id=None), prefix.read() + except ScoValueError: + logo = None + return logo - return "" + +def get_logo_filename(name, dept_id=None): + breakpoint() + return find_logo(name, dept_id).read().filepath + + +def get_logo_url(name, dept_id): + return find_logo(name, dept_id).read().get_url() + + +def write_logo(stream, name, dept_id=None): + Logo(logoname=name, dept_id=dept_id).create(stream) + + +def list_logos(): + inventory = {GLOBAL: _list_dept_logos()} # logos globaux (header / footer) + for dept in Departement.query.filter_by(visible=True).all(): + logos_dept = _list_dept_logos(dept_id=dept.id) + if logos_dept: + inventory[dept.acronym] = _list_dept_logos(dept.id) + return inventory + + +def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX): + "Inventorie tous les logos existants (retourne un dictionnaire de dictionnaires [dept_id][logoname]" + allowed_ext = "|".join(scu.LOGOS_IMAGES_ALLOWED_TYPES) + filename_parser = re.compile(f"{prefix}([^.]*).({allowed_ext})") + logos = {} + path_dir = Path(scu.SCODOC_LOGOS_DIR) + if dept_id: + path_dir = Path( + os.path.sep.join([scu.SCODOC_LOGOS_DIR, LOGOS_DIR_PREFIX + str(dept_id)]) + ) + if path_dir.exists(): + for entry in path_dir.iterdir(): + if os.access(path_dir.joinpath(entry).absolute(), os.R_OK): + result = filename_parser.match(entry.name) + if result: + logoname = result.group(1) + logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).read() + return logos if len(logos.keys()) > 0 else None + + +class Logo: + """Responsable des opérations (read, create), du calcul des chemins et url + et de la récupération des informations sur un logp. + Usage: + logo existant: Logo(, , ...).read() + logo en création: Logo(, , ...).create(stream) + Les attributs filename, filepath, get_url() ne devraient pas être utilisés avant les opérations + read ou save (le format n'est pas encore connu à ce moement là) + """ + + def __init__(self, logoname, dept_id=None, prefix=scu.LOGO_FILE_PREFIX): + """Initialisation des noms et département des logos. + Le format est renseigné au moment de la lecture (read) ou de la création (create) de l'objet + """ + self.logoname = secure_filename(logoname) + self.scodoc_dept = dept_id + self.prefix = prefix + self.suffix = None + self.dimensions = None + if self.scodoc_dept: + self.dirpath = os.path.sep.join( + [scu.SCODOC_LOGOS_DIR, scu.LOGOS_DIR_PREFIX + secure_filename(str(dept_id))] + ) + else: + self.dirpath = scu.SCODOC_LOGOS_DIR + self.basepath = os.path.sep.join( + [self.dirpath, self.prefix + secure_filename(self.logoname)] + ) + self.filepath = None + self.filename = None + + def set_format(self, fmt): + self.suffix = fmt + self.filepath = self.basepath + "." + fmt + self.filename = self.logoname + "." + fmt + + def _ensure_directory_exists(self): + "create enclosing directory if necessary" + if not Path(self.dirpath).exists(): + current_app.logger.info(f"sco_logos creating directory %s", self.dirpath) + os.mkdir(self.dirpath) + + def create(self, stream): + img_type = guess_image_type(stream) + if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES: + abort(400, "type d'image invalide") + self.set_format(img_type) + self._ensure_directory_exists() + filename = self.basepath + "." + self.suffix + with open(filename, "wb") as f: + f.write(stream.read()) + current_app.logger.info(f"sco_logos.store_image %s", self.filename) + # erase other formats if they exists + for suffix in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]): + try: + os.unlink(self.basepath + "." + suffix) + except IOError: + pass + + def read(self): + """ + Récupération des données pour un logo existant (sinon -> Exception) + il doit exister un et un seul fichier image parmi les types autorisés + (sinon on considère le premier trouvé) + permet d'affiner le format d'un logo de format inconnu + """ + for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES: + path = Path(self.basepath + "." + suffix) + if path.exists(): + self.set_format(suffix) + with open(self.filepath, "rb") as f: + img = PILImage.open(f) + self.dimensions = img.size + return self + # if no file found, raise exception + raise ScoValueError( + "Logo %s not found for dept %s" % (self.logoname, self.scodoc_dept) + ) + + def get_url(self): + if self.scodoc_dept + return url_for( + "scodoc.logo_custom", scodoc_dept=self.scodoc_dept, name=self.logoname + ) def guess_image_type(stream) -> str: @@ -68,28 +211,3 @@ def guess_image_type(stream) -> str: if not fmt: return None return fmt if fmt != "jpeg" else "jpg" - - -def _ensure_directory_exists(filename): - "create enclosing directory if necessary" - directory = os.path.split(filename)[0] - if not os.path.exists(directory): - current_app.logger.info(f"sco_logos creating directory %s", directory) - os.mkdir(directory) - - -def store_image(stream, basename): - img_type = guess_image_type(stream) - if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES: - abort(400, "type d'image invalide") - filename = basename + "." + img_type - _ensure_directory_exists(filename) - with open(filename, "wb") as f: - f.write(stream.read()) - current_app.logger.info(f"sco_logos.store_image %s", filename) - # erase other formats if they exists - for extension in set(scu.LOGOS_IMAGES_ALLOWED_TYPES) - set([img_type]): - try: - os.unlink(basename + "." + extension) - except IOError: - pass diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 5860ef07..ec85b3da 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -228,7 +228,13 @@ if not os.path.exists(SCO_TMP_DIR) and os.path.exists(Config.SCODOC_VAR_DIR): # ----- Les logos: /opt/scodoc-data/config/logos SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos") LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "jpeg", "png") # remind that PIL does not read pdf +LOGOS_DIR_PREFIX = "logos_" +LOGO_FILE_PREFIX = "logo_" +BACKGROUND_FILE_PREFIX = "" +# forme générale des noms des fichiers logos/background: +# SCODOC_LOGO_DIR/LOGO_FILE_PREFIX. (fichier global) ou +# SCODOC_LOGO_DIR/LOGOS_DIR_PREFIX/LOGO_FILE_PREFIX. (fichier départemental) # ----- Les outils distribués SCO_TOOLS_DIR = os.path.join(Config.SCODOC_DIR, "tools") From ee4cebb5bb7ae869716c03188b88954007204c7d Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Fri, 5 Nov 2021 09:54:28 +0100 Subject: [PATCH 2/8] adapt pe_tools --- app/pe/pe_tools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/pe/pe_tools.py b/app/pe/pe_tools.py index 46e706ee..76b9e46b 100644 --- a/app/pe/pe_tools.py +++ b/app/pe/pe_tools.py @@ -44,6 +44,7 @@ import unicodedata import app.scodoc.sco_utils as scu from app import log +from app.scodoc.sco_logos import find_logo PE_DEBUG = 0 @@ -202,10 +203,10 @@ def add_pe_stuff_to_zip(zipfile, ziproot): # Logos: (add to logos/ directory in zip) logos_names = ["logo_header.jpg", "logo_footer.jpg"] - for f in logos_names: - logo = os.path.join(scu.SCODOC_LOGOS_DIR, f) - if os.path.isfile(logo): - add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + f) + for name in logos_names: + logo = find_logo(logoname=name, dept_id=g.scodoc_dept) + if logo is not None: + add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + logo.filename) # ---------------------------------------------------------------------------------------- From dca2486eccb0394a30aab89fcc1092fc84244dbe Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Fri, 5 Nov 2021 10:26:35 +0100 Subject: [PATCH 3/8] adapt sco_bulletin_pdf.py --- app/scodoc/sco_bulletins_pdf.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 81df5b1e..438ec236 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -68,6 +68,7 @@ from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_etud import sco_version +from app.scodoc.sco_logos import find_logo def pdfassemblebulletins( @@ -141,24 +142,15 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): return text # --- PDF format: # handle logos: - image_dir = scu.SCODOC_LOGOS_DIR + "/logos_" + g.scodoc_dept + "/" - if not os.path.exists(image_dir): - image_dir = scu.SCODOC_LOGOS_DIR + "/" # use global logos - if not os.path.exists(image_dir): - log(f"Warning: missing global logo directory ({image_dir})") - image_dir = None - + breakpoint() + # la protection contre des noms malveillants est assuré par l'utilisation de secure_filename dans la classe Logo text = re.sub( - r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text - ) # remove forbidden src attribute - if image_dir is not None: + r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text) # remove forbidden src attribute + image = find_logo(logoname=text, dept_id=g.scodoc_dept_id) + if image is not None: text = re.sub( r'<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>', - r'' % image_dir, - text, - ) - # nota: le match sur \w*? donne le nom du logo et interdit les .. et autres - # tentatives d'acceder à d'autres fichiers ! + r'' % image.filepath,) # log('field: %s' % (text)) return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars) From 475d7637a168afbc7a75481e22eb48c10d9d03de Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Fri, 5 Nov 2021 10:39:02 +0100 Subject: [PATCH 4/8] adapt sco_pdf ; discard BACKGROUNF_PREFIX_FILE --- app/scodoc/sco_logos.py | 3 ++- app/scodoc/sco_pdf.py | 19 +++++++------------ app/scodoc/sco_utils.py | 1 - 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/app/scodoc/sco_logos.py b/app/scodoc/sco_logos.py index db3b1ff9..982d268d 100644 --- a/app/scodoc/sco_logos.py +++ b/app/scodoc/sco_logos.py @@ -129,11 +129,12 @@ class Logo: def __init__(self, logoname, dept_id=None, prefix=scu.LOGO_FILE_PREFIX): """Initialisation des noms et département des logos. + if prefix = None on recherche simplement une image 'logoname.*' Le format est renseigné au moment de la lecture (read) ou de la création (create) de l'objet """ self.logoname = secure_filename(logoname) self.scodoc_dept = dept_id - self.prefix = prefix + self.prefix = prefix or "" self.suffix = None self.dimensions = None if self.scodoc_dept: diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index 77e6f4e1..856d954f 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -60,6 +60,7 @@ from reportlab.lib.pagesizes import letter, A4, landscape from flask import g import app.scodoc.sco_utils as scu +from app.scodoc.sco_logos import find_logo from app.scodoc.sco_utils import ( CONFIG, SCODOC_LOGOS_DIR, @@ -221,18 +222,12 @@ class ScolarsPageTemplate(PageTemplate): self.logo = None # XXX COPIED from sco_pvpdf, to be refactored (no time now) # Search background in dept specific dir, then in global config dir - for image_dir in ( - SCODOC_LOGOS_DIR + "/logos_" + g.scodoc_dept + "/", - SCODOC_LOGOS_DIR + "/", # global logos - ): - for suffix in LOGOS_IMAGES_ALLOWED_TYPES: - fn = image_dir + "/bul_pdf_background" + "." + suffix - if not self.background_image_filename and os.path.exists(fn): - self.background_image_filename = fn - # Also try to use PV background - fn = image_dir + "/letter_background" + "." + suffix - if not self.background_image_filename and os.path.exists(fn): - self.background_image_filename = fn + logo = find_logo(logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, global_if_not_found=, prefix=None) + if logo is None: + # Also try to use PV background + logo = find_logo(logoname="letter_background", dept_id=g.scodoc_dept_id, global_if_not_found=, prefix=None) + if logo is not None: + self.background_image_filename = logo.filepath def beforeDrawPage(self, canvas, doc): """Draws (optional) background, logo and contribution message on each page. diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index ec85b3da..548820a3 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -230,7 +230,6 @@ SCODOC_LOGOS_DIR = os.path.join(SCODOC_CFG_DIR, "logos") LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "jpeg", "png") # remind that PIL does not read pdf LOGOS_DIR_PREFIX = "logos_" LOGO_FILE_PREFIX = "logo_" -BACKGROUND_FILE_PREFIX = "" # forme générale des noms des fichiers logos/background: # SCODOC_LOGO_DIR/LOGO_FILE_PREFIX. (fichier global) ou From bb9679e0fc1d9aab68f78409b6e13c4707c7fbb6 Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Fri, 5 Nov 2021 10:41:21 +0100 Subject: [PATCH 5/8] use g.scodoc_dept_id and not g.scodoc_dept --- app/pe/pe_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pe/pe_tools.py b/app/pe/pe_tools.py index 76b9e46b..3ab92452 100644 --- a/app/pe/pe_tools.py +++ b/app/pe/pe_tools.py @@ -204,7 +204,7 @@ def add_pe_stuff_to_zip(zipfile, ziproot): # Logos: (add to logos/ directory in zip) logos_names = ["logo_header.jpg", "logo_footer.jpg"] for name in logos_names: - logo = find_logo(logoname=name, dept_id=g.scodoc_dept) + logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id) if logo is not None: add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + logo.filename) From 0d0ba5ae600b23f79b15f6e097c3b4036c051f0d Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Fri, 5 Nov 2021 13:12:41 +0100 Subject: [PATCH 6/8] wip before alpha testing --- app/scodoc/sco_bulletins_pdf.py | 6 ++++-- app/scodoc/sco_logos.py | 22 ++++++++++++++-------- app/scodoc/sco_pdf.py | 10 ++++++---- app/scodoc/sco_pvpdf.py | 31 +++++++++++++++++++++---------- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 438ec236..885b5a53 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -145,12 +145,14 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): breakpoint() # la protection contre des noms malveillants est assuré par l'utilisation de secure_filename dans la classe Logo text = re.sub( - r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text) # remove forbidden src attribute + r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text + ) # remove forbidden src attribute image = find_logo(logoname=text, dept_id=g.scodoc_dept_id) if image is not None: text = re.sub( r'<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>', - r'' % image.filepath,) + r'' % image.filepath, + ) # log('field: %s' % (text)) return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars) diff --git a/app/scodoc/sco_logos.py b/app/scodoc/sco_logos.py index 982d268d..73edabb6 100644 --- a/app/scodoc/sco_logos.py +++ b/app/scodoc/sco_logos.py @@ -48,7 +48,9 @@ from PIL import Image as PILImage GLOBAL = "_SERVER" # category for server level logos -def find_logo(logoname, dept_id=None, global_if_not_found=True, prefix=scu.LOGO_FILE_PREFIX): +def find_logo( + logoname, dept_id=None, global_if_not_found=True, prefix=scu.LOGO_FILE_PREFIX +): """ "Recherche un logo 'name' existant. Deux strategies: @@ -105,7 +107,9 @@ def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX): path_dir = Path(scu.SCODOC_LOGOS_DIR) if dept_id: path_dir = Path( - os.path.sep.join([scu.SCODOC_LOGOS_DIR, LOGOS_DIR_PREFIX + str(dept_id)]) + os.path.sep.join( + [scu.SCODOC_LOGOS_DIR, scu.LOGOS_DIR_PREFIX + str(dept_id)] + ) ) if path_dir.exists(): for entry in path_dir.iterdir(): @@ -133,13 +137,16 @@ class Logo: Le format est renseigné au moment de la lecture (read) ou de la création (create) de l'objet """ self.logoname = secure_filename(logoname) - self.scodoc_dept = dept_id + self.scodoc_dept_id = dept_id self.prefix = prefix or "" self.suffix = None self.dimensions = None - if self.scodoc_dept: + if self.scodoc_dept_id: self.dirpath = os.path.sep.join( - [scu.SCODOC_LOGOS_DIR, scu.LOGOS_DIR_PREFIX + secure_filename(str(dept_id))] + [ + scu.SCODOC_LOGOS_DIR, + scu.LOGOS_DIR_PREFIX + secure_filename(str(dept_id)), + ] ) else: self.dirpath = scu.SCODOC_LOGOS_DIR @@ -194,13 +201,12 @@ class Logo: return self # if no file found, raise exception raise ScoValueError( - "Logo %s not found for dept %s" % (self.logoname, self.scodoc_dept) + "Logo %s not found for dept %s" % (self.logoname, self.scodoc_dept_id) ) def get_url(self): - if self.scodoc_dept return url_for( - "scodoc.logo_custom", scodoc_dept=self.scodoc_dept, name=self.logoname + "scodoc.logo_custom", scodoc_dept=self.scodoc_dept_id, name=self.logoname ) diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index 856d954f..9b0a0b09 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -220,12 +220,14 @@ class ScolarsPageTemplate(PageTemplate): ) PageTemplate.__init__(self, "ScolarsPageTemplate", [content]) self.logo = None - # XXX COPIED from sco_pvpdf, to be refactored (no time now) - # Search background in dept specific dir, then in global config dir - logo = find_logo(logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, global_if_not_found=, prefix=None) + logo = find_logo( + logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=None + ) if logo is None: # Also try to use PV background - logo = find_logo(logoname="letter_background", dept_id=g.scodoc_dept_id, global_if_not_found=, prefix=None) + logo = find_logo( + logoname="letter_background", dept_id=g.scodoc_dept_id, prefix=None + ) if logo is not None: self.background_image_filename = logo.filepath diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py index 41dce9ba..ce9cc583 100644 --- a/app/scodoc/sco_pvpdf.py +++ b/app/scodoc/sco_pvpdf.py @@ -52,6 +52,7 @@ from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_etud import sco_version +from app.scodoc.sco_logos import find_logo from app.scodoc.sco_pdf import PDFLOCK from app.scodoc.sco_pdf import SU @@ -207,24 +208,34 @@ class CourrierIndividuelTemplate(PageTemplate): ): for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES: if template_name == "PVJuryTemplate": - fn = image_dir + "/pvjury_background" + "." + suffix + background = find_logo( + logoname="pvjury_background", + dept_id=g.scodoc_dept_id, + prefix="", + global_if_not_found=True, + ) else: - fn = image_dir + "/letter_background" + "." + suffix - if not self.background_image_filename and os.path.exists(fn): - self.background_image_filename = fn + background = find_logo( + logoname="letter_background", + dept_id=g.scodoc_dept_id, + prefix="", + global_if_not_found=True, + ) + if not self.background_image_filename and background is not None: + self.background_image_filename = background.filepath - fn = image_dir + "/logo_footer" + "." + suffix - if not self.logo_footer and os.path.exists(fn): + footer = find_logo(logoname="footer", dept_id=g.scodoc_dept_id) + if footer is not None: self.logo_footer = Image( - fn, + footer.filepath, height=LOGO_FOOTER_HEIGHT, width=LOGO_FOOTER_WIDTH, ) - fn = image_dir + "/logo_header" + "." + suffix - if not self.logo_header and os.path.exists(fn): + header = find_logo(logoname="header", dept_id=g.scodoc_dept_id) + if header is not None: self.logo_header = Image( - fn, + header.filepath, height=LOGO_HEADER_HEIGHT, width=LOGO_HEADER_WIDTH, ) From 3576847fb8672b4745c46a433c27755623fc88e5 Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Sat, 6 Nov 2021 16:04:20 +0100 Subject: [PATCH 7/8] avant tests --- app/pe/pe_tools.py | 2 +- app/scodoc/sco_bulletins_pdf.py | 1 - app/scodoc/sco_logos.py | 61 ++++++++---------- app/scodoc/sco_pdf.py | 6 +- app/scodoc/sco_pvpdf.py | 63 +++++++++---------- app/templates/configuration.html | 4 +- app/views/scodoc.py | 104 ++++++++++++++++++++++++------- 7 files changed, 138 insertions(+), 103 deletions(-) diff --git a/app/pe/pe_tools.py b/app/pe/pe_tools.py index 3ab92452..99adbedd 100644 --- a/app/pe/pe_tools.py +++ b/app/pe/pe_tools.py @@ -202,7 +202,7 @@ def add_pe_stuff_to_zip(zipfile, ziproot): add_local_file_to_zip(zipfile, ziproot, pathname, "avis/" + filename) # Logos: (add to logos/ directory in zip) - logos_names = ["logo_header.jpg", "logo_footer.jpg"] + logos_names = ["header", "footer"] for name in logos_names: logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id) if logo is not None: diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 885b5a53..e2660545 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -142,7 +142,6 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): return text # --- PDF format: # handle logos: - breakpoint() # la protection contre des noms malveillants est assuré par l'utilisation de secure_filename dans la classe Logo text = re.sub( r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text diff --git a/app/scodoc/sco_logos.py b/app/scodoc/sco_logos.py index 73edabb6..6ad2ec9b 100644 --- a/app/scodoc/sco_logos.py +++ b/app/scodoc/sco_logos.py @@ -48,44 +48,27 @@ from PIL import Image as PILImage GLOBAL = "_SERVER" # category for server level logos -def find_logo( - logoname, dept_id=None, global_if_not_found=True, prefix=scu.LOGO_FILE_PREFIX -): +def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX): """ "Recherche un logo 'name' existant. Deux strategies: - si global_if_not_found: - On recherche en local au dept d'abord puis si pas trouvé recherche globale - sinon + si strict: reherche uniquement dans le département puis si non trouvé au niveau global + sinon + On recherche en local au dept d'abord puis si pas trouvé recherche globale quelquesoit la stratégie, retourne None si pas trouvé :param logoname: le nom recherche :param dept_id: l'id du département dans lequel se fait la recherche (None si global) - :param global_if_not_found: stratégie de recherche + :param strict: stratégie de recherche (strict = False => dept ou global) :param prefix: le prefix utilisé (parmi scu.LOGO_FILE_PREFIX / scu.BACKGROUND_FILE_PREFIX) :return: un objet Logo désignant le fichier image trouvé (ou None) """ - try: - logo = Logo(logoname, dept_id, prefix).read() - except ScoValueError: - logo = None - if logo is None and global_if_not_found: - try: - logo = Logo(logoname=logoname, dept_id=None), prefix.read() - except ScoValueError: - logo = None + logo = Logo(logoname, dept_id, prefix).select() + if logo is None and not strict: + logo = Logo(logoname=logoname, dept_id=None, prefix=prefix).select() return logo -def get_logo_filename(name, dept_id=None): - breakpoint() - return find_logo(name, dept_id).read().filepath - - -def get_logo_url(name, dept_id): - return find_logo(name, dept_id).read().get_url() - - def write_logo(stream, name, dept_id=None): Logo(logoname=name, dept_id=dept_id).create(stream) @@ -117,7 +100,7 @@ def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX): result = filename_parser.match(entry.name) if result: logoname = result.group(1) - logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).read() + logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select() return logos if len(logos.keys()) > 0 else None @@ -156,7 +139,7 @@ class Logo: self.filepath = None self.filename = None - def set_format(self, fmt): + def _set_format(self, fmt): self.suffix = fmt self.filepath = self.basepath + "." + fmt self.filename = self.logoname + "." + fmt @@ -171,7 +154,7 @@ class Logo: img_type = guess_image_type(stream) if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES: abort(400, "type d'image invalide") - self.set_format(img_type) + self._set_format(img_type) self._ensure_directory_exists() filename = self.basepath + "." + self.suffix with open(filename, "wb") as f: @@ -184,7 +167,7 @@ class Logo: except IOError: pass - def read(self): + def select(self): """ Récupération des données pour un logo existant (sinon -> Exception) il doit exister un et un seul fichier image parmi les types autorisés @@ -194,19 +177,27 @@ class Logo: for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES: path = Path(self.basepath + "." + suffix) if path.exists(): - self.set_format(suffix) + self._set_format(suffix) with open(self.filepath, "rb") as f: img = PILImage.open(f) self.dimensions = img.size return self - # if no file found, raise exception - raise ScoValueError( - "Logo %s not found for dept %s" % (self.logoname, self.scodoc_dept_id) - ) + return None def get_url(self): return url_for( - "scodoc.logo_custom", scodoc_dept=self.scodoc_dept_id, name=self.logoname + "scodoc.get_logo", + scodoc_dept=self.scodoc_dept_id, + name=self.logoname, + global_if_not_found=False, + ) + + def get_url_small(self): + return url_for( + "scodoc.get_logo_small", + scodoc_dept=self.scodoc_dept_id, + name=self.logoname, + global_if_not_found=False, ) diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index 9b0a0b09..54ed29b2 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -61,11 +61,7 @@ from flask import g import app.scodoc.sco_utils as scu from app.scodoc.sco_logos import find_logo -from app.scodoc.sco_utils import ( - CONFIG, - SCODOC_LOGOS_DIR, - LOGOS_IMAGES_ALLOWED_TYPES, -) +from app.scodoc.sco_utils import CONFIG from app import log from app.scodoc.sco_exceptions import ScoGenError, ScoValueError import sco_version diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py index ce9cc583..b8664c51 100644 --- a/app/scodoc/sco_pvpdf.py +++ b/app/scodoc/sco_pvpdf.py @@ -202,43 +202,36 @@ class CourrierIndividuelTemplate(PageTemplate): self.logo_footer = None self.logo_header = None # Search logos in dept specific dir, then in global scu.CONFIG dir - for image_dir in ( - scu.SCODOC_LOGOS_DIR + "/logos_" + g.scodoc_dept, - scu.SCODOC_LOGOS_DIR, # global logos - ): - for suffix in scu.LOGOS_IMAGES_ALLOWED_TYPES: - if template_name == "PVJuryTemplate": - background = find_logo( - logoname="pvjury_background", - dept_id=g.scodoc_dept_id, - prefix="", - global_if_not_found=True, - ) - else: - background = find_logo( - logoname="letter_background", - dept_id=g.scodoc_dept_id, - prefix="", - global_if_not_found=True, - ) - if not self.background_image_filename and background is not None: - self.background_image_filename = background.filepath + if template_name == "PVJuryTemplate": + background = find_logo( + logoname="pvjury_background", + dept_id=g.scodoc_dept_id, + prefix="", + ) + else: + background = find_logo( + logoname="letter_background", + dept_id=g.scodoc_dept_id, + prefix="", + ) + if not self.background_image_filename and background is not None: + self.background_image_filename = background.filepath - footer = find_logo(logoname="footer", dept_id=g.scodoc_dept_id) - if footer is not None: - self.logo_footer = Image( - footer.filepath, - height=LOGO_FOOTER_HEIGHT, - width=LOGO_FOOTER_WIDTH, - ) + footer = find_logo(logoname="footer", dept_id=g.scodoc_dept_id) + if footer is not None: + self.logo_footer = Image( + footer.filepath, + height=LOGO_FOOTER_HEIGHT, + width=LOGO_FOOTER_WIDTH, + ) - header = find_logo(logoname="header", dept_id=g.scodoc_dept_id) - if header is not None: - self.logo_header = Image( - header.filepath, - height=LOGO_HEADER_HEIGHT, - width=LOGO_HEADER_WIDTH, - ) + header = find_logo(logoname="header", dept_id=g.scodoc_dept_id) + if header is not None: + self.logo_header = Image( + header.filepath, + height=LOGO_HEADER_HEIGHT, + width=LOGO_HEADER_WIDTH, + ) def beforeDrawPage(self, canvas, doc): """Draws a logo and an contribution message on each page.""" diff --git a/app/templates/configuration.html b/app/templates/configuration.html index 6dcf1c51..c4099c75 100644 --- a/app/templates/configuration.html +++ b/app/templates/configuration.html @@ -36,12 +36,12 @@ diff --git a/app/views/scodoc.py b/app/views/scodoc.py index 1722aca2..4de2baa4 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -30,6 +30,8 @@ Module main: page d'accueil, avec liste des départements Emmanuel Viennet, 2021 """ +import io + from app.auth.models import User import os @@ -38,7 +40,7 @@ from flask import abort, flash, url_for, redirect, render_template, send_file from flask import request from flask.app import Flask import flask_login -from flask_login.utils import login_required +from flask_login.utils import login_required, current_user from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed from werkzeug.exceptions import BadRequest, NotFound @@ -61,10 +63,14 @@ from app.decorators import ( scodoc, permission_required_compat_scodoc7, ) +from app.scodoc.imageresize import ImageScale from app.scodoc.sco_exceptions import AccessDenied +from app.scodoc.sco_logos import Logo from app.scodoc.sco_permissions import Permission from app.views import scodoc_bp as bp +from PIL import Image as PILImage + @bp.route("/") @bp.route("/ScoDoc") @@ -240,13 +246,9 @@ def configuration(): if form.validate_on_submit(): ScoDocSiteConfig.set_bonus_sport_func(form.bonus_sport_func_name.data) if form.logo_header.data: - sco_logos.store_image( - form.logo_header.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_header") - ) + sco_logos.write_logo(stream=form.logo_header.data, name="header") if form.logo_footer.data: - sco_logos.store_image( - form.logo_footer.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_footer") - ) + sco_logos.write_logo(stream=form.logo_footer.data, name="footer") app.clear_scodoc_cache() flash(f"Configuration enregistrée") return redirect(url_for("scodoc.index")) @@ -259,29 +261,83 @@ def configuration(): ) -def _return_logo(logo_type="header", scodoc_dept=""): +SMALL_SIZE = (300, 300) + + +def _return_logo( + name="header", dept_id="", small=False, global_if_not_found: bool = True +): # stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici - filename = sco_logos.get_logo_filename(logo_type, scodoc_dept) - if filename: - extension = os.path.splitext(filename)[1] - return send_file(filename, mimetype=f"image/{extension}") + # génération d'une url + # url = url_for( + # "scodoc.get_logo_small", + # name=name, + # dept_id=dept_id, + # global_if_not_found=global_if_not_found, + # ) + logo = sco_logos.find_logo(name, dept_id, global_if_not_found) + if logo is not None: + suffix = logo.suffix + if small: + with PILImage.open(logo.filepath) as im: + im.thumbnail(SMALL_SIZE) + stream = io.BytesIO() + # on garde le même format (on pourrait plus simplement générer systématiquement du JPEG) + fmt = { # adapt suffix to be compliant with PIL save format + "PNG": "PNG", + "JPG": "JPEG", + "JPEG": "JPEG", + }[suffix.upper()] + im.save(stream, fmt) + stream.seek(0) + return send_file(stream, mimetype=f"image/{fmt}") + else: + return send_file(logo.filepath, mimetype=f"image/{suffix}") else: - return "" + abort(404) -@bp.route("/ScoDoc/logo_header") -@bp.route("/ScoDoc//logo_header") -def logo_header(scodoc_dept=""): - "Image logo header" - # "/opt/scodoc-data/config/logos/logo_header") - return _return_logo(logo_type="header", scodoc_dept=scodoc_dept) +# small version (copy/paste from get_logo +@bp.route("/ScoDoc/logos//small", defaults={"dept_id": None}) +@bp.route("/ScoDoc//logos//small") +@admin_required +def get_logo_small(name: str, dept_id: int): + global_if_not_found = request.args.get("global_if_not_found", "True") + return _return_logo( + name, + dept_id=dept_id, + small=True, + global_if_not_found=global_if_not_found.upper() not in ["0", "FALSE"], + ) -@bp.route("/ScoDoc/logo_footer") -@bp.route("/ScoDoc//logo_footer") -def logo_footer(scodoc_dept=""): - "Image logo footer" - return _return_logo(logo_type="footer", scodoc_dept=scodoc_dept) +@bp.route( + "/ScoDoc/logos/", defaults={"dept_id": None} +) # if dept not specified, take global logo +@bp.route("/ScoDoc//logos/") +@admin_required +def get_logo(name: str, dept_id: int): + global_if_not_found = request.args.get("global_if_not_found", "True") + return _return_logo( + name, + dept_id=dept_id, + small=False, + global_if_not_found=global_if_not_found.upper() not in ["0", "FALSE"], + ) + + +# @bp.route("/ScoDoc/logo_header") +# @bp.route("/ScoDoc//logo_header") +# def logo_header(scodoc_dept=""): +# "Image logo header" +# return _return_logo(name="header", scodoc_dept=scodoc_dept) + + +# @bp.route("/ScoDoc/logo_footer") +# @bp.route("/ScoDoc//logo_footer") +# def logo_footer(scodoc_dept=""): +# "Image logo footer" +# return _return_logo(name="footer", scodoc_dept=scodoc_dept) # essais From 2859b2caa4e6491b0d8a85077d8782418dcb4ce2 Mon Sep 17 00:00:00 2001 From: Place Jean-Marie Date: Sat, 6 Nov 2021 19:22:04 +0100 Subject: [PATCH 8/8] apres tests logos --- app/scodoc/sco_bulletins_pdf.py | 23 ++++++++++++----------- app/scodoc/sco_logos.py | 6 ++++++ app/scodoc/sco_pdf.py | 1 + app/templates/configuration.html | 4 ++-- app/views/scodoc.py | 14 ++++++-------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index e2660545..7a2aea94 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -51,14 +51,13 @@ Chaque semestre peut si nécessaire utiliser un type de bulletin différent. """ import io -import os import re import time import traceback from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate -from flask import g, url_for, request +from flask import g, request import app.scodoc.sco_utils as scu from app import log @@ -68,7 +67,7 @@ from app.scodoc import sco_pdf from app.scodoc import sco_preferences from app.scodoc import sco_etud import sco_version -from app.scodoc.sco_logos import find_logo +from app.scodoc.sco_logos import replace_logo def pdfassemblebulletins( @@ -146,14 +145,16 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): text = re.sub( r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text ) # remove forbidden src attribute - image = find_logo(logoname=text, dept_id=g.scodoc_dept_id) - if image is not None: - text = re.sub( - r'<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>', - r'' % image.filepath, - ) - - # log('field: %s' % (text)) + text = re.sub( + r'<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>', + lambda m: r'' + % ( + m.group(1), + replace_logo(name=m.group(2), dept_id=g.scodoc_dept_id), + m.group(3), + ), + text, + ) # log('field: %s' % (text)) return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars) diff --git a/app/scodoc/sco_logos.py b/app/scodoc/sco_logos.py index 6ad2ec9b..17a787dd 100644 --- a/app/scodoc/sco_logos.py +++ b/app/scodoc/sco_logos.py @@ -73,6 +73,12 @@ def write_logo(stream, name, dept_id=None): Logo(logoname=name, dept_id=dept_id).create(stream) +def replace_logo(name, dept_id): + logo = find_logo(logoname=name, dept_id=dept_id) + if logo is not None: + return logo.filepath + + def list_logos(): inventory = {GLOBAL: _list_dept_logos()} # logos globaux (header / footer) for dept in Departement.query.filter_by(visible=True).all(): diff --git a/app/scodoc/sco_pdf.py b/app/scodoc/sco_pdf.py index 54ed29b2..1f4d481b 100755 --- a/app/scodoc/sco_pdf.py +++ b/app/scodoc/sco_pdf.py @@ -216,6 +216,7 @@ class ScolarsPageTemplate(PageTemplate): ) PageTemplate.__init__(self, "ScolarsPageTemplate", [content]) self.logo = None + breakpoint() logo = find_logo( logoname="bul_pdf_background", dept_id=g.scodoc_dept_id, prefix=None ) diff --git a/app/templates/configuration.html b/app/templates/configuration.html index c4099c75..88f1fd81 100644 --- a/app/templates/configuration.html +++ b/app/templates/configuration.html @@ -36,12 +36,12 @@ diff --git a/app/views/scodoc.py b/app/views/scodoc.py index 4de2baa4..12a6ed03 100644 --- a/app/views/scodoc.py +++ b/app/views/scodoc.py @@ -264,9 +264,7 @@ def configuration(): SMALL_SIZE = (300, 300) -def _return_logo( - name="header", dept_id="", small=False, global_if_not_found: bool = True -): +def _return_logo(name="header", dept_id="", small=False, strict: bool = True): # stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici # génération d'une url # url = url_for( @@ -275,7 +273,7 @@ def _return_logo( # dept_id=dept_id, # global_if_not_found=global_if_not_found, # ) - logo = sco_logos.find_logo(name, dept_id, global_if_not_found) + logo = sco_logos.find_logo(name, dept_id, strict) if logo is not None: suffix = logo.suffix if small: @@ -302,12 +300,12 @@ def _return_logo( @bp.route("/ScoDoc//logos//small") @admin_required def get_logo_small(name: str, dept_id: int): - global_if_not_found = request.args.get("global_if_not_found", "True") + strict = request.args.get("strict", "False") return _return_logo( name, dept_id=dept_id, small=True, - global_if_not_found=global_if_not_found.upper() not in ["0", "FALSE"], + strict=strict.upper() not in ["0", "FALSE"], ) @@ -317,12 +315,12 @@ def get_logo_small(name: str, dept_id: int): @bp.route("/ScoDoc//logos/") @admin_required def get_logo(name: str, dept_id: int): - global_if_not_found = request.args.get("global_if_not_found", "True") + strict = request.args.get("strict", "False") return _return_logo( name, dept_id=dept_id, small=False, - global_if_not_found=global_if_not_found.upper() not in ["0", "FALSE"], + strict=strict.upper() not in ["0", "FALSE"], )