Docfile commited on
Commit
3e43e90
·
verified ·
1 Parent(s): b645b5a

Update app/admin.py

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