ScoDoc/app/scodoc/sco_logos.py

215 lines
7.9 KiB
Python

# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""Gestion des images logos (nouveau ScoDoc 9)
Les logos sont `logo_header.<ext>` et `logo_footer.<ext>`
avec `ext` membre de LOGOS_IMAGES_ALLOWED_TYPES (= jpg, png)
SCODOC_LOGOS_DIR /opt/scodoc-data/config/logos
"""
import imghdr
import os
import re
from pathlib import Path
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 find_logo(logoname, dept_id=None, global_if_not_found=True, 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
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
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(<name>, <dept_id>, ...).read()
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
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.
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 or ""
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:
"guess image type from header in stream"
header = stream.read(512)
stream.seek(0)
fmt = imghdr.what(None, header)
if not fmt:
return None
return fmt if fmt != "jpeg" else "jpg"