Spaces:
Running
Running
File size: 5,177 Bytes
3e43e90 46e055c 3e43e90 46e055c 3e43e90 2825023 3e43e90 2825023 3e43e90 46e055c 3e43e90 46e055c 3e43e90 46e055c 3e43e90 46e055c 3e43e90 46e055c 3e43e90 46e055c 3e43e90 46e055c 3e43e90 c2b48d3 3e43e90 46e055c 3e43e90 c2b48d3 46e055c 3e43e90 46e055c 3e43e90 46e055c 3e43e90 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# START OF FILE admin.py
from flask import Blueprint
from flask_admin.contrib.sqla import ModelView
from flask_admin import BaseView, expose
from app import db, admin
from app.models import Matiere, SousCategorie, Texte
from flask_ckeditor import CKEditorField
from wtforms import StringField, TextAreaField
from bleach import clean, ALLOWED_TAGS, ALLOWED_ATTRIBUTES
# Importer func pour les tris/filtres
from sqlalchemy import func
bp = Blueprint('custom_admin', __name__, url_prefix='/admin')
# Définir les tags et attributs HTML autorisés
# On ajoute les tags de base + titres, listes, blockquotes etc.
# ALTERNATIVE (résultat en liste)
ALLOWED_TAGS_EXTENDED = list(ALLOWED_TAGS) + [ # Convertit d'abord en liste
'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'ul', 'ol', 'li', 'blockquote', 'pre', 'code',
'strong', 'em', 'u', 's', 'sub', 'sup', 'span'
]
# Autoriser les attributs de base + 'style' pour certains éléments si nécessaire
# ATTENTION : Autoriser 'style' peut ouvrir des failles si pas bien contrôlé.
# Une alternative est de ne pas autoriser 'style' et d'utiliser des classes CSS.
ALLOWED_ATTRIBUTES_EXTENDED = ALLOWED_ATTRIBUTES.copy() # Créer une copie pour modifier
ALLOWED_ATTRIBUTES_EXTENDED['a'] = ['href', 'title', 'target'] # Autoriser target pour ouvrir dans une nouvelle fenetre
ALLOWED_ATTRIBUTES_EXTENDED['span'] = ['style'] # Exemple: Autoriser style sur span
ALLOWED_ATTRIBUTES_EXTENDED['p'] = ['style'] # Exemple: Autoriser style sur p
# Ajoutez d'autres éléments et leurs attributs autorisés si besoin, ex: img: ['src', 'alt', 'title', 'width', 'height']
def sanitize_html(html_content):
"""
Nettoie le contenu HTML en autorisant un ensemble défini de balises et d'attributs.
"""
if not html_content:
return ""
# Utiliser bleach avec les tags/attributs étendus
# strip=True supprime les tags non autorisés au lieu de les échapper
# strip_comments=True supprime les commentaires HTML
cleaned_html = clean(
html_content,
tags=ALLOWED_TAGS_EXTENDED,
attributes=ALLOWED_ATTRIBUTES_EXTENDED,
strip=True,
strip_comments=True
)
return cleaned_html
class MatiereView(ModelView):
column_list = ('nom', 'sous_categories') # Colonnes à afficher dans la liste
form_columns = ('nom',)
# Pour trier la liste déroulante des sous-catégories (si affichée ailleurs)
column_sortable_list = ('nom',)
column_searchable_list = ('nom',)
class SousCategorieView(ModelView):
column_list = ('nom', 'matiere')
form_columns = ('nom', 'matiere')
column_sortable_list = ('nom', ('matiere', 'matiere.nom')) # Tri par nom et par nom de matière
column_searchable_list = ('nom', 'matiere.nom') # Recherche
column_filters = ('matiere',) # Ajout de filtre par matière
form_args = {
'matiere': {
'query_factory': lambda: Matiere.query.order_by(func.lower(Matiere.nom)) #Tri insensible à la casse
}
}
# Ajout de 'func' manquant dans les imports
def on_model_change(self, form, model, is_created):
# Vérification de l'unicité (nom, matiere_id) *avant* l'insertion/mise à jour
matiere_id = form.matiere.data.id if form.matiere.data else None
if not matiere_id:
raise ValueError("La matière est obligatoire.")
nom_lower = func.lower(form.nom.data)
query = SousCategorie.query.filter(
func.lower(SousCategorie.nom) == nom_lower,
SousCategorie.matiere_id == matiere_id
)
if not is_created: # Exclure l'enregistrement actuel lors de la mise à jour
query = query.filter(SousCategorie.id != model.id)
existing = query.first()
if existing:
raise ValueError(f"La sous-catégorie '{form.nom.data}' existe déjà pour la matière '{form.matiere.data.nom}'.")
class TexteView(ModelView):
column_list = ('titre', 'sous_categorie', 'auteur') # Ajout de l'auteur dans la liste
form_columns = ('titre', 'contenu', 'sous_categorie', 'auteur') # Ajout de l'auteur dans le formulaire
form_overrides = dict(contenu=CKEditorField)
column_sortable_list = ('titre', ('sous_categorie', 'sous_categorie.nom'),) # Tri
column_searchable_list = ('titre', 'contenu', , 'sous_categorie.nom') # Recherche
column_filters = ('sous_categorie') # Filtres
form_args = {
'sous_categorie': {
'query_factory': lambda: SousCategorie.query.join(Matiere).order_by(func.lower(Matiere.nom), func.lower(SousCategorie.nom))
},
}
def on_model_change(self, form, model, is_created):
# Appliquer la sanitization améliorée
model.contenu = sanitize_html(form.contenu.data)
# Optionnel: Mettre une valeur par défaut si l'auteur est vide
if not model.auteur:
model.auteur = "Anonyme" # Ou None si vous préférez
admin.add_view(MatiereView(Matiere, db.session))
admin.add_view(SousCategorieView(SousCategorie, db.session))
admin.add_view(TexteView(Texte, db.session))
# --- END OF FILE admin.py --- |