Ecriture des fonctions d'accés aux logos (et aux images)

This commit is contained in:
Jean-Marie Place 2021-11-07 08:53:44 +01:00
parent 915d4059a7
commit 5e461f7dd6
2 changed files with 180 additions and 42 deletions

View File

@ -34,30 +34,188 @@ 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
from PIL import Image as PILImage
GLOBAL = "_GLOBAL" # 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, strict=False, prefix=scu.LOGO_FILE_PREFIX):
"""
# Search logos in dept specific dir (/opt/scodoc-data/config/logos/logos_<dept>),
# 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 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
quelque soit 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 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)
"""
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
return ""
def write_logo(stream, name, dept_id=None):
"""Crée le fichier logo sur le serveur.
Le suffixe du fichier (parmi LOGO_IMAGES_ALLOWED_TYPES) est déduit du contenu du stream"""
Logo(logoname=name, dept_id=dept_id).create(stream)
def list_logos():
"""Crée l'inventaire de tous les logos existants.
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
[GLOBAL][name] pour les logos globaux
[dept_id][name] pour les logos propres à un département (attention id numérique du dept)
"""
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):
"""nventorie toutes les images existantes pour un niveau (GLOBAL ou un département).
retourne un dictionnaire de Logo [logoname] -> Logo
les noms des fichiers concernés doivent être de la forme: <rep>/<prefix><name>.<suffixe>
<rep> : répertoire de recherche (déduit du dept_id)
<prefix>: le prefix (LOGO_FILE_PREFIX pour les logos)
<suffix>: un des suffixes autorisés
:param dept_id: l'id du departement concerné (si None -> global)
:param prefix: le préfixe utilisé
:return: le résultat de la recherche ou None si aucune image trouvée
"""
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, scu.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).select()
return logos if len(logos.keys()) > 0 else None
class Logo:
"""Responsable des opérations (select, create), du calcul des chemins et url
ainsi que de la récupération des informations sur un logp.
Usage:
logo existant: Logo(<name>, <dept_id>, ...).select() (retourne None si fichier non trouvé)
logo en création: Logo(<name>, <dept_id>, ...).create(stream)
Les attributs filename, filepath, get_url() ne devraient pas être utilisés avant les opérations
select 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.
if prefix = None on recherche simplement une image 'logoname.*'
Le format est renseigné au moment de la lecture (select) ou de la création (create) de l'objet
"""
self.logoname = secure_filename(logoname)
self.scodoc_dept_id = dept_id
self.prefix = prefix or ""
self.suffix = None
self.dimensions = None
if self.scodoc_dept_id:
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 select(self):
"""
Récupération des données pour un logo existant
il doit exister un et un seul fichier image parmi de suffixe/types autorisés
(sinon on prend le premier trouvé)
cette opération 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
return None
def get_url(self):
"""Retourne l'URL permettant d'obtenir l'image du logo"""
return url_for(
"scodoc.get_logo",
scodoc_dept=self.scodoc_dept_id,
name=self.logoname,
global_if_not_found=False,
)
def get_url_small(self):
"""Retourne l'URL permettant d'obtenir l'image du logo sous forme de miniature"""
return url_for(
"scodoc.get_logo_small",
scodoc_dept=self.scodoc_dept_id,
name=self.logoname,
global_if_not_found=False,
)
def guess_image_type(stream) -> str:
@ -68,28 +226,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

View File

@ -283,7 +283,12 @@ 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_"
# forme générale des noms des fichiers logos/background:
# SCODOC_LOGO_DIR/LOGO_FILE_PREFIX<name>.<suffix> (fichier global) ou
# SCODOC_LOGO_DIR/LOGOS_DIR_PREFIX<dept_id>/LOGO_FILE_PREFIX<name>.<suffix> (fichier départemental)
# ----- Les outils distribués
SCO_TOOLS_DIR = os.path.join(Config.SCODOC_DIR, "tools")