File size: 5,315 Bytes
3e43e90
46e055c
 
 
 
 
 
 
3e43e90
 
 
46e055c
 
 
 
3e43e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46e055c
3e43e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46e055c
 
 
 
 
3e43e90
 
 
 
46e055c
 
 
 
3e43e90
 
 
 
 
46e055c
 
 
 
3e43e90
 
46e055c
 
3e43e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46e055c
 
 
3e43e90
 
46e055c
3e43e90
 
 
 
 
46e055c
 
 
3e43e90
 
 
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
126
127
# 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.
ALLOWED_TAGS_EXTENDED = ALLOWED_TAGS + [
    'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 
    'ul', 'ol', 'li', 'blockquote', 'pre', 'code',
    'strong', 'em', 'u', 's', 'sub', 'sup', 'span' # Ajout de span pour styles potentiels
]

# 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'), 'auteur') # Tri
    column_searchable_list = ('titre', 'contenu', 'auteur', 'sous_categorie.nom') # Recherche
    column_filters = ('sous_categorie', 'auteur') # Filtres

    form_args = {
      'sous_categorie': {
          'query_factory': lambda: SousCategorie.query.join(Matiere).order_by(func.lower(Matiere.nom), func.lower(SousCategorie.nom))
      },
      'auteur': { # Optionnel: Rendre le champ auteur un peu plus petit si besoin
            'render_kw': {'style': 'width: 300px'} 
      }
    }
   
    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 ---