django-lucy-assist 1.0.6__py3-none-any.whl → 1.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_lucy_assist-1.0.6.dist-info → django_lucy_assist-1.0.8.dist-info}/METADATA +2 -2
- {django_lucy_assist-1.0.6.dist-info → django_lucy_assist-1.0.8.dist-info}/RECORD +16 -14
- lucy_assist/__init__.py +1 -1
- lucy_assist/admin.py +50 -1
- lucy_assist/conf.py +8 -0
- lucy_assist/constantes.py +21 -1
- lucy_assist/migrations/0003_configurationlucyassist_crud_views_mapping.py +18 -0
- lucy_assist/models/configuration.py +54 -0
- lucy_assist/services/context_service.py +17 -6
- lucy_assist/services/crud_service.py +695 -22
- lucy_assist/services/tool_executor_service.py +41 -1
- lucy_assist/services/tools_definition.py +36 -6
- lucy_assist/services/view_discovery_service.py +339 -0
- lucy_assist/static/lucy_assist/css/lucy-assist.css +254 -0
- {django_lucy_assist-1.0.6.dist-info → django_lucy_assist-1.0.8.dist-info}/WHEEL +0 -0
- {django_lucy_assist-1.0.6.dist-info → django_lucy_assist-1.0.8.dist-info}/top_level.txt +0 -0
|
@@ -47,6 +47,7 @@ class ToolExecutorService:
|
|
|
47
47
|
handlers = {
|
|
48
48
|
'create_object': self._handle_create_object,
|
|
49
49
|
'update_object': self._handle_update_object,
|
|
50
|
+
'get_deletion_impact': self._handle_get_deletion_impact,
|
|
50
51
|
'delete_object': self._handle_delete_object,
|
|
51
52
|
'search_objects': self._handle_search_objects,
|
|
52
53
|
'get_object_details': self._handle_get_object_details,
|
|
@@ -110,11 +111,41 @@ class ToolExecutorService:
|
|
|
110
111
|
|
|
111
112
|
return self.crud_service.update_object(app_name, model_name, object_id, data)
|
|
112
113
|
|
|
114
|
+
def _handle_get_deletion_impact(self, params: Dict) -> Dict:
|
|
115
|
+
"""
|
|
116
|
+
Analyse l'impact d'une suppression avant de l'exécuter.
|
|
117
|
+
Retourne la liste de toutes les conséquences (cascade, SET_NULL, etc.)
|
|
118
|
+
"""
|
|
119
|
+
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
120
|
+
model_name = params.get('model_name', '')
|
|
121
|
+
object_id = params.get('object_id')
|
|
122
|
+
|
|
123
|
+
if not model_name:
|
|
124
|
+
return {'error': 'model_name est requis'}
|
|
125
|
+
|
|
126
|
+
if not object_id:
|
|
127
|
+
return {'error': 'object_id est requis'}
|
|
128
|
+
|
|
129
|
+
# Analyser l'impact
|
|
130
|
+
impact = self.crud_service.get_deletion_impact(app_name, model_name, object_id)
|
|
131
|
+
|
|
132
|
+
# Formater le message pour l'utilisateur
|
|
133
|
+
impact['formatted_message'] = self.crud_service.format_deletion_impact_message(impact)
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
'success': True,
|
|
137
|
+
'impact': impact
|
|
138
|
+
}
|
|
139
|
+
|
|
113
140
|
def _handle_delete_object(self, params: Dict) -> Dict:
|
|
114
|
-
"""
|
|
141
|
+
"""
|
|
142
|
+
Supprime un objet.
|
|
143
|
+
Requiert que l'utilisateur ait explicitement confirmé après avoir vu l'impact.
|
|
144
|
+
"""
|
|
115
145
|
app_name = params.get('app_name') or get_app_for_model(params.get('model_name', ''))
|
|
116
146
|
model_name = params.get('model_name', '')
|
|
117
147
|
object_id = params.get('object_id')
|
|
148
|
+
confirmed = params.get('confirmed', False)
|
|
118
149
|
|
|
119
150
|
if not model_name:
|
|
120
151
|
return {'error': 'model_name est requis'}
|
|
@@ -122,6 +153,15 @@ class ToolExecutorService:
|
|
|
122
153
|
if not object_id:
|
|
123
154
|
return {'error': 'object_id est requis'}
|
|
124
155
|
|
|
156
|
+
# Vérifier que la confirmation a été donnée
|
|
157
|
+
if not confirmed:
|
|
158
|
+
return {
|
|
159
|
+
'error': 'Confirmation requise. Utilisez d\'abord get_deletion_impact pour voir les conséquences, '
|
|
160
|
+
'puis demandez confirmation à l\'utilisateur avant de supprimer.',
|
|
161
|
+
'requires_confirmation': True
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Exécuter la suppression
|
|
125
165
|
return self.crud_service.delete_object(app_name, model_name, object_id)
|
|
126
166
|
|
|
127
167
|
def _handle_search_objects(self, params: Dict) -> Dict:
|
|
@@ -10,7 +10,7 @@ LUCY_ASSIST_TOOLS = [
|
|
|
10
10
|
{
|
|
11
11
|
"name": "create_object",
|
|
12
12
|
"description": """Crée un nouvel objet dans la base de données.
|
|
13
|
-
Utilise ce tool quand l'utilisateur demande de créer un objet métier (client,
|
|
13
|
+
Utilise ce tool quand l'utilisateur demande de créer un objet métier (client, facture, etc.).
|
|
14
14
|
IMPORTANT: Utilise ce tool en priorité quand l'utilisateur demande une création.
|
|
15
15
|
Ne demande pas de confirmation, exécute directement l'action.""",
|
|
16
16
|
"input_schema": {
|
|
@@ -18,11 +18,11 @@ LUCY_ASSIST_TOOLS = [
|
|
|
18
18
|
"properties": {
|
|
19
19
|
"app_name": {
|
|
20
20
|
"type": "string",
|
|
21
|
-
"description": "Nom de l'application Django (ex: client,
|
|
21
|
+
"description": "Nom de l'application Django (ex: client, facture)"
|
|
22
22
|
},
|
|
23
23
|
"model_name": {
|
|
24
24
|
"type": "string",
|
|
25
|
-
"description": "Nom du modèle Django (ex: Client,
|
|
25
|
+
"description": "Nom du modèle Django (ex: Client, Facture)"
|
|
26
26
|
},
|
|
27
27
|
"data": {
|
|
28
28
|
"type": "object",
|
|
@@ -62,11 +62,37 @@ LUCY_ASSIST_TOOLS = [
|
|
|
62
62
|
"required": ["app_name", "model_name", "object_id", "data"]
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
"name": "get_deletion_impact",
|
|
67
|
+
"description": """Analyse l'impact d'une suppression AVANT de supprimer.
|
|
68
|
+
OBLIGATOIRE: Utilise TOUJOURS ce tool AVANT delete_object pour montrer à l'utilisateur
|
|
69
|
+
toutes les conséquences de la suppression (objets supprimés en cascade, champs mis à NULL, etc.)
|
|
70
|
+
Affiche le résultat à l'utilisateur et demande sa confirmation explicite avant de supprimer.""",
|
|
71
|
+
"input_schema": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"app_name": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Nom de l'application Django"
|
|
77
|
+
},
|
|
78
|
+
"model_name": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "Nom du modèle Django"
|
|
81
|
+
},
|
|
82
|
+
"object_id": {
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"description": "ID de l'objet à analyser"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"required": ["app_name", "model_name", "object_id"]
|
|
88
|
+
}
|
|
89
|
+
},
|
|
65
90
|
{
|
|
66
91
|
"name": "delete_object",
|
|
67
92
|
"description": """Supprime un objet de la base de données.
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
IMPORTANT: Tu DOIS d'abord utiliser get_deletion_impact pour analyser les conséquences,
|
|
94
|
+
puis afficher le résultat à l'utilisateur, et obtenir sa CONFIRMATION EXPLICITE avant d'exécuter ce tool.
|
|
95
|
+
Ne JAMAIS supprimer sans avoir montré l'impact et obtenu confirmation.""",
|
|
70
96
|
"input_schema": {
|
|
71
97
|
"type": "object",
|
|
72
98
|
"properties": {
|
|
@@ -81,9 +107,13 @@ LUCY_ASSIST_TOOLS = [
|
|
|
81
107
|
"object_id": {
|
|
82
108
|
"type": "integer",
|
|
83
109
|
"description": "ID de l'objet à supprimer"
|
|
110
|
+
},
|
|
111
|
+
"confirmed": {
|
|
112
|
+
"type": "boolean",
|
|
113
|
+
"description": "OBLIGATOIRE: true si l'utilisateur a explicitement confirmé la suppression après avoir vu l'impact"
|
|
84
114
|
}
|
|
85
115
|
},
|
|
86
|
-
"required": ["app_name", "model_name", "object_id"]
|
|
116
|
+
"required": ["app_name", "model_name", "object_id", "confirmed"]
|
|
87
117
|
}
|
|
88
118
|
},
|
|
89
119
|
{
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service de découverte automatique des vues CRUD du projet.
|
|
3
|
+
|
|
4
|
+
Parcourt les URL patterns Django pour identifier les vues CRUD
|
|
5
|
+
et construire un mapping utilisable par Lucy Assist.
|
|
6
|
+
"""
|
|
7
|
+
import re
|
|
8
|
+
from typing import Dict, List, Optional, Tuple
|
|
9
|
+
from django.urls import URLPattern, URLResolver, get_resolver
|
|
10
|
+
from django.urls.resolvers import RoutePattern
|
|
11
|
+
|
|
12
|
+
from lucy_assist.utils.log_utils import LogUtils
|
|
13
|
+
from lucy_assist.conf import lucy_assist_settings
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ViewDiscoveryService:
|
|
17
|
+
"""
|
|
18
|
+
Service pour découvrir automatiquement les vues CRUD d'un projet Django.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Patterns de nommage courants pour les vues CRUD
|
|
22
|
+
# L'ordre est important : les patterns plus spécifiques doivent être en premier
|
|
23
|
+
CRUD_PATTERNS = {
|
|
24
|
+
'list': [
|
|
25
|
+
r'-list$', r'-liste$', r'-gestion$', r'-index$',
|
|
26
|
+
r'_list$', r'_liste$', r'_gestion$', r'_index$',
|
|
27
|
+
r'list$', r'liste$', r'gestion$'
|
|
28
|
+
],
|
|
29
|
+
'create': [
|
|
30
|
+
r'-create$', r'-add$', r'-nouveau$', r'-new$',
|
|
31
|
+
r'_create$', r'_add$', r'_nouveau$', r'_new$',
|
|
32
|
+
r'create$', r'add$', r'nouveau$'
|
|
33
|
+
],
|
|
34
|
+
'detail': [
|
|
35
|
+
r'-detail$', r'-view$', r'-show$', r'-fiche$', r'-consulter$',
|
|
36
|
+
r'_detail$', r'_view$', r'_show$', r'_fiche$',
|
|
37
|
+
r'detail$', r'view$', r'fiche$'
|
|
38
|
+
],
|
|
39
|
+
'update': [
|
|
40
|
+
r'-update$', r'-edit$', r'-modifier$', r'-modification$',
|
|
41
|
+
r'_update$', r'_edit$', r'_modifier$',
|
|
42
|
+
r'update$', r'edit$', r'modifier$'
|
|
43
|
+
],
|
|
44
|
+
'delete': [
|
|
45
|
+
r'-delete$', r'-suppression$', r'-supprimer$', r'-remove$',
|
|
46
|
+
r'_delete$', r'_suppression$', r'_supprimer$', r'_remove$',
|
|
47
|
+
r'delete$', r'suppression$', r'supprimer$'
|
|
48
|
+
],
|
|
49
|
+
# Formulaire peut être create ou update selon le contexte
|
|
50
|
+
'formulaire': [
|
|
51
|
+
r'-formulaire$', r'_formulaire$', r'formulaire$',
|
|
52
|
+
r'-form$', r'_form$', r'form$'
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Patterns d'URL pour détecter les paramètres
|
|
57
|
+
URL_PARAM_PATTERNS = [
|
|
58
|
+
(r'<int:pk>', 'pk'),
|
|
59
|
+
(r'<pk>', 'pk'),
|
|
60
|
+
(r'<int:id>', 'id'),
|
|
61
|
+
(r'<id>', 'id'),
|
|
62
|
+
(r'<slug:slug>', 'slug'),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def __init__(self):
|
|
66
|
+
self.apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX or ''
|
|
67
|
+
|
|
68
|
+
def discover_crud_views(self) -> Dict:
|
|
69
|
+
"""
|
|
70
|
+
Découvre toutes les vues CRUD du projet.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict avec le mapping modèle -> actions -> infos vue
|
|
74
|
+
"""
|
|
75
|
+
LogUtils.info("[ViewDiscovery] Début de la découverte des vues CRUD")
|
|
76
|
+
|
|
77
|
+
crud_mapping = {}
|
|
78
|
+
resolver = get_resolver()
|
|
79
|
+
|
|
80
|
+
# Parcourir tous les URL patterns
|
|
81
|
+
self._scan_url_patterns(resolver.url_patterns, '', '', crud_mapping)
|
|
82
|
+
|
|
83
|
+
LogUtils.info(f"[ViewDiscovery] {len(crud_mapping)} modèles découverts")
|
|
84
|
+
for model, actions in crud_mapping.items():
|
|
85
|
+
LogUtils.info(f"[ViewDiscovery] {model}: {list(actions.keys())}")
|
|
86
|
+
|
|
87
|
+
return crud_mapping
|
|
88
|
+
|
|
89
|
+
def _scan_url_patterns(
|
|
90
|
+
self,
|
|
91
|
+
patterns: List,
|
|
92
|
+
namespace: str,
|
|
93
|
+
url_prefix: str,
|
|
94
|
+
crud_mapping: Dict
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Parcourt récursivement les URL patterns.
|
|
98
|
+
"""
|
|
99
|
+
for pattern in patterns:
|
|
100
|
+
if isinstance(pattern, URLResolver):
|
|
101
|
+
# C'est un include() - descendre récursivement
|
|
102
|
+
new_namespace = pattern.namespace
|
|
103
|
+
if namespace and new_namespace:
|
|
104
|
+
new_namespace = f"{namespace}:{new_namespace}"
|
|
105
|
+
elif namespace:
|
|
106
|
+
new_namespace = namespace
|
|
107
|
+
|
|
108
|
+
# Construire le préfixe URL
|
|
109
|
+
pattern_str = self._get_pattern_string(pattern.pattern)
|
|
110
|
+
new_url_prefix = url_prefix + pattern_str
|
|
111
|
+
|
|
112
|
+
# Filtrer par préfixe d'apps si configuré
|
|
113
|
+
app_name = getattr(pattern, 'app_name', '') or ''
|
|
114
|
+
if self.apps_prefix:
|
|
115
|
+
# Vérifier si c'est une app du projet
|
|
116
|
+
if app_name and not app_name.startswith(self.apps_prefix.rstrip('.')):
|
|
117
|
+
# Permettre aussi les apps dont le namespace correspond
|
|
118
|
+
if new_namespace and not any(
|
|
119
|
+
new_namespace.startswith(prefix)
|
|
120
|
+
for prefix in ['admin', 'lucy_assist']
|
|
121
|
+
):
|
|
122
|
+
pass # Continuer l'exploration
|
|
123
|
+
else:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
self._scan_url_patterns(
|
|
127
|
+
pattern.url_patterns,
|
|
128
|
+
new_namespace or '',
|
|
129
|
+
new_url_prefix,
|
|
130
|
+
crud_mapping
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
elif isinstance(pattern, URLPattern):
|
|
134
|
+
# C'est une vue finale
|
|
135
|
+
self._process_url_pattern(
|
|
136
|
+
pattern,
|
|
137
|
+
namespace,
|
|
138
|
+
url_prefix,
|
|
139
|
+
crud_mapping
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def _process_url_pattern(
|
|
143
|
+
self,
|
|
144
|
+
pattern: URLPattern,
|
|
145
|
+
namespace: str,
|
|
146
|
+
url_prefix: str,
|
|
147
|
+
crud_mapping: Dict
|
|
148
|
+
):
|
|
149
|
+
"""
|
|
150
|
+
Analyse un URL pattern pour détecter s'il s'agit d'une vue CRUD.
|
|
151
|
+
"""
|
|
152
|
+
url_name = pattern.name
|
|
153
|
+
if not url_name:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# Construire l'URL complète
|
|
157
|
+
pattern_str = self._get_pattern_string(pattern.pattern)
|
|
158
|
+
full_url = '/' + url_prefix + pattern_str
|
|
159
|
+
full_url = re.sub(r'/+', '/', full_url) # Normaliser les slashes
|
|
160
|
+
|
|
161
|
+
# Construire le nom complet de la vue
|
|
162
|
+
full_name = f"{namespace}:{url_name}" if namespace else url_name
|
|
163
|
+
|
|
164
|
+
# Détecter le modèle et l'action depuis le nom de l'URL
|
|
165
|
+
model_name, action = self._detect_model_and_action(url_name)
|
|
166
|
+
|
|
167
|
+
if not model_name or not action:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# Détecter si l'URL nécessite un paramètre (pk, id, etc.)
|
|
171
|
+
requires_pk = self._url_requires_pk(full_url)
|
|
172
|
+
|
|
173
|
+
# Déterminer la méthode HTTP
|
|
174
|
+
http_method = self._detect_http_method(pattern, action)
|
|
175
|
+
|
|
176
|
+
# Ajouter au mapping
|
|
177
|
+
model_lower = model_name.lower()
|
|
178
|
+
if model_lower not in crud_mapping:
|
|
179
|
+
crud_mapping[model_lower] = {}
|
|
180
|
+
|
|
181
|
+
# Les vues "formulaire" gèrent généralement create ET update
|
|
182
|
+
if action == 'formulaire':
|
|
183
|
+
crud_mapping[model_lower]['create'] = {
|
|
184
|
+
'url_name': full_name,
|
|
185
|
+
'url': full_url,
|
|
186
|
+
'method': 'POST',
|
|
187
|
+
'requires_pk': False
|
|
188
|
+
}
|
|
189
|
+
crud_mapping[model_lower]['update'] = {
|
|
190
|
+
'url_name': full_name,
|
|
191
|
+
'url': full_url,
|
|
192
|
+
'method': 'POST',
|
|
193
|
+
'requires_pk': True # pk passé en POST ou GET
|
|
194
|
+
}
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# Pour les autres actions create/update
|
|
198
|
+
if action == 'create' and requires_pk:
|
|
199
|
+
# C'est probablement une vue qui gère create et update
|
|
200
|
+
action = 'update'
|
|
201
|
+
|
|
202
|
+
crud_mapping[model_lower][action] = {
|
|
203
|
+
'url_name': full_name,
|
|
204
|
+
'url': full_url,
|
|
205
|
+
'method': http_method,
|
|
206
|
+
'requires_pk': requires_pk
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
def _detect_model_and_action(self, url_name: str) -> Tuple[Optional[str], Optional[str]]:
|
|
210
|
+
"""
|
|
211
|
+
Détecte le nom du modèle et l'action CRUD depuis le nom de l'URL.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
url_name: Nom de l'URL (ex: 'client-list', 'reservation-formulaire', 'client-b2b-gestion')
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Tuple (model_name, action) ou (None, None)
|
|
218
|
+
"""
|
|
219
|
+
url_name_lower = url_name.lower()
|
|
220
|
+
|
|
221
|
+
# Chercher l'action dans le nom
|
|
222
|
+
detected_action = None
|
|
223
|
+
matched_pattern = None
|
|
224
|
+
|
|
225
|
+
for action, patterns in self.CRUD_PATTERNS.items():
|
|
226
|
+
for pattern in patterns:
|
|
227
|
+
if re.search(pattern, url_name_lower):
|
|
228
|
+
detected_action = action
|
|
229
|
+
matched_pattern = pattern
|
|
230
|
+
break
|
|
231
|
+
if detected_action:
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
if not detected_action:
|
|
235
|
+
return None, None
|
|
236
|
+
|
|
237
|
+
# Extraire le nom du modèle en retirant le pattern d'action trouvé
|
|
238
|
+
model_name = re.sub(matched_pattern, '', url_name_lower)
|
|
239
|
+
|
|
240
|
+
# Nettoyer le nom du modèle
|
|
241
|
+
model_name = model_name.strip('-_')
|
|
242
|
+
|
|
243
|
+
# Convertir les tirets en underscores pour la cohérence
|
|
244
|
+
# mais garder aussi la version avec tirets pour le mapping
|
|
245
|
+
model_name_normalized = model_name.replace('-', '_')
|
|
246
|
+
|
|
247
|
+
if not model_name:
|
|
248
|
+
return None, None
|
|
249
|
+
|
|
250
|
+
# Retourner la version normalisée (avec underscores)
|
|
251
|
+
return model_name_normalized, detected_action
|
|
252
|
+
|
|
253
|
+
def _get_pattern_string(self, pattern) -> str:
|
|
254
|
+
"""
|
|
255
|
+
Extrait la chaîne de pattern depuis un RoutePattern ou RegexPattern.
|
|
256
|
+
"""
|
|
257
|
+
if hasattr(pattern, '_route'):
|
|
258
|
+
return pattern._route
|
|
259
|
+
elif hasattr(pattern, '_regex'):
|
|
260
|
+
# Convertir le regex en quelque chose de lisible
|
|
261
|
+
regex = pattern._regex
|
|
262
|
+
# Simplifier les captures
|
|
263
|
+
regex = re.sub(r'\(\?P<(\w+)>[^)]+\)', r'<\1>', regex)
|
|
264
|
+
regex = regex.strip('^$')
|
|
265
|
+
return regex
|
|
266
|
+
return ''
|
|
267
|
+
|
|
268
|
+
def _url_requires_pk(self, url: str) -> bool:
|
|
269
|
+
"""
|
|
270
|
+
Vérifie si l'URL nécessite un paramètre pk/id.
|
|
271
|
+
"""
|
|
272
|
+
pk_patterns = ['<pk>', '<int:pk>', '<id>', '<int:id>', '<slug>']
|
|
273
|
+
return any(p in url for p in pk_patterns)
|
|
274
|
+
|
|
275
|
+
def _detect_http_method(self, pattern: URLPattern, action: str) -> str:
|
|
276
|
+
"""
|
|
277
|
+
Détecte la méthode HTTP appropriée pour une action.
|
|
278
|
+
"""
|
|
279
|
+
# Pour les vues basées sur des classes, on peut essayer de détecter
|
|
280
|
+
# les méthodes supportées, mais par défaut on utilise les conventions
|
|
281
|
+
|
|
282
|
+
method_map = {
|
|
283
|
+
'list': 'GET',
|
|
284
|
+
'detail': 'GET',
|
|
285
|
+
'create': 'POST',
|
|
286
|
+
'update': 'POST', # Souvent POST dans Django (pas PUT)
|
|
287
|
+
'delete': 'POST', # Souvent POST dans Django (pas DELETE)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return method_map.get(action, 'GET')
|
|
291
|
+
|
|
292
|
+
def get_view_info(self, model_name: str, action: str) -> Optional[Dict]:
|
|
293
|
+
"""
|
|
294
|
+
Récupère les informations d'une vue pour un modèle et une action.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
model_name: Nom du modèle
|
|
298
|
+
action: Action CRUD ('list', 'create', 'detail', 'update', 'delete')
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Dict avec 'url_name', 'url', 'method', 'requires_pk' ou None
|
|
302
|
+
"""
|
|
303
|
+
from lucy_assist.models import ConfigurationLucyAssist
|
|
304
|
+
config = ConfigurationLucyAssist.get_config()
|
|
305
|
+
|
|
306
|
+
if not config.crud_views_mapping:
|
|
307
|
+
config.refresh_crud_views_mapping()
|
|
308
|
+
|
|
309
|
+
model_lower = model_name.lower()
|
|
310
|
+
|
|
311
|
+
# Essayer plusieurs variations du nom de modèle
|
|
312
|
+
variations = [
|
|
313
|
+
model_lower,
|
|
314
|
+
model_lower.replace('_', '-'), # client_b2b -> client-b2b
|
|
315
|
+
model_lower.replace('-', '_'), # client-b2b -> client_b2b
|
|
316
|
+
model_lower.replace('_', ''), # client_b2b -> clientb2b
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
for variant in variations:
|
|
320
|
+
model_views = config.crud_views_mapping.get(variant, {})
|
|
321
|
+
if model_views and action in model_views:
|
|
322
|
+
return model_views.get(action)
|
|
323
|
+
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
def get_all_discovered_models(self) -> List[str]:
|
|
327
|
+
"""
|
|
328
|
+
Retourne la liste de tous les modèles découverts avec des vues CRUD.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Liste des noms de modèles
|
|
332
|
+
"""
|
|
333
|
+
from lucy_assist.models import ConfigurationLucyAssist
|
|
334
|
+
config = ConfigurationLucyAssist.get_config()
|
|
335
|
+
|
|
336
|
+
if not config.crud_views_mapping:
|
|
337
|
+
config.refresh_crud_views_mapping()
|
|
338
|
+
|
|
339
|
+
return list(config.crud_views_mapping.keys())
|