Merge pull request 'logo_config' (#367) from jmplace/ScoDoc-Lille:logo_config into dev93

Reviewed-on: https://scodoc.org/git/ScoDoc/ScoDoc/pulls/367
This commit is contained in:
Emmanuel Viennet 2022-04-19 18:01:05 +02:00
commit bc6c220f70
5 changed files with 153 additions and 58 deletions

View File

@ -41,11 +41,8 @@ from wtforms.fields.simple import StringField, HiddenField
from app.models import Departement from app.models import Departement
from app.scodoc import sco_logos, html_sco_header from app.scodoc import sco_logos, html_sco_header
from app.scodoc import sco_utils as scu from app.scodoc import sco_utils as scu
from app.scodoc.sco_config_actions import (
LogoDelete, from app.scodoc.sco_config_actions import LogoInsert
LogoUpdate,
LogoInsert,
)
from app.scodoc.sco_logos import find_logo from app.scodoc.sco_logos import find_logo
@ -120,6 +117,8 @@ def logo_name_validator(message=None):
class AddLogoForm(FlaskForm): class AddLogoForm(FlaskForm):
"""Formulaire permettant l'ajout d'un logo (dans un département)""" """Formulaire permettant l'ajout d'un logo (dans un département)"""
from app.scodoc.sco_config_actions import LogoInsert
dept_key = HiddenField() dept_key = HiddenField()
name = StringField( name = StringField(
label="Nom", label="Nom",
@ -160,13 +159,13 @@ class AddLogoForm(FlaskForm):
return LogoInsert.build_action(self.data) return LogoInsert.build_action(self.data)
return None return None
def errors(self): def opened(self):
if self.do_insert.data: if self.do_insert.data:
if self.name.errors: if self.name.errors:
return True return "open"
if self.upload.errors: if self.upload.errors:
return True return "open"
return False return ""
class LogoForm(FlaskForm): class LogoForm(FlaskForm):
@ -184,7 +183,18 @@ class LogoForm(FlaskForm):
) )
], ],
) )
do_delete = SubmitField("Supprimer l'image") do_delete = SubmitField("Supprimer")
do_rename = SubmitField("Renommer")
new_name = StringField(
label="Nom",
validators=[
logo_name_validator("Nom de logo invalide (alphanumérique, _)"),
validators.Length(
max=20, message="Un nom ne doit pas dépasser 20 caractères"
),
validators.DataRequired("Nom de logo requis (alphanumériques ou '-')"),
],
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs["meta"] = {"csrf": False} kwargs["meta"] = {"csrf": False}
@ -213,16 +223,24 @@ class LogoForm(FlaskForm):
self.titre = "Logo pied de page" self.titre = "Logo pied de page"
def select_action(self): def select_action(self):
from app.scodoc.sco_config_actions import LogoRename
from app.scodoc.sco_config_actions import LogoUpdate
from app.scodoc.sco_config_actions import LogoDelete
if self.do_delete.data and self.can_delete: if self.do_delete.data and self.can_delete:
return LogoDelete.build_action(self.data) return LogoDelete.build_action(self.data)
if self.upload.data and self.validate(): if self.upload.data and self.validate():
return LogoUpdate.build_action(self.data) return LogoUpdate.build_action(self.data)
if self.do_rename.data and self.validate():
return LogoRename.build_action(self.data)
return None return None
def errors(self): def opened(self):
if self.upload.data and self.upload.errors: if self.upload.data and self.upload.errors:
return True return "open"
return False if self.new_name.data and self.new_name.errors:
return "open"
return ""
class DeptForm(FlaskForm): class DeptForm(FlaskForm):
@ -257,13 +275,22 @@ class DeptForm(FlaskForm):
return self return self
return self.index.get(logoname, None) return self.index.get(logoname, None)
def errors(self): def opened(self):
if self.add_logo.errors(): if self.add_logo.opened():
return True return "open"
for logo_form in self.logos: for logo_form in self.logos:
if logo_form.errors(): if logo_form.opened():
return True return "open"
return False return ""
def count(self):
compte = len(self.logos.entries)
if compte == 0:
return "vide"
elif compte == 1:
return "1 élément"
else:
return f"{compte} éléments"
def _make_dept_id_name(): def _make_dept_id_name():

View File

@ -28,11 +28,10 @@
""" """
""" """
from app.models import ScoDocSiteConfig
from app.scodoc.sco_logos import write_logo, find_logo, delete_logo
import app
from flask import current_app from flask import current_app
from app.scodoc.sco_logos import find_logo
class Action: class Action:
"""Base class for all classes describing an action from from config form.""" """Base class for all classes describing an action from from config form."""
@ -42,9 +41,9 @@ class Action:
self.parameters = parameters self.parameters = parameters
@staticmethod @staticmethod
def build_action(parameters, stream=None): def build_action(parameters):
"""Check (from parameters) if some action has to be done and """Check (from parameters) if some action has to be done and
then return list of action (or else return empty list).""" then return list of action (or else return None)."""
raise NotImplementedError raise NotImplementedError
def display(self): def display(self):
@ -59,6 +58,45 @@ class Action:
GLOBAL = "_" GLOBAL = "_"
class LogoRename(Action):
"""Action: rename a logo
dept_id: dept_id or '-'
logo_id: logo_id (old name)
new_name: new_name
"""
def __init__(self, parameters):
super().__init__(
f"Renommage du logo {parameters['logo_id']} en {parameters['new_name']}",
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["new_name"]:
logo = find_logo(
logoname=parameters["new_name"],
dept_id=parameters["dept_key"],
strict=True,
)
if logo is None:
return LogoRename(parameters)
def execute(self):
from app.scodoc.sco_logos import rename_logo
current_app.logger.info(self.message)
rename_logo(
old_name=self.parameters["logo_id"],
new_name=self.parameters["new_name"],
dept_id=self.parameters["dept_id"],
)
class LogoUpdate(Action): class LogoUpdate(Action):
"""Action: change a logo """Action: change a logo
dept_id: dept_id or '_', dept_id: dept_id or '_',
@ -83,6 +121,8 @@ class LogoUpdate(Action):
return None return None
def execute(self): def execute(self):
from app.scodoc.sco_logos import write_logo
current_app.logger.info(self.message) current_app.logger.info(self.message)
write_logo( write_logo(
stream=self.parameters["upload"], stream=self.parameters["upload"],
@ -113,6 +153,8 @@ class LogoDelete(Action):
return None return None
def execute(self): def execute(self):
from app.scodoc.sco_logos import delete_logo
current_app.logger.info(self.message) current_app.logger.info(self.message)
delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"]) delete_logo(name=self.parameters["logo_id"], dept_id=self.parameters["dept_id"])
@ -143,6 +185,8 @@ class LogoInsert(Action):
return None return None
def execute(self): def execute(self):
from app.scodoc.sco_logos import write_logo
dept_id = self.parameters["dept_key"] dept_id = self.parameters["dept_key"]
if dept_id == GLOBAL: if dept_id == GLOBAL:
dept_id = None dept_id = None

View File

@ -89,6 +89,11 @@ def write_logo(stream, name, dept_id=None):
Logo(logoname=name, dept_id=dept_id).create(stream) Logo(logoname=name, dept_id=dept_id).create(stream)
def rename_logo(old_name, new_name, dept_id):
logo = find_logo(old_name, dept_id, True)
logo.rename(new_name)
def list_logos(): def list_logos():
"""Crée l'inventaire de tous les logos existants. """Crée l'inventaire de tous les logos existants.
L'inventaire se présente comme un dictionnaire de dictionnaire de Logo: L'inventaire se présente comme un dictionnaire de dictionnaire de Logo:
@ -285,6 +290,20 @@ class Logo:
dt = path.stat().st_mtime dt = path.stat().st_mtime
return path.stat().st_mtime return path.stat().st_mtime
def rename(self, new_name):
"""Change le nom (pas le département)
Les éléments non utiles ne sont pas recalculés (car rechargés lors des accès ultérieurs)
"""
old_path = Path(self.filepath)
self.logoname = secure_filename(new_name)
if not self.logoname:
self.logoname = "*** *** nom de logo invalide *** à changer ! *** ***"
else:
new_path = os.path.sep.join(
[self.dirpath, self.prefix + self.logoname + "." + self.suffix]
)
old_path.rename(new_path)
def guess_image_type(stream) -> str: def guess_image_type(stream) -> str:
"guess image type from header in stream" "guess image type from header in stream"

View File

@ -1055,6 +1055,14 @@ span.wtf-field ul.errors li {
display: list-item !important; display: list-item !important;
} }
.configuration_logo entete_dept {
display: inline-block;
}
.configuration_logo .effectifs {
float: right;
}
.configuration_logo h1 { .configuration_logo h1 {
display: inline-block; display: inline-block;
} }

View File

@ -20,28 +20,20 @@
{% endmacro %} {% endmacro %}
{% macro render_add_logo(add_logo_form) %} {% macro render_add_logo(add_logo_form) %}
{% if add_logo_form.errors() %} <details {{ add_logo_form.opened() }}>
<details open> <summary>
{% else %} <h3>Ajouter un logo</h3>
<details> </summary>
{% endif %} <div>
<summary> {{ render_field(add_logo_form.name) }}
<h3>Ajouter un logo</h3> {{ render_field(add_logo_form.upload) }}
</summary> {{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
<div> </div>
{{ render_field(add_logo_form.name) }} </details>
{{ render_field(add_logo_form.upload) }}
{{ render_field(add_logo_form.do_insert, False, onSubmit="submit_form") }}
</div>
</details>
{% endmacro %} {% endmacro %}
{% macro render_logo(dept_form, logo_form) %} {% macro render_logo(dept_form, logo_form) %}
{% if logo_form.errors() %} <details {{ logo_form.opened() }}>
<details open>
{% else %}
<details>
{% endif %}
{{ logo_form.hidden_tag() }} {{ logo_form.hidden_tag() }}
<summary> <summary>
{% if logo_form.titre %} {% if logo_form.titre %}
@ -73,6 +65,11 @@
<span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span> <span class="wtf-field">{{ render_field(logo_form.upload, False, onchange="submit_form()") }}</span>
</div> </div>
{% if logo_form.can_delete %} {% if logo_form.can_delete %}
<div class="action_label">Renommer</div>
<div class="action_button">
{{ render_field(logo_form.new_name, False) }}
{{ render_field(logo_form.do_rename, False, onSubmit="submit_form()") }}
</div>
<div class="action_label">Supprimer l'image</div> <div class="action_label">Supprimer l'image</div>
<div class="action_button"> <div class="action_button">
{{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }} {{ render_field(logo_form.do_delete, False, onSubmit="submit_form()") }}
@ -107,22 +104,22 @@
{% for dept_entry in form.depts.entries %} {% for dept_entry in form.depts.entries %}
{% set dept_form = dept_entry.form %} {% set dept_form = dept_entry.form %}
{{ dept_entry.form.hidden_tag() }} {{ dept_entry.form.hidden_tag() }}
{% if dept_form.errors() %} <details {{ dept_form.opened() }}>
<details open>
{% else %}
<details>
{% endif %}
<summary> <summary>
{% if dept_entry.form.is_local() %} <span class="entete_dept">
<h2>Département {{ dept_form.dept_name.data }}</h2> {% if dept_entry.form.is_local() %}
<div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br /> <h2>Département {{ dept_form.dept_name.data }}</h2>
Les logos du département se substituent aux logos de même nom définis globalement:</div> <h3 class="effectifs">{{ dept_form.count() }}</h3>
{% else %} <div class="sco_help">Les paramètres donnés sont spécifiques à ce département.<br />
<h2>Logos généraux</h2> Les logos du département se substituent aux logos de même nom définis globalement:</div>
<div class="sco_help">Les images de cette section sont utilisé pour tous les départements, {% else %}
mais peuvent être redéfinies localement au niveau de chaque département <h2>Logos généraux</h2>
(il suffit de définir un logo local de même nom)</div> <h3 class="effectifs">{{ dept_form.count() }}</h3>
{% endif %} <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>
{% endif %}
</span>
</summary> </summary>
<div> <div>
{{ render_logos(dept_form) }} {{ render_logos(dept_form) }}