temp. le 19/11 12h
@ -33,6 +33,7 @@ from app.scodoc.sco_exceptions import (
|
||||
)
|
||||
from config import DevConfig
|
||||
import sco_version
|
||||
from flask_debugtoolbar import DebugToolbarExtension
|
||||
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate(compare_type=True)
|
||||
@ -187,6 +188,7 @@ def create_app(config_class=DevConfig):
|
||||
moment.init_app(app)
|
||||
cache.init_app(app)
|
||||
sco_cache.CACHE = cache
|
||||
toolbar = DebugToolbarExtension(app)
|
||||
|
||||
app.register_error_handler(ScoGenError, handle_sco_value_error)
|
||||
app.register_error_handler(ScoValueError, handle_sco_value_error)
|
||||
|
181
app/scodoc/sco_config_actions.py
Normal file
@ -0,0 +1,181 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# 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
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Module main: page d'accueil, avec liste des départements
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.scodoc.sco_logos import write_logo, find_logo, delete_logo
|
||||
import app
|
||||
from flask import current_app
|
||||
|
||||
|
||||
class Action:
|
||||
"""Base class for all classes describing an action from from config form."""
|
||||
|
||||
def __init__(self, message, parameters):
|
||||
self.message = message
|
||||
self.parameters = parameters
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters, stream=None):
|
||||
"""Check (from parameters) if some action has to be done and
|
||||
then return list of action (or else return empty list)."""
|
||||
raise NotImplementedError
|
||||
|
||||
def display(self):
|
||||
"""return a str describing the action to be done"""
|
||||
return self.message.format_map(self.parameters)
|
||||
|
||||
def execute(self):
|
||||
"""Executes the action"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
GLOBAL = "_"
|
||||
|
||||
|
||||
class LogoUpdate(Action):
|
||||
"""Action: change a logo
|
||||
dept_id: dept_id or '_',
|
||||
logo_id: logo_id,
|
||||
upload: image file replacement
|
||||
"""
|
||||
|
||||
def __init__(self, parameters):
|
||||
super().__init__(
|
||||
f"Modification du logo {parameters['logo_id']} pour le département {parameters['dept_id']}",
|
||||
parameters,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters):
|
||||
dept_id = parameters["dept_key"]
|
||||
if dept_id == GLOBAL:
|
||||
dept_id = None
|
||||
parameters["dept_id"] = dept_id
|
||||
if parameters["upload"] is not None:
|
||||
return LogoUpdate(parameters)
|
||||
return None
|
||||
|
||||
def execute(self):
|
||||
current_app.logger.info(self.message)
|
||||
write_logo(
|
||||
stream=self.parameters["upload"],
|
||||
dept_id=self.parameters["dept_id"],
|
||||
name=self.parameters["logo_id"],
|
||||
)
|
||||
|
||||
|
||||
class LogoDelete(Action):
|
||||
"""Action: Delete an existing logo
|
||||
dept_id: dept_id or '_',
|
||||
logo_id: logo_id
|
||||
"""
|
||||
|
||||
def __init__(self, parameters):
|
||||
super().__init__(
|
||||
f"Suppression du logo {parameters['logo_id']} pour le département {parameters['dept_id']}.",
|
||||
parameters,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters):
|
||||
parameters["dept_id"] = parameters["dept_key"]
|
||||
if parameters["dept_key"] == GLOBAL:
|
||||
parameters["dept_id"] = None
|
||||
if parameters["do_delete"]:
|
||||
return LogoDelete(parameters)
|
||||
return None
|
||||
|
||||
def execute(self):
|
||||
current_app.logger.info(self.message)
|
||||
delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"])
|
||||
|
||||
|
||||
class LogoInsert(Action):
|
||||
"""Action: add a new logo
|
||||
dept_key: dept_id or '_',
|
||||
logo_id: logo_id,
|
||||
upload: image file replacement
|
||||
"""
|
||||
|
||||
def __init__(self, parameters):
|
||||
super().__init__(
|
||||
f"Ajout du logo {parameters['name']} pour le département {parameters['dept_key']} ({parameters['upload']}).",
|
||||
parameters,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters):
|
||||
if parameters["dept_key"] == GLOBAL:
|
||||
parameters["dept_id"] = None
|
||||
if parameters["upload"] and parameters["name"]:
|
||||
logo = find_logo(
|
||||
logoname=parameters["name"], dept_id=parameters["dept_key"]
|
||||
)
|
||||
if logo is None:
|
||||
return LogoInsert(parameters)
|
||||
return None
|
||||
|
||||
def execute(self):
|
||||
dept_id = self.parameters["dept_key"]
|
||||
if dept_id == GLOBAL:
|
||||
dept_id = None
|
||||
current_app.logger.info(self.message)
|
||||
write_logo(
|
||||
stream=self.parameters["upload"],
|
||||
name=self.parameters["name"],
|
||||
dept_id=dept_id,
|
||||
)
|
||||
|
||||
|
||||
class BonusSportUpdate(Action):
|
||||
"""Action: Change bonus_sport_function_name.
|
||||
bonus_sport_function_name: the new value"""
|
||||
|
||||
def __init__(self, parameters):
|
||||
super().__init__(
|
||||
f"Changement du calcul de bonus sport pour ({parameters['bonus_sport_func_name']}).",
|
||||
parameters,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_action(parameters):
|
||||
if (
|
||||
parameters["bonus_sport_func_name"]
|
||||
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
||||
):
|
||||
return [BonusSportUpdate(parameters)]
|
||||
return []
|
||||
|
||||
def execute(self):
|
||||
current_app.logger.info(self.message)
|
||||
ScoDocSiteConfig.set_bonus_sport_func(self.parameters["bonus_sport_func_name"])
|
||||
app.clear_scodoc_cache()
|
402
app/scodoc/sco_config_form.py
Normal file
@ -0,0 +1,402 @@
|
||||
# -*- mode: python -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# ScoDoc
|
||||
#
|
||||
# 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
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
Module main: page d'accueil, avec liste des départements
|
||||
|
||||
Emmanuel Viennet, 2021
|
||||
"""
|
||||
import re
|
||||
|
||||
from flask import flash, url_for, redirect, render_template
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed
|
||||
from wtforms import SelectField, SubmitField, FormField, validators, FieldList
|
||||
from wtforms.fields.simple import BooleanField, StringField, HiddenField
|
||||
|
||||
from app import AccessDenied
|
||||
from app.models import Departement
|
||||
from app.models import ScoDocSiteConfig
|
||||
from app.scodoc import sco_logos, html_sco_header
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.scodoc.sco_config_actions import (
|
||||
LogoDelete,
|
||||
LogoUpdate,
|
||||
LogoInsert,
|
||||
BonusSportUpdate,
|
||||
)
|
||||
|
||||
from flask_login import current_user
|
||||
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
|
||||
JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + []
|
||||
|
||||
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
|
||||
|
||||
# class ItemForm(FlaskForm):
|
||||
# """Unused Generic class to document common behavior for classes
|
||||
# * ScoConfigurationForm
|
||||
# * DeptForm
|
||||
# * LogoForm
|
||||
# Some or all of these implements:
|
||||
# * Composite design pattern (ScoConfigurationForm and DeptForm)
|
||||
# - a FieldList(FormField(ItemForm))
|
||||
# - FieldListItem are created by browsing the model
|
||||
# - index dictionnary to provide direct access to a SubItemForm
|
||||
# - the direct access method (get_form)
|
||||
# * have some information added to be displayed
|
||||
# - information are collected from a model object
|
||||
# Common methods:
|
||||
# * build(model) (not for LogoForm who has no child)
|
||||
# for each child:
|
||||
# * create en entry in the FieldList for each subitem found
|
||||
# * update self.index
|
||||
# * fill_in additional information into the form
|
||||
# * recursively calls build for each chid
|
||||
# some spécific information may be added after standard processing
|
||||
# (typically header/footer description)
|
||||
# * preview(data)
|
||||
# check the data from a post and build a list of operations that has to be done.
|
||||
# for a two phase process:
|
||||
# * phase 1 (list all opérations)
|
||||
# * phase 2 (may be confirmation and execure)
|
||||
# - if no op found: return to the form with a message 'Aucune modification trouvée'
|
||||
# - only one operation found: execute and go to main page
|
||||
# - more than 1 operation found. asked form confirmation (and execution if confirmed)
|
||||
#
|
||||
# Someday we'll have time to refactor as abstract classes but Abstract FieldList makes this a bit complicated
|
||||
# """
|
||||
|
||||
# Terminology:
|
||||
# dept_id : identifies a dept in modele (= list_logos()). None designates globals logos
|
||||
# dept_key : identifies a dept in this form only (..index[dept_key], and fields 'dept_key').
|
||||
# 'GLOBAL' designates globals logos (we need a string value to set up HiddenField
|
||||
GLOBAL = "_"
|
||||
|
||||
|
||||
def dept_id_to_key(dept_id):
|
||||
if dept_id is None:
|
||||
return GLOBAL
|
||||
return dept_id
|
||||
|
||||
|
||||
def dept_key_to_id(dept_key):
|
||||
if dept_key == GLOBAL:
|
||||
return None
|
||||
return dept_key
|
||||
|
||||
|
||||
class AddLogoForm(FlaskForm):
|
||||
"""Formulaire permettant l'ajout d'un logo (dans un département)"""
|
||||
|
||||
dept_key = HiddenField()
|
||||
name = StringField(
|
||||
label="Nom",
|
||||
validators=[
|
||||
validators.regexp(
|
||||
r"^[a-zA-Z0-9-]*$",
|
||||
re.IGNORECASE,
|
||||
"Ne doit comporter que lettres, chiffres ou -",
|
||||
),
|
||||
validators.Length(
|
||||
max=20, message="Un nom ne doit pas dépasser 20 caractères"
|
||||
),
|
||||
validators.required("Nom de logo requis (alphanumériques ou '-')"),
|
||||
],
|
||||
)
|
||||
upload = FileField(
|
||||
label="Sélectionner l'image",
|
||||
validators=[
|
||||
FileAllowed(
|
||||
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||
f"n'accepte que les fichiers image {', '.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}",
|
||||
),
|
||||
validators.required("Fichier image manquant"),
|
||||
],
|
||||
)
|
||||
do_insert = SubmitField("ajouter une image")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["meta"] = {"csrf": False}
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def validate_name(self, name):
|
||||
dept_id = dept_key_to_id(self.dept_key.data)
|
||||
if dept_id == GLOBAL:
|
||||
dept_id = None
|
||||
if find_logo(logoname=name.data, dept_id=dept_id) is not None:
|
||||
raise validators.ValidationError("Un logo de même nom existe déjà")
|
||||
|
||||
def select_action(self):
|
||||
if self.data["do_insert"]:
|
||||
if self.validate():
|
||||
return LogoInsert.build_action(self.data)
|
||||
return None
|
||||
|
||||
|
||||
class LogoForm(FlaskForm):
|
||||
"""Embed both presentation of a logo (cf. template file configuration.html)
|
||||
and all its data and UI action (change, delete)"""
|
||||
|
||||
dept_key = HiddenField()
|
||||
logo_id = HiddenField()
|
||||
upload = FileField(
|
||||
label="Remplacer l'image",
|
||||
validators=[
|
||||
FileAllowed(
|
||||
scu.LOGOS_IMAGES_ALLOWED_TYPES,
|
||||
f"n'accepte que les fichiers image {', '.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}",
|
||||
)
|
||||
],
|
||||
)
|
||||
do_delete = SubmitField("Supprimer l'image")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["meta"] = {"csrf": False}
|
||||
super().__init__(*args, **kwargs)
|
||||
self.logo = find_logo(
|
||||
logoname=self.logo_id.data, dept_id=dept_key_to_id(self.dept_key.data)
|
||||
).select()
|
||||
self.description = None
|
||||
self.titre = None
|
||||
self.can_delete = True
|
||||
if self.dept_key.data == GLOBAL:
|
||||
if self.logo_id.data == "header":
|
||||
self.can_delete = False
|
||||
self.description = ""
|
||||
self.titre = "Logo en-tête"
|
||||
if self.logo_id.data == "footer":
|
||||
self.can_delete = False
|
||||
self.titre = "Logo pied de page"
|
||||
self.description = ""
|
||||
else:
|
||||
if self.logo_id.data == "header":
|
||||
self.description = "Se substitue au header défini au niveau global"
|
||||
self.titre = "Logo en-tête"
|
||||
if self.logo_id.data == "footer":
|
||||
self.description = "Se substitue au footer défini au niveau global"
|
||||
self.titre = "Logo pied de page"
|
||||
|
||||
def select_action(self):
|
||||
if self.do_delete.data and self.can_delete:
|
||||
return LogoDelete.build_action(self.data)
|
||||
if self.upload.data and self.validate():
|
||||
return LogoUpdate.build_action(self.data)
|
||||
return None
|
||||
|
||||
|
||||
class DeptForm(FlaskForm):
|
||||
dept_key = HiddenField()
|
||||
dept_name = HiddenField()
|
||||
add_logo = FormField(AddLogoForm)
|
||||
logos = FieldList(FormField(LogoForm))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["meta"] = {"csrf": False}
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def is_local(self):
|
||||
if self.dept_key.data == GLOBAL:
|
||||
return None
|
||||
return True
|
||||
|
||||
def select_action(self):
|
||||
action = self.add_logo.form.select_action()
|
||||
if action:
|
||||
return action
|
||||
for logo_entry in self.logos.entries:
|
||||
logo_form = logo_entry.form
|
||||
action = logo_form.select_action()
|
||||
if action:
|
||||
return action
|
||||
return None
|
||||
|
||||
def get_form(self, logoname=None):
|
||||
"""Retourne le formulaire associé à un logo. None si pas trouvé"""
|
||||
if logoname is None: # recherche de département
|
||||
return self
|
||||
return self.index.get(logoname, None)
|
||||
|
||||
|
||||
def _make_dept_id_name():
|
||||
"""Cette section assute que tous les départements sont traités (y compris ceux qu'ont pas de logo au départ)
|
||||
et détermine l'ordre d'affichage des DeptForm (GLOBAL d'abord, puis par ordre alpha de nom de département)
|
||||
-> [ (None, None), (dept_id, dept_name)... ]"""
|
||||
depts = [(None, GLOBAL)]
|
||||
for dept in (
|
||||
Departement.query.filter_by(visible=True).order_by(Departement.acronym).all()
|
||||
):
|
||||
depts.append((dept.id, dept.acronym))
|
||||
return depts
|
||||
|
||||
|
||||
def _ordered_logos(modele):
|
||||
"""sort logoname alphabetically but header and footer moved at start. (since there is no space in logoname)"""
|
||||
|
||||
def sort(name):
|
||||
if name == "header":
|
||||
return " 0"
|
||||
if name == "footer":
|
||||
return " 1"
|
||||
return name
|
||||
|
||||
order = sorted(modele.keys(), key=sort)
|
||||
return order
|
||||
|
||||
|
||||
def _make_dept_data(dept_id, dept_name, modele):
|
||||
dept_key = dept_id_to_key(dept_id)
|
||||
data = {
|
||||
"dept_key": dept_key,
|
||||
"dept_name": dept_name,
|
||||
"add_logo": {"dept_key": dept_key},
|
||||
}
|
||||
logos = []
|
||||
if modele is not None:
|
||||
for name in _ordered_logos(modele):
|
||||
logos.append({"dept_key": dept_key, "logo_id": name})
|
||||
data["logos"] = logos
|
||||
return data
|
||||
|
||||
|
||||
def _make_depts_data(modele):
|
||||
data = []
|
||||
for dept_id, dept_name in _make_dept_id_name():
|
||||
data.append(
|
||||
_make_dept_data(
|
||||
dept_id=dept_id, dept_name=dept_name, modele=modele.get(dept_id, None)
|
||||
)
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def _make_data(bonus_sport, modele):
|
||||
data = {
|
||||
"bonus_sport_func_name": bonus_sport,
|
||||
"depts": _make_depts_data(modele=modele),
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class ScoDocConfigurationForm(FlaskForm):
|
||||
"Panneau de configuration général"
|
||||
bonus_sport_func_name = SelectField(
|
||||
label="Fonction de calcul des bonus sport&culture",
|
||||
choices=[
|
||||
(x, x if x else "Aucune")
|
||||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
||||
],
|
||||
)
|
||||
depts = FieldList(FormField(DeptForm))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# def _set_global_logos_infos(self):
|
||||
# "specific processing for globals items"
|
||||
# global_header = self.get_form(logoname="header")
|
||||
# global_header.description = (
|
||||
# "image placée en haut de certains documents documents PDF."
|
||||
# )
|
||||
# global_header.titre = "Logo en-tête"
|
||||
# global_header.can_delete = False
|
||||
# global_footer = self.get_form(logoname="footer")
|
||||
# global_footer.description = (
|
||||
# "image placée en pied de page de certains documents documents PDF."
|
||||
# )
|
||||
# global_footer.titre = "Logo pied de page"
|
||||
# global_footer.can_delete = False
|
||||
|
||||
# def _build_dept(self, dept_id, dept_name, modele):
|
||||
# dept_key = dept_id or GLOBAL
|
||||
# data = {"dept_key": dept_key}
|
||||
# entry = self.depts.append_entry(data)
|
||||
# entry.form.build(dept_name, modele.get(dept_id, {}))
|
||||
# self.index[str(dept_key)] = entry.form
|
||||
|
||||
# def build(self, modele):
|
||||
# "Build the Form hierachy (DeptForm, LogoForm) and add extra data (from modele)"
|
||||
# # if entries already initialized (POST). keep subforms
|
||||
# self.index = {}
|
||||
# # create entries in FieldList (one entry per dept
|
||||
# for dept_id, dept_name in self.dept_id_name:
|
||||
# self._build_dept(dept_id=dept_id, dept_name=dept_name, modele=modele)
|
||||
# self._set_global_logos_infos()
|
||||
|
||||
def get_form(self, dept_key=GLOBAL, logoname=None):
|
||||
"""Retourne un formulaire:
|
||||
* pour un département (get_form(dept_id)) ou à un logo (get_form(dept_id, logname))
|
||||
* propre à un département (get_form(dept_id, logoname) ou global (get_form(logoname))
|
||||
retourne None si le formulaire cherché ne peut être trouvé
|
||||
"""
|
||||
dept_form = self.index.get(dept_key, None)
|
||||
if dept_form is None: # département non trouvé
|
||||
return None
|
||||
return dept_form.get_form(logoname)
|
||||
|
||||
def select_action(self):
|
||||
if (
|
||||
self.data["bonus_sport_func_name"]
|
||||
!= ScoDocSiteConfig.get_bonus_sport_func_name()
|
||||
):
|
||||
return BonusSportUpdate(self.data)
|
||||
for dept_entry in self.depts:
|
||||
dept_form = dept_entry.form
|
||||
action = dept_form.select_action()
|
||||
if action:
|
||||
return action
|
||||
return None
|
||||
|
||||
|
||||
def configuration():
|
||||
"""Panneau de configuration général"""
|
||||
auth_name = str(current_user)
|
||||
if not current_user.is_administrator():
|
||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||
form = ScoDocConfigurationForm(
|
||||
data=_make_data(
|
||||
bonus_sport=ScoDocSiteConfig.get_bonus_sport_func_name(),
|
||||
modele=sco_logos.list_logos(),
|
||||
)
|
||||
)
|
||||
if form.is_submitted():
|
||||
action = form.select_action()
|
||||
if action:
|
||||
action.execute()
|
||||
flash(action.message)
|
||||
return redirect(
|
||||
url_for(
|
||||
"scodoc.configuration",
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
"configuration.html",
|
||||
scodoc_dept=None,
|
||||
title="Configuration ScoDoc",
|
||||
form=form,
|
||||
)
|
@ -46,7 +46,7 @@ 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
|
||||
GLOBAL = "_" # category for server level logos
|
||||
|
||||
|
||||
def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX):
|
||||
@ -70,6 +70,18 @@ def find_logo(logoname, dept_id=None, strict=False, prefix=scu.LOGO_FILE_PREFIX)
|
||||
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"""
|
||||
@ -79,14 +91,15 @@ def write_logo(stream, name, dept_id=None):
|
||||
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
|
||||
[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 = {GLOBAL: _list_dept_logos()} # logos globaux (header / footer)
|
||||
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.acronym] = _list_dept_logos(dept.id)
|
||||
inventory[dept.id] = _list_dept_logos(dept.id)
|
||||
return inventory
|
||||
|
||||
|
||||
@ -236,7 +249,7 @@ class Logo:
|
||||
"""Retourne l'URL permettant d'obtenir l'image du logo"""
|
||||
return url_for(
|
||||
"scodoc.get_logo",
|
||||
scodoc_dept=self.scodoc_dept_id,
|
||||
dept_id=self.scodoc_dept_id,
|
||||
name=self.logoname,
|
||||
global_if_not_found=False,
|
||||
)
|
||||
@ -245,7 +258,7 @@ class Logo:
|
||||
"""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,
|
||||
dept_id=self.scodoc_dept_id,
|
||||
name=self.logoname,
|
||||
global_if_not_found=False,
|
||||
)
|
||||
@ -254,7 +267,7 @@ class Logo:
|
||||
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">'
|
||||
return f'<logo name="{self.logoname}" width="{self.mm[0]}mm"">'
|
||||
|
||||
|
||||
def guess_image_type(stream) -> str:
|
||||
@ -268,7 +281,6 @@ def guess_image_type(stream) -> str:
|
||||
|
||||
|
||||
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")
|
||||
|
@ -60,7 +60,6 @@ from reportlab.lib.pagesizes import letter, A4, landscape
|
||||
from flask import g
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
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
|
||||
@ -193,6 +192,10 @@ class ScolarsPageTemplate(PageTemplate):
|
||||
preferences=None, # dictionnary with preferences, required
|
||||
):
|
||||
"""Initialise our page template."""
|
||||
from app.scodoc.sco_logos import (
|
||||
find_logo,
|
||||
) # defered import (solve circular dependency ->sco_logo ->scodoc, ->sco_pdf
|
||||
|
||||
self.preferences = preferences
|
||||
self.pagesbookmarks = pagesbookmarks
|
||||
self.pdfmeta_author = author
|
||||
|
@ -2017,10 +2017,10 @@ class BasePreferences(object):
|
||||
H = [
|
||||
html_sco_header.sco_header(page_title="Préférences"),
|
||||
"<h2>Préférences globales pour %s</h2>" % scu.ScoURL(),
|
||||
f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
||||
}">modification des logos du département (pour documents pdf)</a></p>"""
|
||||
if current_user.is_administrator()
|
||||
else "",
|
||||
# f"""<p><a href="{url_for("scolar.config_logos", scodoc_dept=g.scodoc_dept)
|
||||
# }">modification des logos du département (pour documents pdf)</a></p>"""
|
||||
# if current_user.is_administrator()
|
||||
# else "",
|
||||
"""<p class="help">Ces paramètres s'appliquent par défaut à tous les semestres, sauf si ceux-ci définissent des valeurs spécifiques.</p>
|
||||
<p class="msg">Attention: cliquez sur "Enregistrer les modifications" en bas de page pour appliquer vos changements !</p>
|
||||
""",
|
||||
|
@ -867,6 +867,9 @@ div.sco_help {
|
||||
|
||||
span.wtf-field ul.errors li {
|
||||
color: red;
|
||||
}
|
||||
.configuration_logo div.img {
|
||||
|
||||
}
|
||||
.configuration_logo div.img-container {
|
||||
width: 256px;
|
||||
@ -874,6 +877,20 @@ span.wtf-field ul.errors li {
|
||||
.configuration_logo div.img-container img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.configuration_logo div.img-data {
|
||||
vertical-align: top;
|
||||
}
|
||||
.configuration_logo logo-edit titre {
|
||||
background-color:lightblue;
|
||||
}
|
||||
.configuration_logo logo-edit nom {
|
||||
float: left;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.configuration_logo logo-edit description {
|
||||
float:right;
|
||||
vertical-align:baseline;
|
||||
}
|
||||
|
||||
p.indent {
|
||||
padding-left: 2em;
|
||||
|
6
app/static/js/configuration.js
Normal file
@ -0,0 +1,6 @@
|
||||
function submit_form() {
|
||||
$("#configuration_form").submit();
|
||||
}
|
||||
|
||||
$(function () {
|
||||
})
|
81
app/templates/config_dept.html
Normal file
@ -0,0 +1,81 @@
|
||||
{% macro render_field(field) %}
|
||||
<div>
|
||||
<span class="wtf-field">{{ field.label }} :</span>
|
||||
<span class="wtf-field">{{ field()|safe }}
|
||||
{% if field.errors %}
|
||||
<ul class=errors>
|
||||
{% for error in field.errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_logo(logo_form, titre=None) %}
|
||||
{% if titre %}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h3>{{ titre }}</h3>
|
||||
<hr/>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td style="padding-right: 20px; vertical-align: top;">
|
||||
<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 style="vertical-align: top;">
|
||||
{{ logo_form.form.dept_id() }}
|
||||
{{ logo_form.form.logo_id() }}
|
||||
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/>
|
||||
<hr/>
|
||||
Usage: {{ logo_form.logo.get_usage() }}
|
||||
<hr/>
|
||||
<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</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)}}
|
||||
<div class="configuration_logo">
|
||||
<table>
|
||||
{{ render_logo(form.header, 'Logo en-tête') }}
|
||||
{{ render_logo(form.footer, 'Logo pied de page') }}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- <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 %}#}
|
@ -1,10 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% import 'bootstrap/wtf.html' as wtf %}
|
||||
|
||||
{% macro render_field(field) %}
|
||||
{% macro render_field(field, with_label=True) %}
|
||||
<div>
|
||||
<span class="wtf-field">{{ field.label }} :</span>
|
||||
<span class="wtf-field">{{ field()|safe }}
|
||||
{% if with_label %}
|
||||
<span class="wtf-field">{{ field.label }} :</span>
|
||||
{% endif %}
|
||||
<span class="wtf-field">{{ field(**kwargs)|safe }}
|
||||
{% if field.errors %}
|
||||
<ul class=errors>
|
||||
{% for error in field.errors %}
|
||||
@ -16,66 +18,102 @@
|
||||
</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>
|
||||
{% macro render_add_logo(add_logo_form) %}
|
||||
<div class="logo-add">
|
||||
<h3>Ajouter un logo</h3>
|
||||
{{ add_logo_form.hidden_tag() }}
|
||||
{{ render_field(add_logo_form.name) }}
|
||||
{{ render_field(add_logo_form.upload) }}
|
||||
{{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_logo(dept_form, logo_form) %}
|
||||
<div class="logo-edit">
|
||||
{{ logo_form.hidden_tag() }}
|
||||
{% if logo_form.titre %}
|
||||
<tr class="logo-edit">
|
||||
<td colspan="3" class="titre">
|
||||
<div class="nom"><h3>{{ logo_form.titre }}</h3></div>
|
||||
<div class="description">{{ logo_form.description or "" }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="logo-edit">
|
||||
<td colspan="3" class="titre">
|
||||
<span class="nom"><h3>Logo personalisé: {{ logo_form.logo_id.data }}</h3></span>
|
||||
<span class="description">{{ logo_form.description or "" }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td style="padding-right: 20px; ">
|
||||
<div class="img-container">
|
||||
<img src="{{ logo_form.logo.get_url_small() }}" alt="pas de logo chargé" /></div>
|
||||
</td><td class="img-data">
|
||||
<h3>{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})</h3>
|
||||
Taille: {{ logo_form.logo.size }} px
|
||||
{% if logo_form.logo.mm %} / {{ logo_form.logo.mm }} mm {% endif %}<br/>
|
||||
Aspect ratio: {{ logo_form.logo.aspect_ratio }}<br/>
|
||||
Usage: <span style="font-family: system-ui">{{ logo_form.logo.get_usage() }}</span>
|
||||
</td><td class=""img-action">
|
||||
<p>Modifier l'image</p>
|
||||
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
|
||||
{% if logo_form.can_delete %}
|
||||
<p>Supprimer l'image</p>
|
||||
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_logos(dept_form) %}
|
||||
<table>
|
||||
{% for logo_entry in dept_form.logos.entries %}
|
||||
{% set logo_form = logo_entry.form %}
|
||||
{{ render_logo(dept_form, logo_form) }}
|
||||
{% else %}
|
||||
<p class="logo-edit"><h3>Aucun logo défini en propre à ce département</h3></p>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
{% if scodoc_dept %}
|
||||
<h1>Logos du département {{ scodoc_dept }}</h1>
|
||||
{% else %}
|
||||
<h1>Configuration générale</h1>
|
||||
{% endif %}
|
||||
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
|
||||
<script src="/ScoDoc/static/js/configuration.js"></script>
|
||||
|
||||
<form class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
|
||||
<form id="configuration_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)}}
|
||||
<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="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="configuration_logo">
|
||||
<h1>Configuration générale</h1>
|
||||
<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, onChange="submit_form()")}}
|
||||
|
||||
<div class="sco-submit">{{ form.submit() }}</div>
|
||||
<h1>Bibliothèque de logos</h1>
|
||||
{% for dept_entry in form.depts.entries %}
|
||||
{% set dept_form = dept_entry.form %}
|
||||
{{ dept_entry.form.hidden_tag() }}
|
||||
{% if dept_entry.form.is_local() %}
|
||||
<div class="departement">
|
||||
<h2>Département {{ dept_form.dept_name.data }}</h2>
|
||||
<h3>Logos locaux</h3>
|
||||
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br/>
|
||||
Les logos du département se substituent aux logos de même nom définis globalement:</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="departement">
|
||||
<h2>Logos généraux</h2>
|
||||
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements,
|
||||
mais peuvent être redéfinies localement au niveau de chaque département
|
||||
(il suffit de définir un logo local de même nom)</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ render_logos(dept_form) }}
|
||||
{{ render_add_logo(dept_form.add_logo.form) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
@ -46,7 +46,7 @@ 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, FormField, validators, Form
|
||||
from wtforms import SelectField, SubmitField, FormField, validators, Form, FieldList
|
||||
from wtforms.fields import IntegerField
|
||||
from wtforms.fields.simple import BooleanField, StringField, TextAreaField, HiddenField
|
||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||
@ -56,7 +56,7 @@ from app.models import Departement, Identite
|
||||
from app.models import FormSemestre, FormsemestreInscription
|
||||
from app.models import ScoDocSiteConfig
|
||||
import sco_version
|
||||
from app.scodoc import sco_logos
|
||||
from app.scodoc import sco_logos, sco_config_form
|
||||
from app.scodoc import sco_find_etud
|
||||
from app.scodoc import sco_utils as scu
|
||||
from app.decorators import (
|
||||
@ -64,7 +64,9 @@ from app.decorators import (
|
||||
scodoc7func,
|
||||
scodoc,
|
||||
permission_required_compat_scodoc7,
|
||||
permission_required,
|
||||
)
|
||||
from app.scodoc.sco_config_form import configuration
|
||||
from app.scodoc.sco_exceptions import AccessDenied
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
@ -180,64 +182,6 @@ 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"
|
||||
|
||||
bonus_sport_func_name = SelectField(
|
||||
label="Fonction de calcul des bonus sport&culture",
|
||||
choices=[
|
||||
(x, x if x else "Aucune")
|
||||
for x in ScoDocSiteConfig.get_bonus_sport_func_names()
|
||||
],
|
||||
)
|
||||
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
|
||||
# SCOLAR_FONT = "Helvetica"
|
||||
@ -259,29 +203,13 @@ class ScoDocConfigurationForm(FlaskForm):
|
||||
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
|
||||
@admin_required
|
||||
def configuration():
|
||||
"Panneau de configuration général"
|
||||
form = ScoDocConfigurationForm(
|
||||
bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func_name(),
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
ScoDocSiteConfig.set_bonus_sport_func(form.bonus_sport_func_name.data)
|
||||
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"))
|
||||
|
||||
return render_template(
|
||||
"configuration.html",
|
||||
title="Configuration ScoDoc",
|
||||
form=form,
|
||||
scodoc_dept=None,
|
||||
)
|
||||
auth_name = str(current_user)
|
||||
if not current_user.is_administrator():
|
||||
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
||||
return sco_config_form.configuration()
|
||||
|
||||
|
||||
SMALL_SIZE = (300, 300)
|
||||
SMALL_SIZE = (200, 200)
|
||||
|
||||
|
||||
def _return_logo(name="header", dept_id="", small=False, strict: bool = True):
|
||||
|
@ -174,7 +174,7 @@ class DeptLogosConfigurationForm(FlaskForm):
|
||||
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>",
|
||||
f"n'accepte que les fichiers image <tt>{','.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}</tt>",
|
||||
)
|
||||
],
|
||||
)
|
||||
@ -185,7 +185,7 @@ class DeptLogosConfigurationForm(FlaskForm):
|
||||
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>",
|
||||
f"n'accepte que les fichiers image <tt>{','.join(scu.LOGOS_IMAGES_ALLOWED_TYPES)}</tt>",
|
||||
)
|
||||
],
|
||||
)
|
||||
@ -193,36 +193,96 @@ class DeptLogosConfigurationForm(FlaskForm):
|
||||
submit = SubmitField("Enregistrer")
|
||||
|
||||
|
||||
@bp.route("/config_logos", methods=["GET", "POST"])
|
||||
@permission_required(Permission.ScoChangePreferences)
|
||||
def config_logos(scodoc_dept):
|
||||
"Panneau de configuration général"
|
||||
form = DeptLogosConfigurationForm()
|
||||
if form.validate_on_submit():
|
||||
if form.logo_header.data:
|
||||
sco_logos.store_image(
|
||||
form.logo_header.data,
|
||||
os.path.join(
|
||||
scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
|
||||
),
|
||||
)
|
||||
if form.logo_footer.data:
|
||||
sco_logos.store_image(
|
||||
form.logo_footer.data,
|
||||
os.path.join(
|
||||
scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
|
||||
),
|
||||
)
|
||||
app.clear_scodoc_cache()
|
||||
flash(f"Logos enregistrés")
|
||||
return flask.redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
||||
# @bp.route("/config_logos", methods=["GET", "POST"])
|
||||
# @permission_required(Permission.ScoChangePreferences)
|
||||
# def config_logos(scodoc_dept):
|
||||
# "Panneau de configuration général"
|
||||
# form = DeptLogosConfigurationForm()
|
||||
# if form.validate_on_submit():
|
||||
# if form.logo_header.data:
|
||||
# sco_logos.store_image(
|
||||
# form.logo_header.data,
|
||||
# os.path.join(
|
||||
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
|
||||
# ),
|
||||
# )
|
||||
# if form.logo_footer.data:
|
||||
# sco_logos.store_image(
|
||||
# form.logo_footer.data,
|
||||
# os.path.join(
|
||||
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
|
||||
# ),
|
||||
# )
|
||||
# app.clear_scodoc_cache()
|
||||
# flash(f"Logos enregistrés")
|
||||
# return flask.redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
||||
#
|
||||
# return render_template(
|
||||
# "configuration.html",
|
||||
# title="Configuration Logos du département",
|
||||
# form=form,
|
||||
# scodoc_dept=scodoc_dept,
|
||||
# )
|
||||
#
|
||||
#
|
||||
# class DeptLogosConfigurationForm(FlaskForm):
|
||||
# "Panneau de configuration logos dept"
|
||||
#
|
||||
# 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>",
|
||||
# )
|
||||
# ],
|
||||
# )
|
||||
#
|
||||
# submit = SubmitField("Enregistrer")
|
||||
|
||||
return render_template(
|
||||
"configuration.html",
|
||||
title="Configuration Logos du département",
|
||||
form=form,
|
||||
scodoc_dept=scodoc_dept,
|
||||
)
|
||||
|
||||
# @bp.route("/config_logos", methods=["GET", "POST"])
|
||||
# @permission_required(Permission.ScoChangePreferences)
|
||||
# def config_logos(scodoc_dept):
|
||||
# "Panneau de configuration général"
|
||||
# form = DeptLogosConfigurationForm()
|
||||
# if form.validate_on_submit():
|
||||
# if form.logo_header.data:
|
||||
# sco_logos.store_image(
|
||||
# form.logo_header.data,
|
||||
# os.path.join(
|
||||
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_header"
|
||||
# ),
|
||||
# )
|
||||
# if form.logo_footer.data:
|
||||
# sco_logos.store_image(
|
||||
# form.logo_footer.data,
|
||||
# os.path.join(
|
||||
# scu.SCODOC_LOGOS_DIR, "logos_" + scodoc_dept, "logo_footer"
|
||||
# ),
|
||||
# )
|
||||
# app.clear_scodoc_cache()
|
||||
# flash(f"Logos enregistrés")
|
||||
# return flask.redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
|
||||
#
|
||||
# return render_template(
|
||||
# "configuration.html",
|
||||
# title="Configuration Logos du département",
|
||||
# form=form,
|
||||
# scodoc_dept=scodoc_dept,
|
||||
# )
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
tests/ressources/test_logos/logo_A1.jpg
Normal file
After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
tests/ressources/test_logos/logos_2/logo_A1.jpg
Normal file
After Width: | Height: | Size: 3.4 KiB |
@ -7,7 +7,6 @@ Utiliser comme:
|
||||
pytest tests/unit/test_logos.py
|
||||
|
||||
"""
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from shutil import copytree, copy, rmtree
|
||||
|
||||
@ -18,7 +17,14 @@ 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
|
||||
from app.scodoc.sco_logos import (
|
||||
find_logo,
|
||||
Logo,
|
||||
list_logos,
|
||||
GLOBAL,
|
||||
write_logo,
|
||||
delete_logo,
|
||||
)
|
||||
|
||||
RESOURCES_DIR = "/opt/scodoc/tests/ressources/test_logos"
|
||||
|
||||
@ -30,12 +36,15 @@ def create_dept(test_client):
|
||||
"""
|
||||
dept1 = Departement(acronym="RT")
|
||||
dept2 = Departement(acronym="INFO")
|
||||
dept3 = Departement(acronym="GEA")
|
||||
db.session.add(dept1)
|
||||
db.session.add(dept2)
|
||||
db.session.add(dept3)
|
||||
db.session.commit()
|
||||
yield dept1, dept2
|
||||
yield dept1, dept2, dept3
|
||||
db.session.delete(dept1)
|
||||
db.session.delete(dept2)
|
||||
db.session.delete(dept3)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@ -52,9 +61,10 @@ def create_logos(create_dept):
|
||||
+-- logos_{d2} --+-- logo_A.jpg
|
||||
|
||||
"""
|
||||
dept1, dept2 = create_dept
|
||||
dept1, dept2, dept3 = create_dept
|
||||
d1 = dept1.id
|
||||
d2 = dept2.id
|
||||
d3 = dept3.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)
|
||||
@ -83,33 +93,33 @@ def test_select_global_only(create_logos):
|
||||
|
||||
|
||||
def test_select_local_only(create_dept, create_logos):
|
||||
dept1, dept2 = create_dept
|
||||
dept1, dept2, dept3 = 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
|
||||
dept1, dept2, dept3 = 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
|
||||
dept1, dept2, dept3 = 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
|
||||
dept1, dept2, dept3 = 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
|
||||
dept1, dept2, dept3 = create_dept
|
||||
no_logo = app.scodoc.sco_logos.find_logo(
|
||||
logoname="C", dept_id=dept1.id, strict=True
|
||||
)
|
||||
@ -123,8 +133,8 @@ def test_get_jpg_data(create_dept, create_logos):
|
||||
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)
|
||||
assert logo.size == (140, 121)
|
||||
assert logo.mm == approx((5.74, 4.96), 0.1)
|
||||
|
||||
|
||||
def test_get_png_without_data(create_dept, create_logos):
|
||||
@ -139,10 +149,59 @@ def test_get_png_without_data(create_dept, create_logos):
|
||||
assert logo.mm is None
|
||||
|
||||
|
||||
def test_create_globale_jpg_logo(create_dept, create_logos):
|
||||
def test_delete_unique_global_jpg_logo(create_dept, create_logos):
|
||||
from_path = Path(RESOURCES_DIR).joinpath("logo_A.jpg")
|
||||
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath("logo_W.jpg")
|
||||
copy(from_path.absolute(), to_path.absolute())
|
||||
assert to_path.exists()
|
||||
delete_logo(name="W")
|
||||
assert not to_path.exists()
|
||||
|
||||
|
||||
def test_delete_unique_local_jpg_logo(create_dept, create_logos):
|
||||
dept1, dept2, dept3 = create_dept
|
||||
from_path = Path(RESOURCES_DIR).joinpath("logo_A.jpg")
|
||||
to_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_W.jpg")
|
||||
copy(from_path.absolute(), to_path.absolute())
|
||||
assert to_path.exists()
|
||||
delete_logo(name="W", dept_id=dept1.id)
|
||||
assert not to_path.exists()
|
||||
|
||||
|
||||
def test_delete_multiple_local_jpg_logo(create_dept, create_logos):
|
||||
dept1, dept2, dept3 = create_dept
|
||||
from_path_A = Path(RESOURCES_DIR).joinpath("logo_A.jpg")
|
||||
to_path_A = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_V.jpg")
|
||||
from_path_B = Path(RESOURCES_DIR).joinpath("logo_D.png")
|
||||
to_path_B = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_V.png")
|
||||
copy(from_path_A.absolute(), to_path_A.absolute())
|
||||
copy(from_path_B.absolute(), to_path_B.absolute())
|
||||
assert to_path_A.exists()
|
||||
assert to_path_B.exists()
|
||||
delete_logo(name="V", dept_id=dept1.id)
|
||||
assert not to_path_A.exists()
|
||||
assert not to_path_B.exists()
|
||||
|
||||
|
||||
def test_create_global_jpg_logo(create_dept, create_logos):
|
||||
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
|
||||
logo = Logo("X") # create global logo
|
||||
stream = path.open("rb")
|
||||
logo_path = Path(scu.SCODOC_LOGOS_DIR).joinpath("logo_X.jpg")
|
||||
assert not logo_path.exists()
|
||||
write_logo(stream, name="X") # create global logo
|
||||
assert logo_path.exists()
|
||||
logo_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def test_create_locale_jpg_logo(create_dept, create_logos):
|
||||
dept1, dept2, dept3 = create_dept
|
||||
path = Path(f"{RESOURCES_DIR}/logo_C.jpg")
|
||||
stream = path.open("rb")
|
||||
logo_path = Path(scu.SCODOC_LOGOS_DIR).joinpath(f"logos_{dept1.id}", "logo_Y.jpg")
|
||||
assert not logo_path.exists()
|
||||
write_logo(stream, name="Y", dept_id=dept1.id) # create global logo
|
||||
assert logo_path.exists()
|
||||
logo_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def test_create_jpg_instead_of_png_logo(create_dept, create_logos):
|
||||
@ -169,15 +228,17 @@ def test_create_jpg_instead_of_png_logo(create_dept, create_logos):
|
||||
|
||||
def test_list_logo(create_dept, create_logos):
|
||||
# test only existence of copied logos. We assumes that they are OK
|
||||
dept1, dept2 = create_dept
|
||||
dept1, dept2, dept3 = create_dept
|
||||
logos = list_logos()
|
||||
assert logos.keys() == {"_GLOBAL", "RT", "INFO"}
|
||||
assert set(logos.keys()) == {dept1.id, dept2.id, None}
|
||||
assert {"A", "C", "D", "E", "F", "header", "footer"}.issubset(
|
||||
set(logos["_GLOBAL"].keys())
|
||||
set(logos[None].keys())
|
||||
)
|
||||
rt = logos.get("RT", None)
|
||||
rt = logos.get(dept1.id, None)
|
||||
assert rt is not None
|
||||
assert {"A", "B"}.issubset(set(rt.keys()))
|
||||
info = logos.get("INFO", None)
|
||||
info = logos.get(dept2.id, None)
|
||||
assert info is not None
|
||||
assert {"A"}.issubset(set(rt.keys()))
|
||||
gea = logos.get(dept3.id, None)
|
||||
assert gea is None
|
||||
|