Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
c3fde544d2 | |||
fd544c4870 | |||
4f8cace890 | |||
dcf68b7062 | |||
34a83b1026 | |||
f89ea566be | |||
f50a3239e5 | |||
b82f19b68b | |||
61bb46601a | |||
4b57037d2c | |||
f86cf4545b | |||
982ade19e2 |
|
@ -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
|
||||
|
||||
|
@ -201,11 +202,11 @@ 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"]
|
||||
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)
|
||||
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:
|
||||
add_local_file_to_zip(zipfile, ziproot, logo, "avis/logos/" + logo.filename)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------
|
||||
|
|
|
@ -51,23 +51,24 @@ Chaque semestre peut si nécessaire utiliser un type de bulletin différent.
|
|||
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
from pydoc import html
|
||||
|
||||
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
|
||||
from app import log, ScoValueError
|
||||
from app.scodoc import sco_cache
|
||||
from app.scodoc import sco_formsemestre
|
||||
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(
|
||||
|
@ -110,6 +111,17 @@ def pdfassemblebulletins(
|
|||
return data
|
||||
|
||||
|
||||
def replacement_function(match):
|
||||
balise = match.group(1)
|
||||
name = match.group(3)
|
||||
logo = find_logo(logoname=name, dept_id=g.scodoc_dept_id)
|
||||
if logo is not None:
|
||||
return r'<img %s src="%s"%s/>' % (match.group(2), logo.filepath, match.group(4))
|
||||
raise ScoValueError(
|
||||
'balise "%s": logo "%s" introuvable' % (html.escape(balise), html.escape(name))
|
||||
)
|
||||
|
||||
|
||||
def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"):
|
||||
"""Process a field given in preferences, returns
|
||||
- if format = 'pdf': a list of Platypus objects
|
||||
|
@ -141,24 +153,18 @@ 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
|
||||
|
||||
text = re.sub(
|
||||
r"<(\s*)logo(.*?)src\s*=\s*(.*?)>", r"<\1logo\2\3>", text
|
||||
) # remove forbidden src attribute
|
||||
if image_dir is not None:
|
||||
text = re.sub(
|
||||
r'<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>',
|
||||
r'<img\1src="%s/logo_\2.jpg"\3/>' % image_dir,
|
||||
text,
|
||||
)
|
||||
# nota: le match sur \w*? donne le nom du logo et interdit les .. et autres
|
||||
# tentatives d'acceder à d'autres fichiers !
|
||||
text = re.sub(
|
||||
r'(<\s*logo(.*?)name\s*=\s*"(\w*?)"(.*?)/?>)',
|
||||
replacement_function,
|
||||
text,
|
||||
)
|
||||
# nota: le match sur \w*? donne le nom du logo et interdit les .. et autres
|
||||
# tentatives d'acceder à d'autres fichiers !
|
||||
# la protection contre des noms malveillants est aussi assurée par l'utilisation de
|
||||
# secure_filename dans la classe Logo
|
||||
|
||||
# log('field: %s' % (text))
|
||||
return sco_pdf.makeParas(text, style, suppress_empty=suppress_empty_pars)
|
||||
|
|
|
@ -32,32 +32,229 @@ 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
|
||||
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 ""
|
||||
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)]
|
||||
)
|
||||
# next attributes are computer by the select function
|
||||
self.suffix = "Not inited: call the select or create function before access"
|
||||
self.filepath = "Not inited: call the select or create function before access"
|
||||
self.filename = "Not inited: call the select or create function before access"
|
||||
self.size = "Not inited: call the select or create function before access"
|
||||
self.aspect_ratio = (
|
||||
"Not inited: call the select or create function before access"
|
||||
)
|
||||
self.density = "Not inited: call the select or create function before access"
|
||||
self.mm = "Not inited: call the select or create function before access"
|
||||
|
||||
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_info(self, img):
|
||||
"""computes some properties from the real image
|
||||
aspect_ratio assumes that x_density and y_density are equals
|
||||
"""
|
||||
x_size, y_size = img.size
|
||||
self.density = img.info.get("dpi", None)
|
||||
unit = 1
|
||||
if self.density is None: # no dpi found try jfif infos
|
||||
self.density = img.info.get("jfif_density", None)
|
||||
unit = img.info.get("jfif_unit", 0) # 0 = no unit ; 1 = inch ; 2 = mm
|
||||
if self.density is not None:
|
||||
x_density, y_density = self.density
|
||||
if unit != 0:
|
||||
unit2mm = [0, 1 / 0.254, 0.1][unit]
|
||||
x_mm = round(x_size * unit2mm / x_density, 2)
|
||||
y_mm = round(y_size * unit2mm / y_density, 2)
|
||||
self.mm = (x_mm, y_mm)
|
||||
else:
|
||||
self.mm = None
|
||||
else:
|
||||
self.mm = None
|
||||
|
||||
self.size = (x_size, y_size)
|
||||
self.aspect_ratio = round(float(x_size) / y_size, 2)
|
||||
|
||||
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._read_info(img)
|
||||
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 get_usage(self):
|
||||
if self.mm is None:
|
||||
return f'<logo name="{self.logoname}" width="?? mm" height="?? mm">'
|
||||
else:
|
||||
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm" height="{self.mm[1]}mm">'
|
||||
|
||||
|
||||
def guess_image_type(stream) -> str:
|
||||
|
@ -70,26 +267,34 @@ def guess_image_type(stream) -> str:
|
|||
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
|
||||
def make_logo_local(logoname, dept_name):
|
||||
breakpoint()
|
||||
depts = Departement.query.filter_by(acronym=dept_name).all()
|
||||
if len(depts) == 0:
|
||||
print(f"no dept {dept_name} found. aborting")
|
||||
return
|
||||
if len(depts) > 1:
|
||||
print(f"several depts {dept_name} found. aborting")
|
||||
return
|
||||
dept = depts[0]
|
||||
print(f"Move logo {logoname}' from global to {dept.acronym}")
|
||||
old_path_wild = f"/opt/scodoc-data/config/logos/logo_{logoname}.*"
|
||||
new_dir = f"/opt/scodoc-data/config/logos/logos_{dept.id}"
|
||||
logos = glob.glob(old_path_wild)
|
||||
# checks that there is non local already present
|
||||
for logo in logos:
|
||||
filename = os.path.split(logo)[1]
|
||||
new_name = os.path.sep.join([new_dir, filename])
|
||||
if os.path.exists(new_name):
|
||||
print("local version of global logo already exists. aborting")
|
||||
return
|
||||
# create new__dir if necessary
|
||||
if not os.path.exists(new_dir):
|
||||
print(f"- create {new_dir} directory")
|
||||
os.mkdir(new_dir)
|
||||
# move global logo (all suffixes) to local dir note: pre existent file (logo_XXX.*) in local dir does not
|
||||
# prevent operation if there is no conflict with moved files
|
||||
# At this point everything is ok so we can do files manipulation
|
||||
for logo in logos:
|
||||
shutil.move(logo, new_dir)
|
||||
# print(f"moved {n_moves}/{n} etuds")
|
||||
|
|
|
@ -60,11 +60,8 @@ from reportlab.lib.pagesizes import letter, A4, landscape
|
|||
from flask import g
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import (
|
||||
CONFIG,
|
||||
SCODOC_LOGOS_DIR,
|
||||
LOGOS_IMAGES_ALLOWED_TYPES,
|
||||
)
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
from app.scodoc.sco_utils import CONFIG
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||
import sco_version
|
||||
|
@ -219,20 +216,16 @@ 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
|
||||
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, prefix=None
|
||||
)
|
||||
if logo is None:
|
||||
# Also try to use PV background
|
||||
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
|
||||
|
||||
def beforeDrawPage(self, canvas, doc):
|
||||
"""Draws (optional) background, logo and contribution message on each page.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -201,33 +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":
|
||||
fn = image_dir + "/pvjury_background" + "." + suffix
|
||||
else:
|
||||
fn = image_dir + "/letter_background" + "." + suffix
|
||||
if not self.background_image_filename and os.path.exists(fn):
|
||||
self.background_image_filename = fn
|
||||
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
|
||||
|
||||
fn = image_dir + "/logo_footer" + "." + suffix
|
||||
if not self.logo_footer and os.path.exists(fn):
|
||||
self.logo_footer = Image(
|
||||
fn,
|
||||
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,
|
||||
)
|
||||
|
||||
fn = image_dir + "/logo_header" + "." + suffix
|
||||
if not self.logo_header and os.path.exists(fn):
|
||||
self.logo_header = Image(
|
||||
fn,
|
||||
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."""
|
||||
|
|
|
@ -228,7 +228,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")
|
||||
|
|
|
@ -16,38 +16,66 @@
|
|||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_logo(logo_form, titre=None) %}
|
||||
{% if titre %}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h3>{{ titre }}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td style="padding-right: 20px">
|
||||
<p class="help">{{ logo_form.form.description }} Image actuelle:</p>
|
||||
<div class="img-container"><img src="{{ logo_form.logo.get_url_small() }}"
|
||||
alt="pas de logo chargé" /></div>
|
||||
</td>
|
||||
<td>
|
||||
Nom: {{ logo_form.form.logo.logoname }}<br/>
|
||||
{{ logo_form.form.description }}<br/>
|
||||
Format: {{ logo_form.logo.suffix }}<br/>
|
||||
Taille en px: {{ logo_form.logo.size }}<br/>
|
||||
{% if logo_form.logo.mm %}
|
||||
Taile en mm: {{ logo_form.logo.mm }}<br/>
|
||||
{% endif %}
|
||||
Aspect ratio: {{ logo_form.logo.aspect_ratio }}<br/>
|
||||
Usage: {{ logo_form.logo.get_usage() }}
|
||||
<span class="wtf-field">{{ logo_form.action()|safe }}</span>
|
||||
<span class="wtf-field">{{ render_field(logo_form.upload) }}</span>
|
||||
{% if logo_form.can_delete %}
|
||||
{{ render_field(logo_form.do_delete) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
{% if scodoc_dept %}
|
||||
<h1>Logos du département {{ scodoc_dept }}</h1>
|
||||
{% else %}
|
||||
<h1>Configuration générale {{ scodoc_dept }}</h1>
|
||||
<h1>Configuration générale</h1>
|
||||
{% endif %}
|
||||
|
||||
<form class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{% if not scodoc_dept %}
|
||||
<div class="sco_help">Les paramètres donnés ici s'appliquent à tout ScoDoc (tous les départements):</div>
|
||||
|
||||
{{ render_field(form.bonus_sport_func_name)}}
|
||||
{{ render_field(form.bonus_sport_func_name)}}
|
||||
<div class="configuration_logo">
|
||||
<table>
|
||||
{{ form.footer.form.breakpoint(form) }}
|
||||
{{ render_logo(form.header, 'Logo en-tête') }}
|
||||
{{ render_logo(form.footer, 'Logo pied de page') }}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="configuration_logo">
|
||||
<h3>Logo en-tête</h3>
|
||||
<p class="help">image placée en haut de certains documents documents PDF. Image actuelle:</p>
|
||||
<div class="img-container"><img src="{{ url_for('scodoc.logo_header', scodoc_dept=scodoc_dept) }}"
|
||||
alt="pas de logo chargé" /></div>
|
||||
{{ render_field(form.logo_header) }}
|
||||
<h3>Logo pied de page</h3>
|
||||
<p class="help">image placée en pied de page de certains documents documents PDF. Image actuelle:</p>
|
||||
<div class="img-container"><img src="{{ url_for('scodoc.logo_footer', scodoc_dept=g.scodoc_dept) }}"
|
||||
alt="pas de logo chargé" /></div>
|
||||
{{ render_field(form.logo_footer) }}
|
||||
</div>
|
||||
<!-- <div class="sco_help">Les paramètres ci-dessous peuvent être changés dans chaque département
|
||||
(paramétrage).<br />On indique ici les valeurs initiales par défaut:
|
||||
</div> -->
|
||||
|
||||
<div class="sco-submit">{{ form.submit() }}</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -30,6 +30,10 @@ Module main: page d'accueil, avec liste des départements
|
|||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
import io
|
||||
|
||||
import wtforms.validators
|
||||
|
||||
from app.auth.models import User
|
||||
import os
|
||||
|
||||
|
@ -38,13 +42,13 @@ 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
|
||||
from wtforms import SelectField, SubmitField
|
||||
from wtforms import SelectField, SubmitField, FormField, validators, Form
|
||||
from wtforms.fields import IntegerField
|
||||
from wtforms.fields.simple import BooleanField, StringField, TextAreaField
|
||||
from wtforms.fields.simple import BooleanField, StringField, TextAreaField, HiddenField
|
||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||
|
||||
import app
|
||||
|
@ -62,9 +66,12 @@ from app.decorators import (
|
|||
permission_required_compat_scodoc7,
|
||||
)
|
||||
from app.scodoc.sco_exceptions import AccessDenied
|
||||
from app.scodoc.sco_logos import find_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")
|
||||
|
@ -176,6 +183,34 @@ def about(scodoc_dept=None):
|
|||
# ---- CONFIGURATION
|
||||
|
||||
|
||||
class LogoForm(FlaskForm):
|
||||
action = HiddenField("action")
|
||||
upload = FileField(
|
||||
label="Modifier l'image:",
|
||||
validators=[
|
||||
FileAllowed(
|
||||
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||
f"n'accepte que les fichiers image {','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}",
|
||||
)
|
||||
],
|
||||
)
|
||||
do_delete = SubmitField("Supprimer")
|
||||
|
||||
def set_infos(self, logo, description=None, can_delete=None):
|
||||
self.logo = logo
|
||||
self.description = description
|
||||
self.can_delete = can_delete
|
||||
|
||||
def breakpoint(self, form):
|
||||
breakpoint()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LogoForm, self).__init__(*args, **kwargs)
|
||||
self.logo = None
|
||||
self.description = None
|
||||
self.can_delete = None
|
||||
|
||||
|
||||
class ScoDocConfigurationForm(FlaskForm):
|
||||
"Panneau de configuration général"
|
||||
|
||||
|
@ -186,31 +221,24 @@ class ScoDocConfigurationForm(FlaskForm):
|
|||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
||||
],
|
||||
)
|
||||
|
||||
logo_header = FileField(
|
||||
label="Modifier l'image:",
|
||||
description="logo placé en haut des documents PDF",
|
||||
validators=[
|
||||
FileAllowed(
|
||||
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||
f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
logo_footer = FileField(
|
||||
label="Modifier l'image:",
|
||||
description="logo placé en pied des documents PDF",
|
||||
validators=[
|
||||
FileAllowed(
|
||||
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||
f"n'accepte que les fichiers image <tt>{','.join([e for e in scu.LOGOS_IMAGES_ALLOWED_TYPES])}</tt>",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
header = FormField(LogoForm)
|
||||
footer = FormField(LogoForm)
|
||||
submit = SubmitField("Enregistrer")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ScoDocConfigurationForm, self).__init__(*args, **kwargs)
|
||||
breakpoint()
|
||||
self.header.form.set_infos(
|
||||
logo=find_logo("header", dept_id=None).select(),
|
||||
description="image placée en haut de certains documents documents PDF.",
|
||||
can_delete=False,
|
||||
)
|
||||
self.footer.form.set_infos(
|
||||
logo=find_logo("footer", dept_id=None).select(),
|
||||
description="image placée en pied de page de certains documents documents PDF.",
|
||||
can_delete=False,
|
||||
)
|
||||
|
||||
|
||||
# Notes pour variables config: (valeurs par défaut des paramètres de département)
|
||||
# Chaines simples
|
||||
|
@ -239,14 +267,10 @@ 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")
|
||||
)
|
||||
if form.logo_footer.data:
|
||||
sco_logos.store_image(
|
||||
form.logo_footer.data, os.path.join(scu.SCODOC_LOGOS_DIR, "logo_footer")
|
||||
)
|
||||
if form.header.data:
|
||||
sco_logos.write_logo(stream=form.header.data, name="header")
|
||||
if form.footer.data:
|
||||
sco_logos.write_logo(stream=form.footer.data, name="footer")
|
||||
app.clear_scodoc_cache()
|
||||
flash(f"Configuration enregistrée")
|
||||
return redirect(url_for("scodoc.index"))
|
||||
|
@ -259,29 +283,60 @@ def configuration():
|
|||
)
|
||||
|
||||
|
||||
def _return_logo(logo_type="header", scodoc_dept=""):
|
||||
SMALL_SIZE = (300, 300)
|
||||
|
||||
|
||||
def _return_logo(name="header", dept_id="", small=False, strict: 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}")
|
||||
logo = sco_logos.find_logo(name, dept_id, strict)
|
||||
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/<scodoc_dept>/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/<name>/small", defaults={"dept_id": None})
|
||||
@bp.route("/ScoDoc/<int:dept_id>/logos/<name>/small")
|
||||
@admin_required
|
||||
def get_logo_small(name: str, dept_id: int):
|
||||
strict = request.args.get("strict", "False")
|
||||
return _return_logo(
|
||||
name,
|
||||
dept_id=dept_id,
|
||||
small=True,
|
||||
strict=strict.upper() not in ["0", "FALSE"],
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/ScoDoc/logo_footer")
|
||||
@bp.route("/ScoDoc/<scodoc_dept>/logo_footer")
|
||||
def logo_footer(scodoc_dept=""):
|
||||
"Image logo footer"
|
||||
return _return_logo(logo_type="footer", scodoc_dept=scodoc_dept)
|
||||
@bp.route(
|
||||
"/ScoDoc/logos/<name>", defaults={"dept_id": None}
|
||||
) # if dept not specified, take global logo
|
||||
@bp.route("/ScoDoc/<int:dept_id>/logos/<name>")
|
||||
@admin_required
|
||||
def get_logo(name: str, dept_id: int):
|
||||
strict = request.args.get("strict", "False")
|
||||
return _return_logo(
|
||||
name,
|
||||
dept_id=dept_id,
|
||||
small=False,
|
||||
strict=strict.upper() not in ["0", "FALSE"],
|
||||
)
|
||||
|
||||
|
||||
# essais
|
||||
|
|
23
scodoc.py
|
@ -22,6 +22,7 @@ from app import models
|
|||
|
||||
from app.auth.models import User, Role, UserRole
|
||||
from app.models import ScoPreference
|
||||
from app.scodoc.sco_logos import make_logo_local
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.views import notes, scolar, absences
|
||||
import tools
|
||||
|
@ -324,6 +325,28 @@ def migrate_scodoc7_dept_archives(dept: str): # migrate-scodoc7-dept-archives
|
|||
tools.migrate_scodoc7_dept_archives(dept)
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("dept", default="")
|
||||
@with_appcontext
|
||||
def migrate_scodoc7_dept_logos(dept: str = ""): # migrate-scodoc7-dept-logos
|
||||
"""Post-migration: renomme les logos en fonction des id / dept de ScoDoc 9"""
|
||||
tools.migrate_scodoc7_dept_logos(dept)
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("logo", default=None)
|
||||
@click.argument("dept", default=None)
|
||||
@with_appcontext
|
||||
def localize_logo(logo: str = None, dept: str = None): # migrate-scodoc7-dept-logos
|
||||
"""Make local to a dept a global logo (both logo and dept names are mandatory)"""
|
||||
if logo in ["header", "footer"]:
|
||||
print(
|
||||
f"Can't make logo '{logo}' local: add a local version throught configuration form instead"
|
||||
)
|
||||
return
|
||||
make_logo_local(logoname=logo, dept_name=dept)
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
@click.argument("formsemestre_id", type=click.INT)
|
||||
@click.argument("xlsfile", type=click.File("rb"))
|
||||
|
|
BIN
tests/ressources/test_logos/logo_A.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
tests/ressources/test_logos/logo_C.jpg
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
tests/ressources/test_logos/logo_D.png
Normal file
BIN
tests/ressources/test_logos/logo_E.jpg
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
tests/ressources/test_logos/logo_F.jpeg
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tests/ressources/test_logos/logos_1/logo_A.jpg
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
tests/ressources/test_logos/logos_1/logo_B.jpg
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tests/ressources/test_logos/logos_2/logo_A.jpg
Normal file
After Width: | Height: | Size: 2.6 KiB |
183
tests/unit/test_logos.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Test Logos
|
||||
|
||||
|
||||
Utiliser comme:
|
||||
pytest tests/unit/test_logos.py
|
||||
|
||||
"""
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from shutil import copytree, copy, rmtree
|
||||
|
||||
import pytest as pytest
|
||||
from _pytest.python_api import approx
|
||||
|
||||
import app
|
||||
from app import db
|
||||
from app.models import Departement
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_logos import find_logo, Logo, list_logos
|
||||
|
||||
RESOURCES_DIR = "/opt/scodoc/tests/ressources/test_logos"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_dept(test_client):
|
||||
"""Crée 2 départements:
|
||||
return departements object
|
||||
"""
|
||||
dept1 = Departement(acronym="RT")
|
||||
dept2 = Departement(acronym="INFO")
|
||||
db.session.add(dept1)
|
||||
db.session.add(dept2)
|
||||
db.session.commit()
|
||||
yield dept1, dept2
|
||||
db.session.delete(dept1)
|
||||
db.session.delete(dept2)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_logos(create_dept):
|
||||
"""Crée les logos:
|
||||
...logos --+-- logo_A.jpg
|
||||
+-- logo_C.jpg
|
||||
+-- logo_D.png
|
||||
+-- logo_E.jpg
|
||||
+-- logo_F.jpeg
|
||||
+-- logos_{d1} --+-- logo_A.jpg
|
||||
| +-- logo_B.jpg
|
||||
+-- logos_{d2} --+-- logo_A.jpg
|
||||
|
||||
"""
|
||||
dept1, dept2 = create_dept
|
||||
d1 = dept1.id
|
||||
d2 = dept2.id
|
||||
FILE_LIST = ["logo_A.jpg", "logo_C.jpg", "logo_D.png", "logo_E.jpg", "logo_F.jpeg"]
|
||||
for fn in FILE_LIST:
|
||||
from_path = Path(RESOURCES_DIR).joinpath(fn)
|
||||
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(fn)
|
||||
copy(from_path.absolute(), to_path.absolute())
|
||||
copytree(
|
||||
f"{RESOURCES_DIR}/logos_1",
|
||||
f"{scu.SCODOC_LOGOS_DIR}/logos_{d1}",
|
||||
)
|
||||
copytree(
|
||||
f"{RESOURCES_DIR}/logos_2",
|
||||
f"{scu.SCODOC_LOGOS_DIR}/logos_{d2}",
|
||||
)
|
||||
yield None
|
||||
rmtree(f"{scu.SCODOC_LOGOS_DIR}/logos_{d1}")
|
||||
rmtree(f"{scu.SCODOC_LOGOS_DIR}/logos_{d2}")
|
||||
# rm files
|
||||
for fn in FILE_LIST:
|
||||
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(fn)
|
||||
to_path.unlink()
|
||||
|
||||
|
||||
def test_select_global_only(create_logos):
|
||||
C_logo = app.scodoc.sco_logos.find_logo(logoname="C")
|
||||
assert C_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logo_C.jpg"
|
||||
|
||||
|
||||
def test_select_local_only(create_dept, create_logos):
|
||||
dept1, dept2 = create_dept
|
||||
B_logo = app.scodoc.sco_logos.find_logo(logoname="B", dept_id=dept1.id)
|
||||
assert B_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1.id}/logo_B.jpg"
|
||||
|
||||
|
||||
def test_select_local_override_global(create_dept, create_logos):
|
||||
dept1, dept2 = create_dept
|
||||
A1_logo = app.scodoc.sco_logos.find_logo(logoname="A", dept_id=dept1.id)
|
||||
assert A1_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1.id}/logo_A.jpg"
|
||||
|
||||
|
||||
def test_select_global_with_strict(create_dept, create_logos):
|
||||
dept1, dept2 = create_dept
|
||||
A_logo = app.scodoc.sco_logos.find_logo(logoname="A", dept_id=dept1.id, strict=True)
|
||||
assert A_logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logos_{dept1.id}/logo_A.jpg"
|
||||
|
||||
|
||||
def test_looks_for_non_existant_should_give_none(create_dept, create_logos):
|
||||
# search for a local non-existant logo returns None
|
||||
dept1, dept2 = create_dept
|
||||
no_logo = app.scodoc.sco_logos.find_logo(logoname="Z", dept_id=dept1.id)
|
||||
assert no_logo is None
|
||||
|
||||
|
||||
def test_looks_localy_for_a_global_should_give_none(create_dept, create_logos):
|
||||
# search for a local non-existant logo returns None
|
||||
dept1, dept2 = create_dept
|
||||
no_logo = app.scodoc.sco_logos.find_logo(
|
||||
logoname="C", dept_id=dept1.id, strict=True
|
||||
)
|
||||
assert no_logo is None
|
||||
|
||||
|
||||
def test_get_jpg_data(create_dept, create_logos):
|
||||
logo = find_logo("A", dept_id=None)
|
||||
assert logo is not None
|
||||
logo.select()
|
||||
assert logo.logoname == "A"
|
||||
assert logo.suffix == "jpg"
|
||||
assert logo.filename == "A.jpg"
|
||||
assert logo.size == (1200, 600)
|
||||
assert logo.mm == approx((40, 30), 0.1)
|
||||
|
||||
|
||||
def test_get_png_without_data(create_dept, create_logos):
|
||||
logo = find_logo("D", dept_id=None)
|
||||
assert logo is not None
|
||||
logo.select()
|
||||
assert logo.logoname == "D"
|
||||
assert logo.suffix == "png"
|
||||
assert logo.filename == "D.png"
|
||||
assert logo.size == (121, 121)
|
||||
assert logo.density is None
|
||||
assert logo.mm is None
|
||||
|
||||
|
||||
def test_create_globale_jpg_logo(create_dept, create_logos):
|
||||
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
|
||||
logo = Logo("X") # create global logo
|
||||
stream = path.open("rb")
|
||||
|
||||
|
||||
def test_create_jpg_instead_of_png_logo(create_dept, create_logos):
|
||||
# action
|
||||
logo = Logo("D") # create global logo (replace logo_D.png)
|
||||
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
|
||||
stream = path.open("rb")
|
||||
logo.create(stream)
|
||||
# test
|
||||
created = Path(f"{scu.SCODOC_LOGOS_DIR}/logo_D.jpg")
|
||||
removed = Path(f"{scu.SCODOC_LOGOS_DIR}/logo_D.png")
|
||||
# file system check
|
||||
assert created.exists()
|
||||
assert not removed.exists()
|
||||
# logo check
|
||||
logo = find_logo("D")
|
||||
assert logo is not None
|
||||
assert logo.filepath == f"{scu.SCODOC_LOGOS_DIR}/logo_D.jpg" # created.absolute()
|
||||
# restore initial state
|
||||
original = Path(f"{RESOURCES_DIR}/logo_D.png")
|
||||
copy(original, removed)
|
||||
created.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def test_list_logo(create_dept, create_logos):
|
||||
# test only existence of copied logos. We assumes that they are OK
|
||||
dept1, dept2 = create_dept
|
||||
logos = list_logos()
|
||||
assert logos.keys() == {"_GLOBAL", "RT", "INFO"}
|
||||
assert {"A", "C", "D", "E", "F", "header", "footer"}.issubset(
|
||||
set(logos["_GLOBAL"].keys())
|
||||
)
|
||||
rt = logos.get("RT", None)
|
||||
assert rt is not None
|
||||
assert {"A", "B"}.issubset(set(rt.keys()))
|
||||
info = logos.get("INFO", None)
|
||||
assert info is not None
|
||||
assert {"A"}.issubset(set(rt.keys()))
|
|
@ -7,3 +7,4 @@
|
|||
from tools.import_scodoc7_user_db import import_scodoc7_user_db
|
||||
from tools.import_scodoc7_dept import import_scodoc7_dept
|
||||
from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archives
|
||||
from tools.migrate_scodoc7_logos import migrate_scodoc7_dept_logos
|
||||
|
|
|
@ -235,6 +235,9 @@ done
|
|||
# ----- Post-Migration: renomme archives en fonction des nouveaux ids
|
||||
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask migrate-scodoc7-dept-archives)" "$SCODOC_USER" || die "Erreur de la post-migration des archives"
|
||||
|
||||
# ----- Post-Migration: renomme logos en fonction des nouveaux ids
|
||||
su -c "(cd $SCODOC_DIR && source venv/bin/activate && flask migrate-scodoc7-dept-logos)" || die "Erreur de la post-migration des logos"
|
||||
|
||||
|
||||
# --- Si migration "en place", désactive ScoDoc 7
|
||||
if [ "$INPLACE" == 1 ]
|
||||
|
|
53
tools/migrate_scodoc7_logos.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from app.models import Departement
|
||||
|
||||
|
||||
def migrate_scodoc7_dept_logos(dept_name=""):
|
||||
if dept_name:
|
||||
depts = Departement.query.filter_by(acronym=dept_name)
|
||||
else:
|
||||
depts = Departement.query
|
||||
n_dir = 0
|
||||
n_moves = 0
|
||||
n_depts = 0
|
||||
purged_candidates = [] # directory that maybe purged at the end
|
||||
for dept in depts:
|
||||
logos_dir7 = f"/opt/scodoc-data/config/logos/logos_{dept.acronym}"
|
||||
logos_dir9 = f"/opt/scodoc-data/config/logos/logos_{dept.id}"
|
||||
if os.path.exists(logos_dir7):
|
||||
print(f"Migrating {dept.acronym} logos...")
|
||||
purged_candidates.append(logos_dir7)
|
||||
n_depts += 1
|
||||
if not os.path.exists(logos_dir9):
|
||||
# print(f"renaming {logos_dir7} to {logos_dir9}")
|
||||
shutil.move(logos_dir7, logos_dir9)
|
||||
n_dir += 1
|
||||
else:
|
||||
# print(f"merging {logos_dir7} with {logos_dir9}")
|
||||
for logo in glob.glob(f"{logos_dir7}/*"):
|
||||
# print(f"\tmoving {logo}")
|
||||
fn = os.path.split(logo)[1]
|
||||
if not os.path.exists(os.path.sep.join([logos_dir9, fn])):
|
||||
shutil.move(logo, logos_dir9)
|
||||
n_moves += 1
|
||||
n_purged = 0
|
||||
for candidate in purged_candidates:
|
||||
if len(os.listdir(candidate)) == 0:
|
||||
os.rmdir(candidate)
|
||||
n_purged += 1
|
||||
print(f"{n_depts} department(s) scanned")
|
||||
if n_dir:
|
||||
print(f"{n_dir} directory(ies) moved")
|
||||
if n_moves:
|
||||
print(f"{n_moves} file(s) moved")
|
||||
if n_purged:
|
||||
print(f"{n_purged} scodoc7 logo dir(s) removed")
|
||||
if n_dir + n_moves + n_purged == 0:
|
||||
print("nothing done")
|
||||
# print(f"moved {n_moves}/{n} etuds")
|