django-cfg 1.3.5__py3-none-any.whl → 1.3.9__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.
Files changed (252) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/__init__.py +24 -8
  3. django_cfg/apps/accounts/admin/activity_admin.py +146 -0
  4. django_cfg/apps/accounts/admin/filters.py +98 -22
  5. django_cfg/apps/accounts/admin/group_admin.py +86 -0
  6. django_cfg/apps/accounts/admin/inlines.py +42 -13
  7. django_cfg/apps/accounts/admin/otp_admin.py +115 -0
  8. django_cfg/apps/accounts/admin/registration_admin.py +173 -0
  9. django_cfg/apps/accounts/admin/resources.py +123 -19
  10. django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
  11. django_cfg/apps/accounts/admin/user_admin.py +362 -0
  12. django_cfg/apps/agents/admin/__init__.py +17 -4
  13. django_cfg/apps/agents/admin/execution_admin.py +204 -183
  14. django_cfg/apps/agents/admin/registry_admin.py +230 -255
  15. django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
  16. django_cfg/apps/agents/core/__init__.py +1 -1
  17. django_cfg/apps/agents/core/django_agent.py +221 -0
  18. django_cfg/apps/agents/core/exceptions.py +14 -0
  19. django_cfg/apps/agents/core/orchestrator.py +18 -3
  20. django_cfg/apps/knowbase/admin/__init__.py +1 -1
  21. django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
  22. django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
  23. django_cfg/apps/knowbase/admin/document_admin.py +269 -262
  24. django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
  25. django_cfg/apps/knowbase/config/settings.py +21 -4
  26. django_cfg/apps/knowbase/views/chat_views.py +3 -0
  27. django_cfg/apps/leads/admin/__init__.py +3 -1
  28. django_cfg/apps/leads/admin/leads_admin.py +235 -35
  29. django_cfg/apps/maintenance/admin/__init__.py +2 -2
  30. django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
  31. django_cfg/apps/maintenance/admin/log_admin.py +143 -61
  32. django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
  33. django_cfg/apps/maintenance/admin/site_admin.py +213 -352
  34. django_cfg/apps/newsletter/admin/__init__.py +29 -2
  35. django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
  36. django_cfg/apps/payments/admin/__init__.py +18 -27
  37. django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
  38. django_cfg/apps/payments/admin/balance_admin.py +166 -632
  39. django_cfg/apps/payments/admin/currencies_admin.py +235 -607
  40. django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
  41. django_cfg/apps/payments/admin/filters.py +83 -3
  42. django_cfg/apps/payments/admin/networks_admin.py +258 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +171 -461
  44. django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
  45. django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
  46. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +105 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
  48. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  49. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
  50. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  51. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  52. django_cfg/apps/payments/middleware/api_access.py +32 -6
  53. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
  54. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
  55. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -0
  56. django_cfg/apps/payments/models/balance.py +12 -0
  57. django_cfg/apps/payments/models/currencies.py +106 -32
  58. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  59. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  60. django_cfg/apps/payments/services/core/payment_service.py +1 -1
  61. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  62. django_cfg/apps/payments/services/providers/base.py +95 -39
  63. django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
  64. django_cfg/apps/payments/services/providers/models/base.py +122 -0
  65. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  66. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  67. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  68. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  69. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  70. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  71. django_cfg/apps/payments/services/providers/{nowpayments.py → nowpayments/provider.py} +240 -209
  72. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  73. django_cfg/apps/payments/services/providers/registry.py +4 -32
  74. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  75. django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
  76. django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
  77. django_cfg/apps/payments/tasks/__init__.py +39 -0
  78. django_cfg/apps/payments/tasks/types.py +73 -0
  79. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  80. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  81. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  82. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  83. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  84. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  85. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  86. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  87. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  88. django_cfg/apps/payments/urls_admin.py +1 -1
  89. django_cfg/apps/payments/views/api/currencies.py +5 -5
  90. django_cfg/apps/payments/views/overview/services.py +2 -2
  91. django_cfg/apps/payments/views/serializers/currencies.py +4 -3
  92. django_cfg/apps/support/admin/__init__.py +10 -1
  93. django_cfg/apps/support/admin/support_admin.py +338 -141
  94. django_cfg/apps/tasks/admin/__init__.py +11 -0
  95. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  96. django_cfg/apps/urls.py +1 -2
  97. django_cfg/config.py +1 -1
  98. django_cfg/core/config.py +10 -5
  99. django_cfg/core/generation.py +1 -1
  100. django_cfg/management/commands/__init__.py +13 -1
  101. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  102. django_cfg/management/commands/app_agent_generate.py +342 -0
  103. django_cfg/management/commands/app_agent_info.py +308 -0
  104. django_cfg/management/commands/migrate_all.py +9 -3
  105. django_cfg/management/commands/migrator.py +11 -6
  106. django_cfg/management/commands/rundramatiq.py +3 -2
  107. django_cfg/middleware/__init__.py +0 -2
  108. django_cfg/models/api_keys.py +115 -0
  109. django_cfg/modules/django_admin/__init__.py +64 -0
  110. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  111. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  112. django_cfg/modules/django_admin/decorators/display.py +106 -0
  113. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  114. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  115. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  116. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  117. django_cfg/modules/django_admin/models/__init__.py +20 -0
  118. django_cfg/modules/django_admin/models/action_models.py +33 -0
  119. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  120. django_cfg/modules/django_admin/models/base.py +26 -0
  121. django_cfg/modules/django_admin/models/display_models.py +31 -0
  122. django_cfg/modules/django_admin/utils/badges.py +159 -0
  123. django_cfg/modules/django_admin/utils/displays.py +247 -0
  124. django_cfg/modules/django_app_agent/__init__.py +87 -0
  125. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  126. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  127. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  128. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  129. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  130. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  135. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  136. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  137. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  138. django_cfg/modules/django_app_agent/core/config.py +300 -0
  139. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  140. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  141. django_cfg/modules/django_app_agent/models/base.py +283 -0
  142. django_cfg/modules/django_app_agent/models/context.py +496 -0
  143. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  144. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  145. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  146. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  147. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  153. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  154. django_cfg/modules/django_app_agent/services/base.py +437 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  160. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  165. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  171. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  172. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  188. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  195. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  196. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  197. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  198. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  199. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  200. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  201. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  202. django_cfg/modules/django_currency/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  204. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  205. django_cfg/modules/django_currency/core/converter.py +12 -12
  206. django_cfg/modules/django_currency/database/__init__.py +2 -2
  207. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  208. django_cfg/modules/django_llm/llm/client.py +10 -2
  209. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  210. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  211. django_cfg/modules/django_unfold/dashboard.py +14 -13
  212. django_cfg/modules/django_unfold/models/config.py +1 -1
  213. django_cfg/registry/core.py +3 -0
  214. django_cfg/registry/third_party.py +2 -2
  215. django_cfg/template_archive/django_sample.zip +0 -0
  216. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  217. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/RECORD +224 -118
  218. django_cfg/apps/accounts/admin/activity.py +0 -96
  219. django_cfg/apps/accounts/admin/group.py +0 -17
  220. django_cfg/apps/accounts/admin/otp.py +0 -59
  221. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  222. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  223. django_cfg/apps/accounts/admin/user.py +0 -300
  224. django_cfg/apps/agents/core/agent.py +0 -281
  225. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  226. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  227. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  228. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  229. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  230. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  231. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  232. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  236. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  237. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  239. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  242. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  243. django_cfg/apps/tasks/admin.py +0 -320
  244. django_cfg/middleware/static_nocache.py +0 -55
  245. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  249. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  250. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  251. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  252. {django_cfg-1.3.5.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,31 @@
1
1
  """
2
- Chat admin interfaces with Unfold optimization.
2
+ Chat admin interfaces using Django Admin Utilities.
3
+
4
+ Enhanced chat management with Material Icons and optimized queries.
3
5
  """
4
6
 
5
- from django.contrib import admin
6
- from django.utils.html import format_html
7
+ from django.contrib import admin, messages
7
8
  from django.urls import reverse
9
+ from django.utils.safestring import mark_safe
8
10
  from django.db import models
9
11
  from django.db.models import Count, Sum, Avg, Q
10
12
  from unfold.admin import ModelAdmin, TabularInline
11
- from unfold.decorators import display
12
13
  from unfold.contrib.filters.admin import AutocompleteSelectFilter
13
14
  from django_cfg import ExportMixin
14
15
 
16
+ from django_cfg.modules.django_admin import (
17
+ OptimizedModelAdmin,
18
+ DisplayMixin,
19
+ MoneyDisplayConfig,
20
+ StatusBadgeConfig,
21
+ DateTimeDisplayConfig,
22
+ Icons,
23
+ ActionVariant,
24
+ display,
25
+ action
26
+ )
27
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
28
+
15
29
  from ..models import ChatSession, ChatMessage
16
30
 
17
31
 
@@ -22,20 +36,17 @@ class ChatMessageInline(TabularInline):
22
36
  verbose_name = "Chat Message"
23
37
  verbose_name_plural = "💬 Chat Messages (Read-only)"
24
38
  extra = 0
25
- max_num = 0 # No new messages allowed
26
- can_delete = False # Prevent deletion through inline
27
- show_change_link = True # Allow viewing individual messages
39
+ max_num = 0
40
+ can_delete = False
41
+ show_change_link = True
28
42
 
29
43
  def has_add_permission(self, request, obj=None):
30
- """Disable adding new messages through inline."""
31
44
  return False
32
45
 
33
46
  def has_change_permission(self, request, obj=None):
34
- """Disable editing messages through inline."""
35
47
  return False
36
48
 
37
49
  def has_delete_permission(self, request, obj=None):
38
- """Disable deleting messages through inline."""
39
50
  return False
40
51
 
41
52
  fields = [
@@ -47,41 +58,41 @@ class ChatMessageInline(TabularInline):
47
58
  'cost_display_inline', 'processing_time_inline', 'created_at'
48
59
  ]
49
60
 
50
- # Unfold specific options
51
- hide_title = False # Show titles for better UX
52
- classes = ['collapse'] # Collapsed by default
61
+ hide_title = False
62
+ classes = ['collapse']
53
63
 
54
64
  @display(description="Role")
55
65
  def role_badge_inline(self, obj):
56
66
  """Display message role with color coding for inline."""
57
- role_colors = {
58
- 'user': '#2563eb',
59
- 'assistant': '#059669',
60
- 'system': '#7c3aed'
67
+ role_variants = {
68
+ 'user': 'primary',
69
+ 'assistant': 'success',
70
+ 'system': 'info'
61
71
  }
62
- color = role_colors.get(obj.role, '#6b7280')
63
- return format_html(
64
- '<span style="background-color: {}; color: white; padding: 1px 6px; '
65
- 'border-radius: 8px; font-size: 11px; font-weight: 500;">{}</span>',
66
- color,
67
- obj.role.upper()
72
+ variant = role_variants.get(obj.role, 'secondary')
73
+
74
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.PERSON)
75
+ return StatusBadge.create(
76
+ text=obj.role.upper(),
77
+ variant=variant,
78
+ config=config
68
79
  )
69
80
 
70
81
  @display(description="Content Preview")
71
82
  def content_preview_inline(self, obj):
72
83
  """Shortened content preview for inline display."""
73
84
  if not obj.content:
74
- return "-"
75
- preview = obj.content[:80] + "..." if len(obj.content) > 80 else obj.content
76
- return format_html(
77
- '<div style="max-width: 250px; font-size: 12px; color: #666; '
78
- 'font-family: monospace;">{}</div>',
79
- preview
80
- )
85
+ return ""
86
+ return obj.content[:80] + "..." if len(obj.content) > 80 else obj.content
81
87
 
82
88
  @display(description="Cost (USD)")
83
89
  def cost_display_inline(self, obj):
84
90
  """Display cost with currency formatting for inline."""
91
+ config = MoneyDisplayConfig(
92
+ currency="USD",
93
+ decimal_places=6,
94
+ show_sign=False
95
+ )
85
96
  return f"${obj.cost_usd:.6f}"
86
97
 
87
98
  @display(description="Time")
@@ -100,95 +111,142 @@ class ChatMessageInline(TabularInline):
100
111
 
101
112
 
102
113
  @admin.register(ChatSession)
103
- class ChatSessionAdmin(ModelAdmin, ExportMixin):
104
- """Admin interface for ChatSession model with Unfold styling."""
114
+ class ChatSessionAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
115
+ """Admin interface for ChatSession model using Django Admin Utilities."""
116
+
117
+ # Performance optimization
118
+ select_related_fields = ['user']
105
119
 
106
120
  list_display = [
107
- 'short_uuid', 'title_display', 'user', 'status_badge', 'messages_count',
108
- 'tokens_display', 'cost_display', 'model_name', 'created_at'
121
+ 'title_display', 'user_display', 'status_display', 'messages_count_display',
122
+ 'total_tokens_display', 'total_cost_display', 'last_activity_display', 'created_at_display'
109
123
  ]
110
- ordering = ['-created_at'] # Newest first
124
+ list_display_links = ['title_display']
125
+ ordering = ['-updated_at']
111
126
  inlines = [ChatMessageInline]
112
127
  list_filter = [
113
- 'is_active', 'model_name', 'created_at',
128
+ 'is_active', 'created_at',
114
129
  ('user', AutocompleteSelectFilter)
115
130
  ]
116
131
  search_fields = ['title', 'user__username', 'user__email']
117
132
  autocomplete_fields = ['user']
118
133
  readonly_fields = [
119
- 'id', 'messages_count', 'total_tokens_used', 'total_cost_usd',
120
- 'created_at', 'updated_at', 'avg_tokens_per_message'
134
+ 'id', 'user', 'messages_count', 'total_tokens_used', 'total_cost_usd',
135
+ 'created_at', 'updated_at'
121
136
  ]
122
137
 
123
138
  fieldsets = (
124
- ('Basic Information', {
125
- 'fields': ('id', 'title', 'user', 'is_active')
139
+ ('💬 Session Info', {
140
+ 'fields': ('id', 'title', 'user', 'is_active'),
141
+ 'classes': ('tab',)
126
142
  }),
127
- ('Configuration', {
128
- 'fields': ('model_name', 'temperature', 'max_context_chunks')
143
+ ('📊 Statistics', {
144
+ 'fields': ('message_count', 'total_tokens', 'total_cost_usd'),
145
+ 'classes': ('tab',)
129
146
  }),
130
- ('Statistics', {
131
- 'fields': (
132
- 'messages_count', 'total_tokens_used', 'total_cost_usd',
133
- 'avg_tokens_per_message'
134
- )
135
- }),
136
- ('Timestamps', {
147
+ ('⏰ Activity', {
137
148
  'fields': ('created_at', 'updated_at'),
138
- 'classes': ('collapse',)
139
- })
149
+ 'classes': ('tab',)
150
+ }),
140
151
  )
141
152
 
142
- # Unfold configuration
143
- compressed_fields = True
144
- warn_unsaved_form = True
145
-
146
- def get_queryset(self, request):
147
- """Optimize queryset with select_related."""
148
- return super().get_queryset(request).select_related('user')
153
+ actions = ['activate_sessions', 'deactivate_sessions', 'clear_old_sessions']
149
154
 
150
155
  @display(description="Session Title", ordering="title")
151
156
  def title_display(self, obj):
152
- """Display session title with truncation."""
157
+ """Display session title."""
153
158
  title = obj.title or "Untitled Session"
154
159
  if len(title) > 50:
155
160
  title = title[:47] + "..."
156
- return format_html(
157
- '<div style="font-weight: 500;">{}</div>',
158
- title
161
+
162
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHAT)
163
+ return StatusBadge.create(
164
+ text=title,
165
+ variant="primary",
166
+ config=config
159
167
  )
160
168
 
161
- @display(description="Status", ordering="is_active")
162
- def status_badge(self, obj):
163
- """Display session status with color coding."""
164
- if obj.is_active:
165
- return format_html(
166
- '<span style="color: green; font-weight: bold;">● Active</span>'
167
- )
168
- return format_html(
169
- '<span style="color: gray; font-weight: bold;">● Inactive</span>'
170
- )
169
+ @display(description="User")
170
+ def user_display(self, obj):
171
+ """User display."""
172
+ if not obj.user:
173
+ return "—"
174
+ return self.display_user_simple(obj.user)
171
175
 
172
- @display(description="Tokens", ordering="total_tokens_used")
173
- def tokens_display(self, obj):
174
- """Display token usage with formatting."""
175
- tokens = obj.total_tokens_used
176
+ @display(description="Status")
177
+ def status_display(self, obj):
178
+ """Display session status."""
179
+ if obj.is_active:
180
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
181
+ return StatusBadge.create(text="Active", variant="success", config=config)
182
+ else:
183
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.PAUSE_CIRCLE)
184
+ return StatusBadge.create(text="Inactive", variant="secondary", config=config)
185
+
186
+ @display(description="Messages", ordering="message_count")
187
+ def messages_count_display(self, obj):
188
+ """Display messages count."""
189
+ count = obj.message_count
190
+ return f"{count} messages"
191
+
192
+ @display(description="Tokens", ordering="total_tokens")
193
+ def total_tokens_display(self, obj):
194
+ """Display total tokens with formatting."""
195
+ tokens = obj.total_tokens
176
196
  if tokens > 1000:
177
197
  return f"{tokens/1000:.1f}K"
178
198
  return str(tokens)
179
199
 
180
200
  @display(description="Cost (USD)", ordering="total_cost_usd")
181
- def cost_display(self, obj):
182
- """Display cost with currency formatting."""
183
- return f"${obj.total_cost_usd:.6f}"
184
-
185
- @display(description="Avg Tokens/Message")
186
- def avg_tokens_per_message(self, obj):
187
- """Calculate average tokens per message."""
188
- if obj.messages_count > 0:
189
- avg = obj.total_tokens_used / obj.messages_count
190
- return f"{avg:.0f}"
191
- return "0"
201
+ def total_cost_display(self, obj):
202
+ """Display total cost with currency formatting."""
203
+ config = MoneyDisplayConfig(
204
+ currency="USD",
205
+ decimal_places=6,
206
+ show_sign=False
207
+ )
208
+ return self.display_money_amount(obj, 'total_cost_usd', config)
209
+
210
+ @display(description="Last Activity", ordering="last_activity_at")
211
+ def last_activity_display(self, obj):
212
+ """Last activity time with relative display."""
213
+ if not obj.last_activity_at:
214
+ return "—"
215
+ config = DateTimeDisplayConfig(show_relative=True)
216
+ return self.display_datetime_relative(obj, 'last_activity_at', config)
217
+
218
+ @display(description="Created")
219
+ def created_at_display(self, obj):
220
+ """Created time with relative display."""
221
+ config = DateTimeDisplayConfig(show_relative=True)
222
+ return self.display_datetime_relative(obj, 'created_at', config)
223
+
224
+ @action(description="Activate sessions", variant=ActionVariant.SUCCESS)
225
+ def activate_sessions(self, request, queryset):
226
+ """Activate selected sessions."""
227
+ updated = queryset.update(is_active=True)
228
+ messages.success(request, f"Activated {updated} sessions.")
229
+
230
+ @action(description="Deactivate sessions", variant=ActionVariant.WARNING)
231
+ def deactivate_sessions(self, request, queryset):
232
+ """Deactivate selected sessions."""
233
+ updated = queryset.update(is_active=False)
234
+ messages.warning(request, f"Deactivated {updated} sessions.")
235
+
236
+ @action(description="Clear old sessions", variant=ActionVariant.DANGER)
237
+ def clear_old_sessions(self, request, queryset):
238
+ """Clear old inactive sessions."""
239
+ from datetime import timedelta
240
+ from django.utils import timezone
241
+
242
+ cutoff_date = timezone.now() - timedelta(days=30)
243
+ old_sessions = queryset.filter(is_active=False, last_activity_at__lt=cutoff_date)
244
+ count = old_sessions.count()
245
+
246
+ if count > 0:
247
+ messages.warning(request, f"Clear old sessions functionality not implemented yet. {count} old sessions found.")
248
+ else:
249
+ messages.info(request, "No old sessions found to clear.")
192
250
 
193
251
  def changelist_view(self, request, extra_context=None):
194
252
  """Add session statistics to changelist."""
@@ -198,17 +256,9 @@ class ChatSessionAdmin(ModelAdmin, ExportMixin):
198
256
  stats = queryset.aggregate(
199
257
  total_sessions=Count('id'),
200
258
  active_sessions=Count('id', filter=Q(is_active=True)),
201
- total_messages=Sum('messages_count'),
202
- total_tokens=Sum('total_tokens_used'),
203
- total_cost=Sum('total_cost_usd'),
204
- avg_messages_per_session=Avg('messages_count')
205
- )
206
-
207
- # Model breakdown
208
- model_counts = dict(
209
- queryset.values_list('model_name').annotate(
210
- count=Count('id')
211
- )
259
+ total_messages=Sum('message_count'),
260
+ total_tokens=Sum('total_tokens'),
261
+ total_cost=Sum('total_cost_usd')
212
262
  )
213
263
 
214
264
  extra_context['session_stats'] = {
@@ -216,108 +266,107 @@ class ChatSessionAdmin(ModelAdmin, ExportMixin):
216
266
  'active_sessions': stats['active_sessions'] or 0,
217
267
  'total_messages': stats['total_messages'] or 0,
218
268
  'total_tokens': stats['total_tokens'] or 0,
219
- 'total_cost': f"${(stats['total_cost'] or 0):.6f}",
220
- 'avg_messages_per_session': f"{(stats['avg_messages_per_session'] or 0):.1f}",
221
- 'model_counts': model_counts
269
+ 'total_cost': f"${(stats['total_cost'] or 0):.6f}"
222
270
  }
223
271
 
224
272
  return super().changelist_view(request, extra_context)
225
273
 
226
274
 
227
275
  @admin.register(ChatMessage)
228
- class ChatMessageAdmin(ModelAdmin, ExportMixin):
229
- """Admin interface for ChatMessage model with Unfold styling."""
276
+ class ChatMessageAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
277
+ """Admin interface for ChatMessage model using Django Admin Utilities."""
278
+
279
+ # Performance optimization
280
+ select_related_fields = ['session', 'user']
230
281
 
231
282
  list_display = [
232
- 'short_uuid', 'session_link', 'role_badge', 'content_preview', 'user',
233
- 'tokens_display', 'cost_display', 'processing_time_display', 'created_at'
283
+ 'message_display', 'session_display', 'user_display', 'role_display',
284
+ 'tokens_display', 'cost_display', 'processing_time_display', 'created_at_display'
234
285
  ]
235
- ordering = ['-created_at'] # Newest first
286
+ list_display_links = ['message_display']
287
+ ordering = ['-created_at']
236
288
  list_filter = [
237
- 'role', 'model_name', 'finish_reason', 'created_at',
289
+ 'role', 'created_at',
238
290
  ('user', AutocompleteSelectFilter),
239
291
  ('session', AutocompleteSelectFilter)
240
292
  ]
241
- search_fields = ['session__title', 'user__username', 'content']
293
+ search_fields = ['content', 'user__username', 'session__title']
294
+ autocomplete_fields = ['user', 'session']
242
295
  readonly_fields = [
243
- 'id', 'tokens_used', 'cost_usd', 'processing_time_ms',
244
- 'created_at', 'updated_at', 'content_stats'
296
+ 'id', 'user', 'tokens_used', 'cost_usd', 'processing_time_ms',
297
+ 'created_at', 'updated_at', 'content_preview'
245
298
  ]
246
299
 
247
300
  fieldsets = (
248
- ('Basic Information', {
249
- 'fields': ('id', 'session', 'user', 'role')
250
- }),
251
- ('Content', {
252
- 'fields': ('content', 'content_stats')
301
+ ('💬 Message Info', {
302
+ 'fields': ('id', 'session', 'user', 'role'),
303
+ 'classes': ('tab',)
253
304
  }),
254
- ('Context', {
255
- 'fields': ('context_chunks',),
256
- 'classes': ('collapse',)
305
+ ('📝 Content', {
306
+ 'fields': ('content_preview', 'content'),
307
+ 'classes': ('tab',)
257
308
  }),
258
- ('Usage Statistics', {
259
- 'fields': ('tokens_used', 'cost_usd', 'processing_time_ms')
309
+ ('📊 Metrics', {
310
+ 'fields': ('tokens_used', 'cost_usd', 'processing_time_ms'),
311
+ 'classes': ('tab',)
260
312
  }),
261
- ('Response Metadata', {
262
- 'fields': ('model_name', 'finish_reason'),
263
- 'classes': ('collapse',)
264
- }),
265
- ('Timestamps', {
313
+ (' Timestamps', {
266
314
  'fields': ('created_at', 'updated_at'),
267
- 'classes': ('collapse',)
315
+ 'classes': ('tab', 'collapse')
268
316
  })
269
317
  )
270
318
 
271
- # Unfold configuration
272
- compressed_fields = True
273
- warn_unsaved_form = True
319
+ actions = ['delete_user_messages', 'delete_assistant_messages']
274
320
 
275
- def get_queryset(self, request):
276
- """Optimize queryset with select_related."""
277
- return super().get_queryset(request).select_related('session', 'user')
321
+ @display(description="Message", ordering="id")
322
+ def message_display(self, obj):
323
+ """Display message identifier."""
324
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.MESSAGE)
325
+ return StatusBadge.create(
326
+ text=f"#{str(obj.id)[:8]}",
327
+ variant="secondary",
328
+ config=config
329
+ )
278
330
 
279
331
  @display(description="Session", ordering="session__title")
280
- def session_link(self, obj):
281
- """Display session title with admin link."""
282
- url = reverse('admin:django_cfg_knowbase_chatsession_change', args=[obj.session.id])
283
- title = obj.session.title or "Untitled Session"
284
- if len(title) > 30:
285
- title = title[:27] + "..."
286
- return format_html(
287
- '<a href="{}" style="text-decoration: none;">{}</a>',
288
- url,
289
- title
290
- )
332
+ def session_display(self, obj):
333
+ """Display session title."""
334
+ return obj.session.title or "Untitled Session"
291
335
 
292
- @display(description="Role", ordering="role")
293
- def role_badge(self, obj):
336
+ @display(description="User")
337
+ def user_display(self, obj):
338
+ """User display."""
339
+ if not obj.user:
340
+ return "—"
341
+ return self.display_user_simple(obj.user)
342
+
343
+ @display(description="Role")
344
+ def role_display(self, obj):
294
345
  """Display message role with color coding."""
295
- role_colors = {
296
- 'user': '#2563eb',
297
- 'assistant': '#059669',
298
- 'system': '#7c3aed'
346
+ role_variants = {
347
+ 'user': 'primary',
348
+ 'assistant': 'success',
349
+ 'system': 'info'
299
350
  }
300
- color = role_colors.get(obj.role, '#6b7280')
301
- return format_html(
302
- '<span style="background-color: {}; color: white; padding: 2px 8px; '
303
- 'border-radius: 12px; font-size: 12px; font-weight: 500;">{}</span>',
304
- color,
305
- obj.role.upper()
306
- )
307
-
308
- @display(description="Content Preview")
309
- def content_preview(self, obj):
310
- """Display content preview with truncation."""
311
- preview = obj.content[:100] + "..." if len(obj.content) > 100 else obj.content
312
- return format_html(
313
- '<div style="max-width: 300px; word-wrap: break-word; '
314
- 'font-family: monospace; font-size: 13px;">{}</div>',
315
- preview
351
+ variant = role_variants.get(obj.role, 'secondary')
352
+
353
+ role_icons = {
354
+ 'user': Icons.PERSON,
355
+ 'assistant': Icons.SMART_TOY,
356
+ 'system': Icons.SETTINGS
357
+ }
358
+ icon = role_icons.get(obj.role, Icons.MESSAGE)
359
+
360
+ config = StatusBadgeConfig(show_icons=True, icon=icon)
361
+ return StatusBadge.create(
362
+ text=obj.role.title(),
363
+ variant=variant,
364
+ config=config
316
365
  )
317
366
 
318
367
  @display(description="Tokens", ordering="tokens_used")
319
368
  def tokens_display(self, obj):
320
- """Display token usage with formatting."""
369
+ """Display tokens used with formatting."""
321
370
  tokens = obj.tokens_used
322
371
  if tokens > 1000:
323
372
  return f"{tokens/1000:.1f}K"
@@ -326,11 +375,16 @@ class ChatMessageAdmin(ModelAdmin, ExportMixin):
326
375
  @display(description="Cost (USD)", ordering="cost_usd")
327
376
  def cost_display(self, obj):
328
377
  """Display cost with currency formatting."""
329
- return f"${obj.cost_usd:.6f}"
378
+ config = MoneyDisplayConfig(
379
+ currency="USD",
380
+ decimal_places=6,
381
+ show_sign=False
382
+ )
383
+ return self.display_money_amount(obj, 'cost_usd', config)
330
384
 
331
385
  @display(description="Processing Time", ordering="processing_time_ms")
332
386
  def processing_time_display(self, obj):
333
- """Display processing time in readable format."""
387
+ """Display processing time."""
334
388
  ms = obj.processing_time_ms
335
389
  if ms < 1000:
336
390
  return f"{ms}ms"
@@ -338,18 +392,38 @@ class ChatMessageAdmin(ModelAdmin, ExportMixin):
338
392
  seconds = ms / 1000
339
393
  return f"{seconds:.1f}s"
340
394
 
341
- @display(description="Content Statistics")
342
- def content_stats(self, obj):
343
- """Display content statistics."""
344
- char_count = len(obj.content)
345
- word_count = len(obj.content.split())
346
- return format_html(
347
- '<div style="font-size: 12px; color: #6b7280;">'
348
- 'Characters: {} | Words: {} | Context Chunks: {}</div>',
349
- char_count,
350
- word_count,
351
- len(obj.context_chunks)
352
- )
395
+ @display(description="Created")
396
+ def created_at_display(self, obj):
397
+ """Created time with relative display."""
398
+ config = DateTimeDisplayConfig(show_relative=True)
399
+ return self.display_datetime_relative(obj, 'created_at', config)
400
+
401
+ @display(description="Content Preview")
402
+ def content_preview(self, obj):
403
+ """Display content preview with truncation."""
404
+ return obj.content[:200] + "..." if len(obj.content) > 200 else obj.content
405
+
406
+ @action(description="Delete user messages", variant=ActionVariant.DANGER)
407
+ def delete_user_messages(self, request, queryset):
408
+ """Delete user messages from selection."""
409
+ user_messages = queryset.filter(role='user')
410
+ count = user_messages.count()
411
+
412
+ if count > 0:
413
+ messages.warning(request, f"Delete user messages functionality not implemented yet. {count} user messages selected.")
414
+ else:
415
+ messages.info(request, "No user messages in selection.")
416
+
417
+ @action(description="Delete assistant messages", variant=ActionVariant.DANGER)
418
+ def delete_assistant_messages(self, request, queryset):
419
+ """Delete assistant messages from selection."""
420
+ assistant_messages = queryset.filter(role='assistant')
421
+ count = assistant_messages.count()
422
+
423
+ if count > 0:
424
+ messages.warning(request, f"Delete assistant messages functionality not implemented yet. {count} assistant messages selected.")
425
+ else:
426
+ messages.info(request, "No assistant messages in selection.")
353
427
 
354
428
  def changelist_view(self, request, extra_context=None):
355
429
  """Add message statistics to changelist."""
@@ -360,28 +434,20 @@ class ChatMessageAdmin(ModelAdmin, ExportMixin):
360
434
  total_messages=Count('id'),
361
435
  user_messages=Count('id', filter=Q(role='user')),
362
436
  assistant_messages=Count('id', filter=Q(role='assistant')),
437
+ system_messages=Count('id', filter=Q(role='system')),
363
438
  total_tokens=Sum('tokens_used'),
364
439
  total_cost=Sum('cost_usd'),
365
- avg_processing_time=Avg('processing_time_ms'),
366
- avg_tokens_per_message=Avg('tokens_used')
367
- )
368
-
369
- # Role breakdown
370
- role_counts = dict(
371
- queryset.values_list('role').annotate(
372
- count=Count('id')
373
- )
440
+ avg_processing_time=Avg('processing_time_ms')
374
441
  )
375
442
 
376
443
  extra_context['message_stats'] = {
377
444
  'total_messages': stats['total_messages'] or 0,
378
445
  'user_messages': stats['user_messages'] or 0,
379
446
  'assistant_messages': stats['assistant_messages'] or 0,
447
+ 'system_messages': stats['system_messages'] or 0,
380
448
  'total_tokens': stats['total_tokens'] or 0,
381
449
  'total_cost': f"${(stats['total_cost'] or 0):.6f}",
382
- 'avg_processing_time': f"{(stats['avg_processing_time'] or 0):.0f}ms",
383
- 'avg_tokens_per_message': f"{(stats['avg_tokens_per_message'] or 0):.0f}",
384
- 'role_counts': role_counts
450
+ 'avg_processing_time': f"{(stats['avg_processing_time'] or 0):.0f}ms"
385
451
  }
386
452
 
387
453
  return super().changelist_view(request, extra_context)