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
|
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
|
||||||
"""
|
"""
|
||||||
import glob
|
import glob
|
||||||
import imghdr
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@ -41,6 +40,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from flask import current_app, url_for
|
from flask import current_app, url_for
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
import puremagic
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from app import log
|
from app import log
|
||||||
@ -51,99 +51,6 @@ from app.scodoc.sco_exceptions import ScoValueError
|
|||||||
GLOBAL = "_" # category for server level logos
|
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:
|
class Logo:
|
||||||
"""Responsable des opérations (select, create), du calcul des chemins et url
|
"""Responsable des opérations (select, create), du calcul des chemins et url
|
||||||
ainsi que de la récupération des informations sur un logo.
|
ainsi que de la récupération des informations sur un logo.
|
||||||
@ -212,7 +119,7 @@ class Logo:
|
|||||||
def create(self, stream):
|
def create(self, stream):
|
||||||
img_type = guess_image_type(stream)
|
img_type = guess_image_type(stream)
|
||||||
if img_type not in scu.LOGOS_IMAGES_ALLOWED_TYPES:
|
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._set_format(img_type)
|
||||||
self._ensure_directory_exists()
|
self._ensure_directory_exists()
|
||||||
filename = self.basepath + "." + self.suffix
|
filename = self.basepath + "." + self.suffix
|
||||||
@ -310,14 +217,118 @@ class Logo:
|
|||||||
)
|
)
|
||||||
old_path.rename(new_path)
|
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:
|
def guess_image_type(stream) -> str:
|
||||||
"guess image type from header in stream"
|
"guess image type from header in stream"
|
||||||
header = stream.read(512)
|
ext = puremagic.from_stream(stream)
|
||||||
stream.seek(0)
|
if not ext or not ext.startswith("."):
|
||||||
fmt = imghdr.what(None, header)
|
|
||||||
if not fmt:
|
|
||||||
return None
|
return None
|
||||||
|
fmt = ext[1:] # remove leading .
|
||||||
|
if fmt == "jfif":
|
||||||
|
fmt = "jpg"
|
||||||
return fmt if fmt != "jpeg" else "jpg"
|
return fmt if fmt != "jpeg" else "jpg"
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ Pillow==10.0.0
|
|||||||
platformdirs==3.10.0
|
platformdirs==3.10.0
|
||||||
pluggy==1.2.0
|
pluggy==1.2.0
|
||||||
psycopg2==2.9.6
|
psycopg2==2.9.6
|
||||||
|
puremagic==1.15
|
||||||
py==1.11.0
|
py==1.11.0
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
pydot==1.4.2
|
pydot==1.4.2
|
||||||
|
Loading…
Reference in New Issue
Block a user