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