django-lucy-assist 1.0.4__tar.gz → 1.0.6__tar.gz

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.
Files changed (51) hide show
  1. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/PKG-INFO +1 -9
  2. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/README.md +0 -8
  3. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/PKG-INFO +1 -9
  4. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/SOURCES.txt +1 -0
  5. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/__init__.py +1 -1
  6. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/conf.py +4 -4
  7. django_lucy_assist-1.0.6/lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py +18 -0
  8. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/context_service.py +111 -21
  9. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/crud_service.py +103 -27
  10. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/tools_definition.py +6 -7
  11. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/pyproject.toml +1 -1
  12. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/MANIFEST.in +0 -0
  13. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/dependency_links.txt +0 -0
  14. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/requires.txt +0 -0
  15. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/django_lucy_assist.egg-info/top_level.txt +0 -0
  16. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/admin.py +0 -0
  17. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/apps.py +0 -0
  18. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/constantes.py +0 -0
  19. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/context_processors.py +0 -0
  20. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/migrations/0001_initial.py +0 -0
  21. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/migrations/__init__.py +0 -0
  22. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/__init__.py +0 -0
  23. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/base.py +0 -0
  24. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/configuration.py +0 -0
  25. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/conversation.py +0 -0
  26. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/message.py +0 -0
  27. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/models/project_context_cache.py +0 -0
  28. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/__init__.py +0 -0
  29. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/bug_notification_service.py +0 -0
  30. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/claude_service.py +0 -0
  31. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/gitlab_service.py +0 -0
  32. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/project_context_service.py +0 -0
  33. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/services/tool_executor_service.py +0 -0
  34. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/signals.py +0 -0
  35. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/static/lucy_assist/css/lucy-assist.css +0 -0
  36. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/static/lucy_assist/image/icon-lucy.png +0 -0
  37. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/static/lucy_assist/js/lucy-assist.js +0 -0
  38. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/templates/lucy_assist/chatbot_sidebar.html +0 -0
  39. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/templates/lucy_assist/partials/documentation_content.html +0 -0
  40. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/__init__.py +0 -0
  41. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/factories/__init__.py +0 -0
  42. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/factories/lucy_assist_factories.py +0 -0
  43. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/tests/test_lucy_assist.py +0 -0
  44. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/urls.py +0 -0
  45. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/__init__.py +0 -0
  46. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/log_utils.py +0 -0
  47. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/message_utils.py +0 -0
  48. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/utils/token_utils.py +0 -0
  49. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/views/__init__.py +0 -0
  50. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/lucy_assist/views/api_views.py +0 -0
  51. {django_lucy_assist-1.0.4 → django_lucy_assist-1.0.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lucy-assist
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: Assistant IA intelligent Revolucy pour outil métier
5
5
  Author-email: Revolucy <hello@revolucy.fr>
6
6
  Maintainer-email: Maxence <hello@revolucy.fr>
@@ -183,14 +183,6 @@ Lucy Assist expose plusieurs endpoints API :
183
183
 
184
184
  [Revolucy](https://www.revolucy.fr)
185
185
 
186
- ## Versionning
187
-
188
- - V1.0.0 | Création du module Lucy
189
- - V1.0.1 | Correction de bugs
190
- - V1.0.2 | Correction de bugs
191
- - V1.0.3 | Correction de bugs
192
- - V1.0.4 | Ajout Prompt Custom dans configuration
193
-
194
186
  ## Déploiement Pypi
195
187
 
196
188
  1. `docker-compose exec django pip install build twine`
@@ -140,14 +140,6 @@ Lucy Assist expose plusieurs endpoints API :
140
140
 
141
141
  [Revolucy](https://www.revolucy.fr)
142
142
 
143
- ## Versionning
144
-
145
- - V1.0.0 | Création du module Lucy
146
- - V1.0.1 | Correction de bugs
147
- - V1.0.2 | Correction de bugs
148
- - V1.0.3 | Correction de bugs
149
- - V1.0.4 | Ajout Prompt Custom dans configuration
150
-
151
143
  ## Déploiement Pypi
152
144
 
153
145
  1. `docker-compose exec django pip install build twine`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lucy-assist
3
- Version: 1.0.4
3
+ Version: 1.0.6
4
4
  Summary: Assistant IA intelligent Revolucy pour outil métier
5
5
  Author-email: Revolucy <hello@revolucy.fr>
6
6
  Maintainer-email: Maxence <hello@revolucy.fr>
@@ -183,14 +183,6 @@ Lucy Assist expose plusieurs endpoints API :
183
183
 
184
184
  [Revolucy](https://www.revolucy.fr)
185
185
 
186
- ## Versionning
187
-
188
- - V1.0.0 | Création du module Lucy
189
- - V1.0.1 | Correction de bugs
190
- - V1.0.2 | Correction de bugs
191
- - V1.0.3 | Correction de bugs
192
- - V1.0.4 | Ajout Prompt Custom dans configuration
193
-
194
186
  ## Déploiement Pypi
195
187
 
196
188
  1. `docker-compose exec django pip install build twine`
@@ -15,6 +15,7 @@ lucy_assist/context_processors.py
15
15
  lucy_assist/signals.py
16
16
  lucy_assist/urls.py
17
17
  lucy_assist/migrations/0001_initial.py
18
+ lucy_assist/migrations/0002_configurationlucyassist_prompt_complementaire.py
18
19
  lucy_assist/migrations/__init__.py
19
20
  lucy_assist/models/__init__.py
20
21
  lucy_assist/models/base.py
@@ -5,7 +5,7 @@ Un chatbot IA basé sur Claude d'Anthropic, intégrable dans n'importe quelle
5
5
  application Django pour fournir une assistance contextuelle aux utilisateurs.
6
6
  """
7
7
 
8
- __version__ = '1.0.4'
8
+ __version__ = '1.0.6'
9
9
  __author__ = 'Revolucy'
10
10
 
11
11
  default_app_config = 'lucy_assist.apps.LucyAssistConfig'
@@ -47,13 +47,13 @@ class LucyAssistSettings:
47
47
  # Nombre moyen de tokens par conversation
48
48
  'TOKENS_MOYENS_PAR_CONVERSATION': 2000,
49
49
 
50
- # Questions fréquentes par défaut
50
+ # Questions fréquentes par défaut (génériques)
51
51
  'QUESTIONS_FREQUENTES_DEFAULT': [
52
- "Comment créer un nouveau membre ?",
53
- "Comment effectuer un paiement ?",
52
+ "Comment créer un nouveau client ?",
53
+ "Comment effectuer une recherche ?",
54
54
  "Comment exporter des données ?",
55
55
  "Comment modifier mon profil ?",
56
- "Où trouver la liste des adhésions ?",
56
+ "Où trouver la liste des réservations ?",
57
57
  ],
58
58
  }
59
59
 
@@ -0,0 +1,18 @@
1
+ # Generated by Django 6.0.1 on 2026-01-27 09:51
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('lucy_assist', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='configurationlucyassist',
15
+ name='prompt_complementaire',
16
+ field=models.TextField(blank=True, default=''),
17
+ ),
18
+ ]
@@ -1,8 +1,8 @@
1
1
  """
2
2
  Service de détection et construction du contexte de page.
3
3
  """
4
- import logging
5
4
  import re
5
+ from datetime import datetime
6
6
  from typing import Dict, List, Optional
7
7
  from urllib.parse import urlparse
8
8
 
@@ -10,14 +10,44 @@ from django.urls import resolve, Resolver404
10
10
  from django.apps import apps
11
11
 
12
12
  from lucy_assist.utils.log_utils import LogUtils
13
+ from lucy_assist.conf import lucy_assist_settings
13
14
 
14
15
 
15
16
  class ContextService:
16
17
  """Service pour construire le contexte de la page courante."""
17
18
 
19
+ # Patterns pour détecter les dates dans les requêtes
20
+ DATE_PATTERNS = [
21
+ (r'(\d{1,2})/(\d{1,2})/(\d{4})', '%d/%m/%Y'), # 18/10/2025
22
+ (r'(\d{1,2})-(\d{1,2})-(\d{4})', '%d-%m-%Y'), # 18-10-2025
23
+ (r'(\d{4})/(\d{1,2})/(\d{1,2})', '%Y/%m/%d'), # 2025/10/18
24
+ (r'(\d{4})-(\d{1,2})-(\d{1,2})', '%Y-%m-%d'), # 2025-10-18
25
+ ]
26
+
18
27
  def __init__(self, user):
19
28
  self.user = user
20
29
 
30
+ def _extract_date_from_query(self, query: str) -> Optional[datetime]:
31
+ """
32
+ Extrait une date d'une requête texte.
33
+ Supporte les formats: 18/10/2025, 18-10-2025, 2025/10/18, 2025-10-18
34
+ """
35
+ for pattern, date_format in self.DATE_PATTERNS:
36
+ match = re.search(pattern, query)
37
+ if match:
38
+ try:
39
+ date_str = match.group(0)
40
+ return datetime.strptime(date_str, date_format)
41
+ except ValueError:
42
+ continue
43
+ return None
44
+
45
+ def _remove_date_from_query(self, query: str) -> str:
46
+ """Supprime la date de la requête pour garder les autres mots-clés."""
47
+ for pattern, _ in self.DATE_PATTERNS:
48
+ query = re.sub(pattern, '', query)
49
+ return query.strip()
50
+
21
51
  def get_page_context(self, url_path: str) -> Dict:
22
52
  """
23
53
  Construit le contexte complet d'une page.
@@ -269,44 +299,104 @@ class ContextService:
269
299
  except LookupError:
270
300
  continue
271
301
  else:
272
- # Chercher dans les principaux modèles métier
273
- model_names = ['Membre', 'Structure', 'Paiement', 'Adhesion']
274
- for name in model_names:
275
- for app_config in apps.get_app_configs():
276
- try:
277
- model = app_config.get_model(name)
278
- models_to_search.append(model)
279
- break
280
- except LookupError:
281
- continue
302
+ # Découvrir dynamiquement les modèles du projet
303
+ apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX
304
+
305
+ for app_config in apps.get_app_configs():
306
+ # Filtrer par préfixe si configuré
307
+ if apps_prefix and not app_config.name.startswith(apps_prefix):
308
+ continue
309
+
310
+ # Exclure les apps système et lucy_assist
311
+ if app_config.name.startswith('django.') or app_config.name == 'lucy_assist':
312
+ continue
313
+
314
+ # Ajouter les modèles de cette app (limiter à 3 par app)
315
+ app_models = list(app_config.get_models())[:3]
316
+ models_to_search.extend(app_models)
317
+
318
+ # Limiter le nombre total de modèles à rechercher
319
+ if len(models_to_search) >= 10:
320
+ break
321
+
322
+ # Log des modèles découverts pour debug
323
+ LogUtils.info(f"[search_objects] Recherche '{query}' dans {len(models_to_search)} modèles: {[m.__name__ for m in models_to_search]}")
324
+
325
+ # Extraire une date de la requête si présente
326
+ search_date = self._extract_date_from_query(query)
327
+ text_query = self._remove_date_from_query(query) if search_date else query
328
+
329
+ if search_date:
330
+ LogUtils.info(f"[search_objects] Date détectée: {search_date.date()}")
282
331
 
283
332
  # Rechercher dans chaque modèle
284
333
  for model in models_to_search:
285
334
  try:
286
- # Trouver les champs texte pour la recherche
335
+ # Trouver les champs texte et date pour la recherche
287
336
  search_fields = []
337
+ date_fields = []
288
338
  for field in model._meta.get_fields():
289
339
  if hasattr(field, 'get_internal_type'):
290
- if field.get_internal_type() in ['CharField', 'TextField']:
340
+ field_type = field.get_internal_type()
341
+ if field_type in ['CharField', 'TextField']:
291
342
  search_fields.append(field.name)
292
-
293
- if not search_fields:
294
- continue
343
+ elif field_type in ['DateField', 'DateTimeField']:
344
+ date_fields.append(field.name)
295
345
 
296
346
  # Construire la requête
297
347
  from django.db.models import Q
298
- q_objects = Q()
299
- for field_name in search_fields[:5]: # Limiter à 5 champs
300
- q_objects |= Q(**{f'{field_name}__icontains': query})
348
+ q_objects = None
349
+ has_criteria = False
350
+
351
+ # Si on a une date, chercher dans les champs date
352
+ if search_date and date_fields:
353
+ date_q = Q()
354
+ for field_name in date_fields:
355
+ date_q |= Q(**{f'{field_name}__date': search_date.date()})
356
+ q_objects = date_q
357
+ has_criteria = True
358
+ LogUtils.info(f"[search_objects] {model.__name__}: recherche date dans {date_fields}")
359
+
360
+ # Si on a du texte, chercher dans les champs texte
361
+ if text_query.strip() and search_fields:
362
+ query_words = text_query.strip().split()
363
+ text_q = Q()
364
+
365
+ if len(query_words) > 1:
366
+ # Recherche multi-mots: chaque mot doit matcher au moins un champ
367
+ for word in query_words:
368
+ word_q = Q()
369
+ for field_name in search_fields[:5]:
370
+ word_q |= Q(**{f'{field_name}__icontains': word})
371
+ text_q &= word_q
372
+ else:
373
+ # Recherche simple
374
+ for field_name in search_fields[:5]:
375
+ text_q |= Q(**{f'{field_name}__icontains': text_query})
376
+
377
+ if q_objects is not None:
378
+ q_objects &= text_q
379
+ else:
380
+ q_objects = text_q
381
+ has_criteria = True
382
+
383
+ # Si pas de critère de recherche valide, skip
384
+ if not has_criteria:
385
+ if not search_fields and not date_fields:
386
+ LogUtils.info(f"[search_objects] {model.__name__}: aucun champ recherchable")
387
+ continue
301
388
 
302
389
  # Filtrer par permissions si possible
303
390
  # Note: certains modèles utilisent des managers customs qui
304
391
  # dépendent de ThreadLocal/middleware pour l'utilisateur courant
305
392
  try:
306
393
  queryset = model.objects.all()
394
+ count = queryset.count()
395
+ LogUtils.info(f"[search_objects] {model.__name__}: {count} objets en base")
307
396
  objects = queryset.filter(q_objects)[:limit]
308
- except AttributeError:
397
+ except AttributeError as e:
309
398
  # Si le manager a besoin d'un utilisateur non disponible, skip ce modèle
399
+ LogUtils.info(f"[search_objects] {model.__name__}: skip (AttributeError: {e})")
310
400
  continue
311
401
 
312
402
  for obj in objects:
@@ -322,7 +412,7 @@ class ContextService:
322
412
  return results
323
413
 
324
414
  except Exception as e:
325
- LogUtils.info(f"Erreur recherche dans {model.__name__}: {e}")
415
+ LogUtils.info(f"[search_objects] Erreur recherche dans {model.__name__}: {e}")
326
416
  continue
327
417
 
328
418
  return results
@@ -1,7 +1,6 @@
1
1
  """
2
2
  Service pour exécuter des opérations CRUD via Lucy Assist.
3
3
  """
4
- import logging
5
4
  from typing import Dict, List, Optional, Any
6
5
 
7
6
  from django.apps import apps
@@ -9,6 +8,7 @@ from django.db import transaction
9
8
  from django.forms import modelform_factory
10
9
 
11
10
  from lucy_assist.utils.log_utils import LogUtils
11
+ from lucy_assist.conf import lucy_assist_settings
12
12
 
13
13
 
14
14
  class CRUDService:
@@ -17,6 +17,44 @@ class CRUDService:
17
17
  def __init__(self, user):
18
18
  self.user = user
19
19
 
20
+ def _serialize_value(self, value):
21
+ """
22
+ Convertit une valeur en un format sérialisable JSON.
23
+ """
24
+ from datetime import datetime, date, time
25
+ from decimal import Decimal
26
+ import uuid
27
+
28
+ if value is None:
29
+ return None
30
+ elif isinstance(value, (datetime,)):
31
+ return value.isoformat()
32
+ elif isinstance(value, (date,)):
33
+ return value.isoformat()
34
+ elif isinstance(value, (time,)):
35
+ return value.isoformat()
36
+ elif isinstance(value, Decimal):
37
+ return float(value)
38
+ elif isinstance(value, uuid.UUID):
39
+ return str(value)
40
+ elif hasattr(value, 'pk'):
41
+ # ForeignKey ou relation
42
+ return {'id': value.pk, 'str': str(value)}
43
+ elif hasattr(value, 'all'):
44
+ # ManyToMany ou reverse FK - retourner juste le count
45
+ return {'count': value.count()}
46
+ elif isinstance(value, bytes):
47
+ return value.decode('utf-8', errors='replace')
48
+ else:
49
+ # Essayer de retourner directement, sinon convertir en string
50
+ try:
51
+ # Types simples (str, int, float, bool, list, dict)
52
+ import json
53
+ json.dumps(value)
54
+ return value
55
+ except (TypeError, ValueError):
56
+ return str(value)
57
+
20
58
  def can_perform_action(self, app_name: str, model_name: str, action: str) -> bool:
21
59
  """
22
60
  Vérifie si l'utilisateur peut effectuer l'action.
@@ -37,35 +75,67 @@ class CRUDService:
37
75
 
38
76
  def get_model(self, app_name: str, model_name: str):
39
77
  """Récupère une classe de modèle Django."""
78
+ # Essayer d'abord avec app_name fourni
40
79
  try:
41
- return apps.get_model(app_name, model_name)
80
+ model = apps.get_model(app_name, model_name)
81
+ LogUtils.info(f"[CRUD] Modèle trouvé: {app_name}.{model_name}")
82
+ return model
42
83
  except LookupError:
43
- # Essayer de trouver dans toutes les apps
44
- for app_config in apps.get_app_configs():
45
- try:
46
- return app_config.get_model(model_name)
47
- except LookupError:
48
- continue
49
- return None
84
+ pass
85
+
86
+ # Essayer avec le préfixe PROJECT_APPS_PREFIX
87
+ apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX or ''
88
+ if apps_prefix and not app_name.startswith(apps_prefix):
89
+ try:
90
+ full_app_name = f"{apps_prefix}{app_name}"
91
+ model = apps.get_model(full_app_name, model_name)
92
+ LogUtils.info(f"[CRUD] Modèle trouvé avec préfixe: {full_app_name}.{model_name}")
93
+ return model
94
+ except LookupError:
95
+ pass
96
+
97
+ # Chercher dans toutes les apps du projet
98
+ for app_config in apps.get_app_configs():
99
+ # Filtrer par préfixe si configuré
100
+ if apps_prefix and not app_config.name.startswith(apps_prefix):
101
+ continue
102
+
103
+ try:
104
+ model = app_config.get_model(model_name)
105
+ LogUtils.info(f"[CRUD] Modèle trouvé par recherche: {app_config.label}.{model_name}")
106
+ return model
107
+ except LookupError:
108
+ continue
109
+
110
+ LogUtils.warning(f"[CRUD] Modèle non trouvé: {app_name}.{model_name}")
111
+ return None
50
112
 
51
113
  def get_form_class(self, model):
52
114
  """Récupère ou crée une classe de formulaire pour le modèle."""
53
- # Essayer de trouver un formulaire existant
54
115
  app_label = model._meta.app_label
116
+ model_name = model.__name__
55
117
 
56
- # Chercher le formulaire dans l'app
57
- try:
58
- forms_module = __import__(
59
- f'apps.{app_label}.forms',
60
- fromlist=[f'{model.__name__}Form']
61
- )
62
- form_class = getattr(forms_module, f'{model.__name__}Form', None)
63
- if form_class:
64
- return form_class
65
- except (ImportError, AttributeError):
66
- pass
118
+ # Liste des chemins d'import possibles pour les formulaires
119
+ apps_prefix = lucy_assist_settings.PROJECT_APPS_PREFIX or ''
120
+ possible_paths = [
121
+ f'{apps_prefix}{app_label}.forms', # apps.client.forms
122
+ f'{app_label}.forms', # client.forms
123
+ f'apps.{app_label}.forms', # apps.client.forms (legacy)
124
+ ]
125
+
126
+ # Essayer de trouver un formulaire existant
127
+ for path in possible_paths:
128
+ try:
129
+ forms_module = __import__(path, fromlist=[f'{model_name}Form'])
130
+ form_class = getattr(forms_module, f'{model_name}Form', None)
131
+ if form_class:
132
+ LogUtils.info(f"[CRUD] Formulaire trouvé: {path}.{model_name}Form")
133
+ return form_class
134
+ except (ImportError, AttributeError, ModuleNotFoundError):
135
+ continue
67
136
 
68
137
  # Créer un formulaire automatique
138
+ LogUtils.info(f"[CRUD] Formulaire auto-généré pour {model_name}")
69
139
  return modelform_factory(model, fields='__all__')
70
140
 
71
141
  def get_required_fields(self, model) -> List[Dict]:
@@ -332,16 +402,21 @@ class CRUDService:
332
402
  Returns:
333
403
  Dict avec les données de l'objet ou None
334
404
  """
405
+ LogUtils.info(f"[CRUD] get_object: {app_name}.{model_name} #{object_id}")
406
+
335
407
  # Vérifier les permissions
336
408
  if not self.can_perform_action(app_name, model_name, 'view'):
409
+ LogUtils.info(f"[CRUD] get_object: permission refusée pour {model_name}")
337
410
  return None
338
411
 
339
412
  model = self.get_model(app_name, model_name)
340
413
  if not model:
414
+ LogUtils.warning(f"[CRUD] get_object: modèle {model_name} non trouvé")
341
415
  return None
342
416
 
343
417
  try:
344
- obj = model.objects.get(pk=object_id)
418
+ # Utiliser objects.all() pour éviter les problèmes avec les managers customs
419
+ obj = model.objects.all().get(pk=object_id)
345
420
 
346
421
  # Construire un dict avec les données
347
422
  data = {'id': obj.pk, 'str': str(obj)}
@@ -350,15 +425,16 @@ class CRUDService:
350
425
  if hasattr(field, 'verbose_name'):
351
426
  try:
352
427
  value = getattr(obj, field.name)
353
- # Gérer les FK
354
- if hasattr(value, 'pk'):
355
- data[field.name] = {'id': value.pk, 'str': str(value)}
356
- else:
357
- data[field.name] = value
428
+ data[field.name] = self._serialize_value(value)
358
429
  except Exception:
359
430
  pass
360
431
 
432
+ LogUtils.info(f"[CRUD] get_object: {model_name} #{object_id} récupéré avec succès")
361
433
  return data
362
434
 
363
435
  except model.DoesNotExist:
436
+ LogUtils.info(f"[CRUD] get_object: {model_name} #{object_id} non trouvé")
437
+ return None
438
+ except Exception as e:
439
+ LogUtils.error(f"[CRUD] get_object: erreur pour {model_name} #{object_id}: {e}")
364
440
  return None
@@ -10,8 +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 membre, une structure,
14
- un paiement, une adhésion ou tout autre objet métier.
13
+ Utilise ce tool quand l'utilisateur demande de créer un objet métier (client, réservation, etc.).
15
14
  IMPORTANT: Utilise ce tool en priorité quand l'utilisateur demande une création.
16
15
  Ne demande pas de confirmation, exécute directement l'action.""",
17
16
  "input_schema": {
@@ -19,11 +18,11 @@ LUCY_ASSIST_TOOLS = [
19
18
  "properties": {
20
19
  "app_name": {
21
20
  "type": "string",
22
- "description": "Nom de l'application Django (ex: membre, structure, paiement, adhesion)"
21
+ "description": "Nom de l'application Django (ex: client, reservation, franchise)"
23
22
  },
24
23
  "model_name": {
25
24
  "type": "string",
26
- "description": "Nom du modèle Django (ex: Membre, Structure, Paiement, Adhesion)"
25
+ "description": "Nom du modèle Django (ex: Client, Reservation, Franchise)"
27
26
  },
28
27
  "data": {
29
28
  "type": "object",
@@ -37,7 +36,7 @@ LUCY_ASSIST_TOOLS = [
37
36
  {
38
37
  "name": "update_object",
39
38
  "description": """Met à jour un objet existant dans la base de données.
40
- Utilise ce tool quand l'utilisateur demande de modifier un membre, une structure, etc.
39
+ Utilise ce tool quand l'utilisateur demande de modifier un objet existant.
41
40
  IMPORTANT: Exécute directement l'action sans demander de confirmation.""",
42
41
  "input_schema": {
43
42
  "type": "object",
@@ -90,7 +89,7 @@ LUCY_ASSIST_TOOLS = [
90
89
  {
91
90
  "name": "search_objects",
92
91
  "description": """Recherche des objets dans la base de données.
93
- Utilise ce tool pour trouver des membres, structures, paiements, etc.""",
92
+ Utilise ce tool pour trouver des clients, réservations, ou tout autre objet métier.""",
94
93
  "input_schema": {
95
94
  "type": "object",
96
95
  "properties": {
@@ -206,7 +205,7 @@ LUCY_ASSIST_TOOLS = [
206
205
  },
207
206
  "model_name": {
208
207
  "type": "string",
209
- "description": "Nom du modèle Django concerné (si identifiable, ex: Membre, Structure)"
208
+ "description": "Nom du modèle Django concerné (si identifiable, ex: Client, Reservation)"
210
209
  },
211
210
  "action_type": {
212
211
  "type": "string",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-lucy-assist"
7
- version = "1.0.4"
7
+ version = "1.0.6"
8
8
  description = "Assistant IA intelligent Revolucy pour outil métier"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}