This commit is contained in:
Jean-Marie Place 2021-12-06 10:05:32 +01:00
parent e97eca3fb4
commit 25bfcc7ed0
5 changed files with 240 additions and 179 deletions

View File

@ -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)

View File

@ -32,6 +32,7 @@ Emmanuel Viennet, 2021
"""
from app.models import ScoDocSiteConfig
from app.scodoc.sco_logos import write_logo, find_logo, delete_logo
import app
class Action:
@ -42,7 +43,7 @@ class Action:
self.parameters = parameters
@staticmethod
def get_action(parameters, stream=None):
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
@ -68,17 +69,17 @@ class LogoUpdate(Action):
def __init__(self, parameters):
super().__init__(
"Modification du logo {logo_id} pour le département {dept_id} ({upload}).",
f"Modification du logo {parameters['logo_id']} pour le département {parameters['dept_id']}",
parameters,
)
@staticmethod
def get_action(parameters):
def build_action(parameters):
if parameters["dept_id"] == GLOBAL:
parameters["dept_id"] = None
if parameters["upload"] is not None:
return [LogoUpdate(parameters)]
return []
return LogoUpdate(parameters)
return None
def execute(self):
write_logo(
@ -96,17 +97,17 @@ class LogoDelete(Action):
def __init__(self, parameters):
super().__init__(
"Suppression du logo {logo_id} pour le département {dept_id}.",
f"Suppression du logo {parameters['logo_id']} pour le département {parameters['dept_id']}.",
parameters,
)
@staticmethod
def get_action(parameters):
def build_action(parameters):
if parameters["dept_id"] == GLOBAL:
parameters["dept_id"] = None
if parameters["do_delete"]:
return [LogoDelete(parameters)]
return []
return LogoDelete(parameters)
return None
def execute(self):
delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"])
@ -126,15 +127,14 @@ class LogoInsert(Action):
)
@staticmethod
def get_action(parameters):
def build_action(parameters):
if parameters["dept_id"] == GLOBAL:
parameters["dept_id"] = None
if parameters["upload"] and parameters["name"]:
logo = find_logo(logoname=parameters["name"], dept_id=parameters["dept_id"])
if logo is None:
return [LogoInsert(parameters)]
return [] # there already exists a logo with the same name/dept
return []
return LogoInsert(parameters)
return None
def execute(self):
write_logo(
@ -155,7 +155,7 @@ class BonusSportUpdate(Action):
)
@staticmethod
def get_action(parameters):
def build_action(parameters):
if (
parameters["bonus_sport_func_name"]
!= ScoDocSiteConfig.get_bonus_sport_func_name()
@ -165,3 +165,4 @@ class BonusSportUpdate(Action):
def execute(self):
ScoDocSiteConfig.set_bonus_sport_func(self.parameters["bonus_sport_func_name"])
app.clear_scodoc_cache()

View File

@ -38,7 +38,6 @@ from flask_wtf.file import FileField, FileAllowed
from wtforms import SelectField, SubmitField, FormField, validators, FieldList
from wtforms.fields.simple import BooleanField, StringField, HiddenField
import app
from app import AccessDenied
from app.models import Departement
from app.models import ScoDocSiteConfig
@ -53,11 +52,12 @@ from app.scodoc.sco_config_actions import (
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
@ -114,6 +114,7 @@ class AddLogoForm(FlaskForm):
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(
@ -122,23 +123,27 @@ class AddLogoForm(FlaskForm):
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 get_action(self, add_data):
return LogoInsert.get_action(add_data)
def select_action(self, add_data):
if add_data["do_insert"] and self.validate():
return LogoInsert.build_action(add_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_id = HiddenField()
dept_key = HiddenField()
logo_id = HiddenField()
upload = FileField(
label="Remplacer l'image",
@ -149,28 +154,31 @@ class LogoForm(FlaskForm):
)
],
)
do_delete = BooleanField("Supprimer l'image", default=None)
do_delete = SubmitField("Supprimer l'image")
def dept_id(self):
if self.dept_key.data == GLOBAL:
return None
return self.dept_key.data
def __init__(self, *args, **kwargs):
kwargs["meta"] = {"csrf": False}
super().__init__(*args, **kwargs)
self.logo = None
self.logo = find_logo(logoname=self.logo_id.data, dept_id=self.dept_key).select()
self.description = None
self.can_delete = True
def build(self, modele):
self.logo = modele
self.do_delete.data = False
def get_actions(self, logo_data):
actions = []
actions += LogoDelete.get_action(logo_data)
actions += LogoUpdate.get_action(logo_data)
return actions
def select_action(self, logo_data):
if logo_data["logo_id"] and self.can_delete:
return LogoDelete.build_action(logo_data)
if logo_data["upload"] and self.upload.validate(form=self):
return LogoUpdate.build_action(logo_data)
return None
class DeptForm(FlaskForm):
dept_key = HiddenField()
dept_name = HiddenField()
add_logo = FormField(AddLogoForm)
logos = FieldList(FormField(LogoForm))
@ -189,7 +197,7 @@ class DeptForm(FlaskForm):
local_footer.description = "Remplace le footer défini au niveau global"
def is_local(self):
if self.dept_name == GLOBAL:
if self.dept_key.data == GLOBAL:
return None
return True
@ -207,33 +215,38 @@ class DeptForm(FlaskForm):
order = sorted(modele.keys(), key=sort)
return order
def build(self, dept_name, modele):
dept_key = self.dept_key.data
self.dept_name = dept_name
self.index = {}
self.add_logo.form.dept_id.data = dept_key
for logoname in self._ordered_logos(modele):
self._build_logo(dept_key, logoname, modele)
if self.is_local():
self._set_local_logos_infos()
# def build(self, dept_name, modele):
# dept_key = self.dept_key.data
# self.dept_name = dept_name
# self.index = {}
# self.add_logo.form.dept_id.data = dept_key
# for logoname in self._ordered_logos(modele):
# self._build_logo(dept_key, logoname, modele)
# if self.is_local():
# self._set_local_logos_infos()
def _build_logo(self, dept_key, logoname, modele):
entry = self.logos.append_entry(
{
"dept_id": dept_key,
"logo_id": logoname,
}
)
self.index[logoname] = entry.form
entry.form.build(modele[logoname])
# def _build_logo(self, dept_key, logoname, modele):
# entry = self.logos.append_entry(
# {
# "dept_id": dept_key,
# "logo_id": logoname,
# }
# )
# self.index[logoname] = entry.form
# entry.form.build(modele[logoname])
def get_actions(self, dept_data):
actions = []
actions += LogoInsert.get_action(dept_data["add_logo"])
def select_action(self, dept_data):
action = self.add_logo.form.select_action(dept_data["add_logo"])
if action:
return action
if action:
return action
for logo_data in dept_data["logos"]:
logo_form = self.index[logo_data["logo_id"]]
actions += logo_form.get_actions(logo_data)
return actions
action = logo_form.select_action(logo_data)
if action:
return action
return None
def get_form(self, logoname=None):
"""Retourne le formulaire associé à un logo. None si pas trouvé"""
@ -242,6 +255,53 @@ class DeptForm(FlaskForm):
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 _make_dept_data(dept_id, dept_name, modele):
data = {}
dept_key = dept_id or dept_name
data = {
"dept_key": dept_key,
"dept_name": dept_name,
"add_logo": {"dept_id": dept_key},
}
logos = []
if modele is not None:
for name in 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(
@ -252,57 +312,40 @@ class ScoDocConfigurationForm(FlaskForm):
],
)
depts = FieldList(FormField(DeptForm))
submit = SubmitField("Enregistrer")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.index = None
self.dept_id_name = (
None # list a one tuple (dept_id, dept_name) for all departements
)
def _make_dept_id_name(self):
"""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)... ]"""
self.dept_id_name = [(None, GLOBAL)]
for dept in (
Departement.query.filter_by(visible=True)
.order_by(Departement.acronym)
.all()
):
self.dept_id_name.append((dept.id, dept.acronym))
# 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 _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_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)"
self.index = {}
self._make_dept_id_name()
# 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 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:
@ -315,39 +358,40 @@ class ScoDocConfigurationForm(FlaskForm):
return None
return dept_form.get_form(logoname)
def get_actions(self, data):
actions = []
if BonusSportUpdate.get_action(data):
actions.append(BonusSportUpdate(data))
def select_action(self, data):
if BonusSportUpdate.build_action(data) and self.bonus_sport_func_name.validate(
form=self
):
return BonusSportUpdate(data)
for dept_data in data["depts"]:
dept_form = self.index[str(dept_data["dept_key"])]
actions += dept_form.get_actions(dept_data)
return actions
action = dept_form.select_action(dept_data)
if action:
return action
return None
def configuration():
"""Panneau de configuration général"""
form = ScoDocConfigurationForm(
bonus_sport_func_name=ScoDocSiteConfig.get_bonus_sport_func_name(),
auth_name = str(current_user)
if not current_user.is_administrator():
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
data = _make_data(
bonus_sport=ScoDocSiteConfig.get_bonus_sport_func_name(),
modele=sco_logos.list_logos(),
)
modele = sco_logos.list_logos()
form.build(modele)
if form.validate_on_submit():
auth_name = str(current_user)
if not current_user.is_administrator():
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
actions = form.get_actions(form.data)
for action in actions:
breakpoint()
form = ScoDocConfigurationForm(data=data)
if form.is_submitted():
action = form.select_action(form.data)
if action:
action.execute()
# 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("Configuration enregistrée")
return redirect(url_for("scodoc.index"))
flash(action.message)
return redirect(
url_for(
"scodoc.configuration",
)
)
return render_template(
"configuration.html",
scodoc_dept=None,

View File

@ -0,0 +1,6 @@
function submit_form() {
$("#configuration_form").submit();
}
$(function () {
})

View File

@ -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 %}
@ -17,15 +19,16 @@
{% endmacro %}
{% macro render_add_logo(add_logo_form) %}
<div class=""logo-add">
<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) }}
<div>
{{ 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(logo_form) %}
{% macro render_logo(dept_form, logo_form) %}
<div class="logo-edit">
{{ logo_form.hidden_tag() }}
{% if logo_form.titre %}
@ -38,28 +41,30 @@
{% else %}
<tr class="logo-edit">
<td colspan="2" class=""titre">
<span class=""nom"{{ logo_form.logo.logoname }}</span>
<span class="nom">{{ logo_form.logo_id.data }}</span>
<span class="description">{{ logo_form.description or "" }}</span>
</td>
</tr>
{% endif %}
<tr>
<td style="padding-right: 20px; vertical-align: top;">
<td style="padding-right: 20px; ">
<div class="img-container">
<img src="{{ logo_form.logo.get_url_small() }}" alt="pas de logo chargé" /></div>
<p class="help">
<br/> Image actuelle</p>
</td>
<td class="img-data">
{{ logo_form.dept_id() }}
{{ logo_form.logo_id() }}
<h3>{{ logo_form.logo.logoname }} (Format: {{ logo_form.logo.suffix }})</h3>h3>
Taille: {{ logo_form.logo.size }} px
{% if logo_form.logo.mm %} &nbsp; / &nbsp; {{ 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>
<span class="wtf-field">{{ render_field(logo_form.upload) }}</span>
{% if logo_form.can_delete %}{{ render_field(logo_form.do_delete) }}{% endif %}
<img src="{{ logo_form.logo().get_url_small() }}" alt="pas de logo chargé" /></div>
</td><td class="img-data">
{{ logo_form.dept_id.data }}
{{ logo_form.logo_id.data }}
<h3>{{ logo_form.logo().logoname }} (Format: {{ logo_form.logo().suffix }})</h3>
Taille: {{ logo_form.logo().size }} px
{% if logo_form.logo().mm %} &nbsp; / &nbsp; {{ 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) }}</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>
@ -67,47 +72,50 @@
{% macro render_logos(dept_form) %}
<table>
{% for logoform in dept_form.index.values() %}
{{ render_logo(logoform) }}
{% 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">Aucun logo défini en propre à ce département</p>
<p class="logo-edit"><h2>Aucun logo défini en propre à ce département</h2></p>
{% endfor %}
</table>
{% endmacro %}
{% block app_content %}
<form class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
<script src="/ScoDoc/static/jQuery/jquery.js"></script>
<script src="/ScoDoc/static/js/configuration.js"></script>
<form id="configuration_form" class="sco-form" action="" method="post" enctype="multipart/form-data" novalidate>
{{ form.hidden_tag() }}
<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)}}
<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()")}}
<h1>Bibliothèque de logos</h1>
{% for dept, dept_form in form.index.items() %}
{{ dept_form.hidden_tag() }}
{% if dept_form.is_local() %}
<div class=""departement">
<h2>Logos du département {{ dept_form.dept_name }}</h2>
<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>
{{ render_add_logo(dept_form.add_logo.form) }}
{{ render_logos(dept_form) }}
</div>
{% else %}
<div class=""departement">
<h2>Logos généraux</h2>
<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_names.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>
{{ render_add_logo(dept_form.add_logo.form) }}
{{ render_logos(dept_form) }}
</div>
{% endif %}
{% endfor %}
</div>
<div class="sco-submit">{{ form.submit() }}</div>
</div>
{% endif %}
{{ render_logos(dept_form) }}
{{ render_add_logo(dept_form.add_logo.form) }}
{% endfor %}
</div>
</form>
{% endblock %}