forked from ScoDoc/ScoDoc
Ajout Logo.html() + remplace deprecated imghdr par puremagic
This commit is contained in:
parent
14aa70fdc5
commit
606eaf6d14
@ -33,7 +33,6 @@ avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
|
||||
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
|
||||
"""
|
||||
import glob
|
||||
import imghdr
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@ -41,6 +40,7 @@ from pathlib import Path
|
||||
|
||||
from flask import current_app, url_for
|
||||
from PIL import Image as PILImage
|
||||
import puremagic
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from app import log
|
||||
@ -51,99 +51,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
||||
GLOBAL = "_" # category for server level logos
|
||||
|
||||
|
||||
def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX):
|
||||
"""
|
||||
"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
|
||||
|
||||
|
||||
def delete_logo(name, dept_id=None):
|
||||
"""Delete all files matching logo (dept_id, name) (including all allowed extensions)
|
||||
Args:
|
||||
name: The name of the logo
|
||||
dept_id: the dept_id (if local). Use None to destroy globals logos
|
||||
"""
|
||||
logo = find_logo(logoname=name, dept_id=dept_id)
|
||||
while logo is not None:
|
||||
os.unlink(logo.select().filepath)
|
||||
logo = find_logo(logoname=name, dept_id=dept_id)
|
||||
|
||||
|
||||
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 rename_logo(old_name, new_name, dept_id):
|
||||
logo = find_logo(old_name, dept_id, True)
|
||||
logo.rename(new_name)
|
||||
|
||||
|
||||
def list_logos():
|
||||
"""Crée l'inventaire de tous les logos existants.
|
||||
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
|
||||
[None][name] pour les logos globaux
|
||||
[dept_id][name] pour les logos propres à un département (attention id numérique du dept)
|
||||
Les départements sans logos sont absents du résultat
|
||||
"""
|
||||
inventory = {None: _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.id] = _list_dept_logos(dept.id)
|
||||
return inventory
|
||||
|
||||
|
||||
def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
||||
"""Inventorie 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)
|
||||
# parse filename 'logo_<logoname>.<ext> . be carefull: logoname may include '.'
|
||||
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)[
|
||||
:-1
|
||||
] # retreive logoname from filename (less final dot)
|
||||
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 logo.
|
||||
@ -212,7 +119,7 @@ class Logo:
|
||||
def create(self, stream):
|
||||
img_type = guess_image_type(stream)
|
||||
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
||||
raise ScoValueError("type d'image invalide")
|
||||
raise ScoValueError(f"type d'image invalide ({img_type})")
|
||||
self._set_format(img_type)
|
||||
self._ensure_directory_exists()
|
||||
filename = self.basepath + "." + self.suffix
|
||||
@ -310,14 +217,118 @@ class Logo:
|
||||
)
|
||||
old_path.rename(new_path)
|
||||
|
||||
def html(self) -> str:
|
||||
"élément HTML img affichant ce logo"
|
||||
return f"""<img class="sco_logo" src="{self.get_url()}" alt="Logo {self.logoname}">"""
|
||||
|
||||
|
||||
def find_logo(
|
||||
logoname: str,
|
||||
dept_id: int | None = None,
|
||||
strict: bool = False,
|
||||
prefix: str = scu.LOGO_FILE_PREFIX,
|
||||
) -> Logo | None:
|
||||
"""
|
||||
"Recherche un logo 'name' existant.
|
||||
Deux strategies:
|
||||
si strict:
|
||||
recherche 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
|
||||
|
||||
|
||||
def delete_logo(name, dept_id=None):
|
||||
"""Delete all files matching logo (dept_id, name) (including all allowed extensions)
|
||||
Args:
|
||||
name: The name of the logo
|
||||
dept_id: the dept_id (if local). Use None to destroy globals logos
|
||||
"""
|
||||
logo = find_logo(logoname=name, dept_id=dept_id)
|
||||
while logo is not None:
|
||||
os.unlink(logo.select().filepath)
|
||||
logo = find_logo(logoname=name, dept_id=dept_id)
|
||||
|
||||
|
||||
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 rename_logo(old_name, new_name, dept_id):
|
||||
logo = find_logo(old_name, dept_id, True)
|
||||
logo.rename(new_name)
|
||||
|
||||
|
||||
def list_logos():
|
||||
"""Crée l'inventaire de tous les logos existants.
|
||||
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
|
||||
[None][name] pour les logos globaux
|
||||
[dept_id][name] pour les logos propres à un département (attention id numérique du dept)
|
||||
Les départements sans logos sont absents du résultat
|
||||
"""
|
||||
inventory = {None: _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.id] = _list_dept_logos(dept.id)
|
||||
return inventory
|
||||
|
||||
|
||||
def _list_dept_logos(dept_id=None, prefix=scu.LOGO_FILE_PREFIX):
|
||||
"""Inventorie 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)
|
||||
# parse filename 'logo_<logoname>.<ext> . be carefull: logoname may include '.'
|
||||
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)[
|
||||
:-1
|
||||
] # retreive logoname from filename (less final dot)
|
||||
logos[logoname] = Logo(logoname=logoname, dept_id=dept_id).select()
|
||||
return logos if len(logos.keys()) > 0 else None
|
||||
|
||||
|
||||
def guess_image_type(stream) -> str:
|
||||
"guess image type from header in stream"
|
||||
header = stream.read(512)
|
||||
stream.seek(0)
|
||||
fmt = imghdr.what(None, header)
|
||||
if not fmt:
|
||||
ext = puremagic.from_stream(stream)
|
||||
if not ext or not ext.startswith("."):
|
||||
return None
|
||||
fmt = ext[1:] # remove leading .
|
||||
if fmt == "jfif":
|
||||
fmt = "jpg"
|
||||
return fmt if fmt != "jpeg" else "jpg"
|
||||
|
||||
|
||||
|
@ -59,6 +59,7 @@ Pillow==10.0.0
|
||||
platformdirs==3.10.0
|
||||
pluggy==1.2.0
|
||||
psycopg2==2.9.6
|
||||
puremagic==1.15
|
||||
py==1.11.0
|
||||
pycparser==2.21
|
||||
pydot==1.4.2
|
||||
|
Loading…
Reference in New Issue
Block a user