From debe07d2ca820b8e5652e39691ce8dcc540124ba Mon Sep 17 00:00:00 2001 From: jmpla Date: Sat, 18 Feb 2023 13:24:40 +0100 Subject: [PATCH] prefs detail persistence --- app/forms/config_logos.py | 451 --------------------------------- app/forms/main/config_logos.py | 10 + 2 files changed, 10 insertions(+), 451 deletions(-) delete mode 100644 app/forms/config_logos.py diff --git a/app/forms/config_logos.py b/app/forms/config_logos.py deleted file mode 100644 index d01cc5cc4..000000000 --- a/app/forms/config_logos.py +++ /dev/null @@ -1,451 +0,0 @@ -# -*- mode: python -*- -# -*- coding: utf-8 -*- - -############################################################################## -# -# ScoDoc -# -# Copyright (c) 1999 - 2023 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 -# -############################################################################## - -""" -Formulaires configuration logos - -Contrib @jmp, dec 21 -""" - -from flask import flash, url_for, redirect, render_template -from flask_wtf import FlaskForm -from flask_wtf.file import FileField, FileAllowed -from wtforms import SubmitField, FormField, validators, FieldList -from wtforms import ValidationError -from wtforms.fields.simple import StringField, HiddenField - -from app.models import Departement -from app.scodoc import sco_logos, html_sco_header -from app.scodoc import sco_utils as scu - -from app.scodoc.sco_config_actions import LogoInsert -from app.scodoc.sco_exceptions import ScoValueError -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 - - -def logo_name_validator(message=None): - def validate_logo_name(form, field): - name = field.data if field.data else "" - if "." in name: - raise ValidationError(message) - if not scu.is_valid_filename(name): - raise ValidationError(message) - - return validate_logo_name - - -class AddLogoForm(FlaskForm): - """Formulaire permettant l'ajout d'un logo (dans un département)""" - - from app.scodoc.sco_config_actions import LogoInsert - - dept_key = HiddenField() - 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 '-')"), - ], - ) - 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.DataRequired("Fichier image manquant"), - ], - ) - do_insert = SubmitField("ajouter une image") - - def __init__(self, *args, **kwargs): - kwargs["meta"] = {"csrf": False} - super().__init__(*args, **kwargs) - - def id(self): - return f"id=add_{self.dept_key.data}" - - 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, strict=True) 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 - - def opened(self): - if self.do_insert.data: - if self.name.errors: - return "open" - if self.upload.errors: - return "open" - return "" - - -class LogoForm(FlaskForm): - """Embed both presentation of a logo (cf. template file configuration.j2) - 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") - 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): - kwargs["meta"] = {"csrf": False} - super().__init__(*args, **kwargs) - logo = find_logo( - logoname=self.logo_id.data, dept_id=dept_key_to_id(self.dept_key.data) - ) - if logo is None: - raise ScoValueError("logo introuvable") - self.logo = logo.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 id(self): - idstring = f"{self.dept_key.data}_{self.logo_id.data}" - return f"id={idstring}" - - 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: - return LogoDelete.build_action(self.data) - if self.upload.data and self.validate(): - return LogoUpdate.build_action(self.data) - if self.do_rename.data and self.validate(): - return LogoRename.build_action(self.data) - return None - - def opened(self): - if self.upload.data and self.upload.errors: - return "open" - if self.new_name.data and self.new_name.errors: - return "open" - return "" - - -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 id(self): - return f"id=DEPT_{self.dept_key.data}" - - 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 opened(self): - if self.add_logo.opened(): - return "open" - for logo_form in self.logos: - if logo_form.opened(): - return "open" - 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(): - """Cette section assure 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(modele): - data = { - "depts": _make_depts_data(modele=modele), - } - return data - - -class LogosConfigurationForm(FlaskForm): - "Panneau de configuration des logos" - 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): - for dept_entry in self.depts: - dept_form = dept_entry.form - action = dept_form.select_action() - if action: - return action - return None - - -def config_logos(): - "Page de configuration des logos" - # nb: le contrôle d'accès (SuperAdmin) doit être fait dans la vue - form = LogosConfigurationForm( - data=_make_data( - 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.configure_logos")) - else: - if not form.validate(): - scu.flash_errors(form) - - return render_template( - "config_logos.j2", - scodoc_dept=None, - title="Configuration ScoDoc", - form=form, - ) diff --git a/app/forms/main/config_logos.py b/app/forms/main/config_logos.py index 2a54dd7c7..d01cc5cc4 100644 --- a/app/forms/main/config_logos.py +++ b/app/forms/main/config_logos.py @@ -148,6 +148,9 @@ class AddLogoForm(FlaskForm): kwargs["meta"] = {"csrf": False} super().__init__(*args, **kwargs) + def id(self): + return f"id=add_{self.dept_key.data}" + def validate_name(self, name): dept_id = dept_key_to_id(self.dept_key.data) if dept_id == GLOBAL: @@ -227,6 +230,10 @@ class LogoForm(FlaskForm): self.description = "Se substitue au footer défini au niveau global" self.titre = "Logo pied de page" + def id(self): + idstring = f"{self.dept_key.data}_{self.logo_id.data}" + return f"id={idstring}" + def select_action(self): from app.scodoc.sco_config_actions import LogoRename from app.scodoc.sco_config_actions import LogoUpdate @@ -258,6 +265,9 @@ class DeptForm(FlaskForm): kwargs["meta"] = {"csrf": False} super().__init__(*args, **kwargs) + def id(self): + return f"id=DEPT_{self.dept_key.data}" + def is_local(self): if self.dept_key.data == GLOBAL: return None