django-cfg 1.3.7__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 (251) 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/config.py +1 -1
  97. django_cfg/core/config.py +10 -5
  98. django_cfg/core/generation.py +1 -1
  99. django_cfg/management/commands/__init__.py +13 -1
  100. django_cfg/management/commands/app_agent_diagnose.py +470 -0
  101. django_cfg/management/commands/app_agent_generate.py +342 -0
  102. django_cfg/management/commands/app_agent_info.py +308 -0
  103. django_cfg/management/commands/migrate_all.py +9 -3
  104. django_cfg/management/commands/migrator.py +11 -6
  105. django_cfg/management/commands/rundramatiq.py +3 -2
  106. django_cfg/middleware/__init__.py +0 -2
  107. django_cfg/models/api_keys.py +115 -0
  108. django_cfg/modules/django_admin/__init__.py +64 -0
  109. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  110. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  111. django_cfg/modules/django_admin/decorators/display.py +106 -0
  112. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  113. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  114. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  115. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  116. django_cfg/modules/django_admin/models/__init__.py +20 -0
  117. django_cfg/modules/django_admin/models/action_models.py +33 -0
  118. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  119. django_cfg/modules/django_admin/models/base.py +26 -0
  120. django_cfg/modules/django_admin/models/display_models.py +31 -0
  121. django_cfg/modules/django_admin/utils/badges.py +159 -0
  122. django_cfg/modules/django_admin/utils/displays.py +247 -0
  123. django_cfg/modules/django_app_agent/__init__.py +87 -0
  124. django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
  125. django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
  126. django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
  127. django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
  128. django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
  129. django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
  130. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
  131. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
  132. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
  133. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
  134. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
  135. django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
  136. django_cfg/modules/django_app_agent/core/__init__.py +33 -0
  137. django_cfg/modules/django_app_agent/core/config.py +300 -0
  138. django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
  139. django_cfg/modules/django_app_agent/models/__init__.py +71 -0
  140. django_cfg/modules/django_app_agent/models/base.py +283 -0
  141. django_cfg/modules/django_app_agent/models/context.py +496 -0
  142. django_cfg/modules/django_app_agent/models/enums.py +481 -0
  143. django_cfg/modules/django_app_agent/models/requests.py +500 -0
  144. django_cfg/modules/django_app_agent/models/responses.py +585 -0
  145. django_cfg/modules/django_app_agent/pytest.ini +6 -0
  146. django_cfg/modules/django_app_agent/services/__init__.py +42 -0
  147. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
  148. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
  149. django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
  150. django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
  151. django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
  152. django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
  153. django_cfg/modules/django_app_agent/services/base.py +437 -0
  154. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
  155. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
  156. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
  157. django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
  158. django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
  159. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
  160. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
  161. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
  162. django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
  163. django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
  164. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
  165. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
  166. django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
  167. django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
  168. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
  169. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
  170. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
  171. django_cfg/modules/django_app_agent/services/report_service.py +332 -0
  172. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
  173. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
  174. django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
  175. django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
  176. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
  177. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
  178. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
  179. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
  180. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
  181. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
  182. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
  183. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
  184. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
  185. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
  186. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
  187. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
  188. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
  189. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
  190. django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
  191. django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
  192. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
  193. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
  194. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
  195. django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
  196. django_cfg/modules/django_app_agent/ui/cli.py +419 -0
  197. django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
  198. django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
  199. django_cfg/modules/django_app_agent/utils/logging.py +360 -0
  200. django_cfg/modules/django_app_agent/utils/validation.py +417 -0
  201. django_cfg/modules/django_currency/__init__.py +2 -2
  202. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  203. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  204. django_cfg/modules/django_currency/core/converter.py +12 -12
  205. django_cfg/modules/django_currency/database/__init__.py +2 -2
  206. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  207. django_cfg/modules/django_llm/llm/client.py +10 -2
  208. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  209. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  210. django_cfg/modules/django_unfold/dashboard.py +14 -13
  211. django_cfg/modules/django_unfold/models/config.py +1 -1
  212. django_cfg/registry/core.py +3 -0
  213. django_cfg/registry/third_party.py +2 -2
  214. django_cfg/template_archive/django_sample.zip +0 -0
  215. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/METADATA +2 -1
  216. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
  217. django_cfg/apps/accounts/admin/activity.py +0 -96
  218. django_cfg/apps/accounts/admin/group.py +0 -17
  219. django_cfg/apps/accounts/admin/otp.py +0 -59
  220. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  221. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  222. django_cfg/apps/accounts/admin/user.py +0 -300
  223. django_cfg/apps/agents/core/agent.py +0 -281
  224. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  225. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  226. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  227. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  228. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  229. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  230. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  231. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  232. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  233. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  234. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  235. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  236. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  237. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  238. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  239. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  240. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  241. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  242. django_cfg/apps/tasks/admin.py +0 -320
  243. django_cfg/middleware/static_nocache.py +0 -55
  244. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  245. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  246. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  247. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  248. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  249. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/WHEEL +0 -0
  250. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
  251. {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,44 @@
1
- from django.contrib import admin
2
- from unfold.admin import ModelAdmin, TabularInline
3
- from unfold.decorators import action
4
- from django.utils.timesince import timesince
5
- from django.utils.html import format_html
1
+ """
2
+ Support admin interfaces using Django Admin Utilities.
3
+
4
+ Enhanced support ticket management with Material Icons and optimized queries.
5
+ """
6
+
7
+ from django.contrib import admin, messages
6
8
  from django.urls import reverse
7
9
  from django.shortcuts import redirect
8
10
  from django.http import HttpRequest
9
11
  from django.utils.translation import gettext_lazy as _
12
+ from django.utils.timesince import timesince
13
+ from django.db import models
14
+ from django.db.models import Count, Q
15
+ from unfold.admin import ModelAdmin, TabularInline
10
16
  from django_cfg import ExportMixin, ExportForm
11
17
 
18
+ from django_cfg.modules.django_admin import (
19
+ OptimizedModelAdmin,
20
+ DisplayMixin,
21
+ StatusBadgeConfig,
22
+ DateTimeDisplayConfig,
23
+ Icons,
24
+ ActionVariant,
25
+ display,
26
+ action
27
+ )
28
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
29
+
12
30
  from ..models import Ticket, Message
13
31
  from .filters import TicketUserEmailFilter, TicketUserNameFilter, MessageSenderEmailFilter
14
32
  from .resources import TicketResource, MessageResource
15
- from django import forms
33
+
16
34
 
17
35
  class MessageInline(TabularInline):
18
36
  """Read-only inline for viewing messages. Use Chat interface for replies."""
19
37
 
20
38
  model = Message
21
39
  extra = 0
22
- fields = ("sender_avatar", "created_at", "text")
23
- readonly_fields = ("sender_avatar", "created_at", "text")
40
+ fields = ["sender_display", "created_at", "text_preview"]
41
+ readonly_fields = ["sender_display", "created_at", "text_preview"]
24
42
  show_change_link = False
25
43
  classes = ('collapse',)
26
44
 
@@ -32,174 +50,353 @@ class MessageInline(TabularInline):
32
50
  """Disable deleting messages through admin."""
33
51
  return False
34
52
 
35
- def sender_avatar(self, obj):
36
- """Display sender avatar with fallback to initials."""
37
- if obj.sender.avatar:
38
- return format_html(
39
- '<img src="{}" style="width: 24px; height: 24px; border-radius: 50%; object-fit: cover;" />',
40
- obj.sender.avatar.url
41
- )
53
+ @display(description="Sender")
54
+ def sender_display(self, obj):
55
+ """Display sender with badge."""
56
+ if not obj.sender:
57
+ return ""
58
+
59
+ # Determine sender type and variant
60
+ if obj.sender.is_superuser:
61
+ variant = "danger"
62
+ icon = Icons.ADMIN_PANEL_SETTINGS
63
+ elif obj.sender.is_staff:
64
+ variant = "primary"
65
+ icon = Icons.SUPPORT_AGENT
42
66
  else:
43
- initials = obj.sender.__class__.objects.get_initials(obj.sender)
44
- bg_color = '#0d6efd' if obj.sender.is_staff else '#6f42c1' if obj.sender.is_superuser else '#198754'
45
-
46
- return format_html(
47
- '<div style="width: 24px; height: 24px; border-radius: 50%; background: {}; '
48
- 'color: white; display: flex; align-items: center; justify-content: center; '
49
- 'font-weight: bold; font-size: 10px;">{}</div>',
50
- bg_color, initials
51
- )
52
- sender_avatar.short_description = "Sender"
67
+ variant = "info"
68
+ icon = Icons.PERSON
69
+
70
+ config = StatusBadgeConfig(show_icons=True, icon=icon)
71
+ return StatusBadge.create(
72
+ text=obj.sender.get_full_name() or obj.sender.username,
73
+ variant=variant,
74
+ config=config
75
+ )
76
+
77
+ @display(description="Message")
78
+ def text_preview(self, obj):
79
+ """Display message preview."""
80
+ if not obj.text:
81
+ return "—"
82
+
83
+ preview = obj.text[:100]
84
+ if len(obj.text) > 100:
85
+ preview += "..."
86
+
87
+ return preview
53
88
 
54
89
 
55
90
  @admin.register(Ticket)
56
- class TicketAdmin(ModelAdmin, ExportMixin):
91
+ class TicketAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
92
+ """Admin interface for Ticket using Django Admin Utilities."""
93
+
94
+ # Performance optimization
95
+ select_related_fields = ['user']
96
+
57
97
  # Export-only configuration
58
98
  resource_class = TicketResource
59
99
  export_form_class = ExportForm
60
- list_display = ("user_avatar", "uuid_link", "subject", "status", "last_message_short", "last_message_ago", "chat_link", "created_at")
61
- list_display_links = ("subject",)
62
- list_editable = ("status",)
63
- search_fields = ("uuid", "user__username", "user__email", "subject")
64
- list_filter = ("status", "created_at", TicketUserEmailFilter, TicketUserNameFilter)
65
- ordering = ("-created_at",)
100
+
101
+ list_display = [
102
+ "user_display", "uuid_display", "subject_display", "status_display",
103
+ "last_message_display", "last_message_ago_display", "chat_link_display", "created_at_display"
104
+ ]
105
+ list_display_links = ["subject_display"]
106
+ ordering = ["-created_at"]
107
+ search_fields = ["uuid", "user__username", "user__email", "subject"]
108
+ list_filter = ["status", "created_at", TicketUserEmailFilter, TicketUserNameFilter]
66
109
  inlines = [MessageInline]
110
+ autocomplete_fields = ["user"]
111
+
112
+ actions = ["mark_as_open", "mark_as_waiting_for_user", "mark_as_waiting_for_admin", "mark_as_resolved", "mark_as_closed"]
113
+
67
114
  def get_readonly_fields(self, request, obj=None):
68
115
  """Different readonly fields for add/change forms."""
69
116
  if obj is None: # Adding new ticket
70
117
  return ("uuid", "created_at")
71
118
  else: # Editing existing ticket
72
119
  return ("uuid", "user", "created_at")
73
- actions_detail = ["open_chat"]
74
- autocomplete_fields = ["user"]
120
+
75
121
  def get_fieldsets(self, request, obj=None):
76
122
  """Different fieldsets for add/change forms."""
77
123
  if obj is None: # Adding new ticket
78
124
  return (
79
- (None, {
80
- "fields": ("user", "subject", "status")
125
+ ('🎫 New Ticket', {
126
+ "fields": ("user", "subject", "status"),
127
+ "classes": ("tab",)
81
128
  }),
82
129
  )
83
130
  else: # Editing existing ticket
84
131
  return (
85
- (None, {
86
- "fields": (("uuid", "user"), "subject", "status", "created_at")
132
+ ('🎫 Ticket Information', {
133
+ "fields": (("uuid", "user"), "subject", "status", "created_at"),
134
+ "classes": ("tab",)
87
135
  }),
88
- ("💬 Chat Interface", {
89
- "description": "Use the beautiful Chat interface to reply to this ticket. Click the '💬 Chat' button above.",
136
+ ('💬 Chat Interface', {
137
+ "description": "Use the Chat interface to reply to this ticket. Click the Chat button.",
90
138
  "fields": (),
91
- "classes": ("collapse",)
139
+ "classes": ("tab", "collapse")
92
140
  }),
93
141
  )
94
142
 
143
+ @display(description="User")
144
+ def user_display(self, obj: Ticket) -> str:
145
+ """Display user with avatar representation."""
146
+ if not obj.user:
147
+ return "—"
148
+
149
+ # Use simple user display from DisplayMixin
150
+ return self.display_user_simple(obj.user)
95
151
 
96
- def user_avatar(self, obj):
97
- """Display user avatar with fallback to initials."""
98
- if obj.user.avatar:
99
- return format_html(
100
- '<img src="{}" style="width: 32px; height: 32px; border-radius: 50%; object-fit: cover;" />',
101
- obj.user.avatar.url
102
- )
103
- else:
104
- initials = obj.user.__class__.objects.get_initials(obj.user)
105
- bg_color = '#0d6efd' if obj.user.is_staff else '#6f42c1' if obj.user.is_superuser else '#198754'
106
-
107
- return format_html(
108
- '<div style="width: 32px; height: 32px; border-radius: 50%; background: {}; '
109
- 'color: white; display: flex; align-items: center; justify-content: center; '
110
- 'font-weight: bold; font-size: 12px;">{}</div>',
111
- bg_color, initials
112
- )
113
- user_avatar.short_description = "User"
114
-
115
- def uuid_link(self, obj):
116
- """Make UUID clickable link to ticket detail."""
117
- url = reverse('admin:django_cfg_support_ticket_change', args=[obj.uuid])
118
- return format_html(
119
- '<a href="{}" style="font-family: monospace; font-size: 11px; background: #f8f9fa; '
120
- 'padding: 2px 4px; border-radius: 3px; text-decoration: none; color: #0d6efd;">{}</a>',
121
- url, str(obj.uuid)[:8] + '...'
152
+ @display(description="UUID", ordering="uuid")
153
+ def uuid_display(self, obj: Ticket) -> str:
154
+ """Display ticket UUID."""
155
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CONFIRMATION_NUMBER)
156
+ return StatusBadge.create(
157
+ text=str(obj.uuid)[:8] + "...",
158
+ variant="secondary",
159
+ config=config
122
160
  )
123
- uuid_link.short_description = "ID"
124
-
125
-
126
- def last_message_short(self, obj):
127
- msg = obj.last_message
128
- if msg:
129
- return (msg.text[:40] + '...') if len(msg.text) > 40 else msg.text
130
- return "-"
131
- last_message_short.short_description = "Last Message"
132
-
133
- def last_message_ago(self, obj):
134
- msg = obj.last_message
135
- if msg:
136
- return timesince(msg.created_at) + ' ago'
137
- return "-"
138
- last_message_ago.short_description = "Last Reply"
139
-
140
- def chat_link(self, obj):
141
- """Display chat link button in list view."""
142
- chat_url = reverse('cfg_support:ticket-chat', kwargs={'ticket_uuid': obj.uuid})
143
- return format_html(
144
- '<a href="{}" target="_blank" class="btn btn-sm btn-primary" '
145
- 'style="background: #0d6efd; color: white; padding: 4px 8px; '
146
- 'border-radius: 4px; text-decoration: none; font-size: 11px; '
147
- 'display: inline-flex; align-items: center; gap: 4px;">'
148
- '<svg width="12" height="12" fill="currentColor" viewBox="0 0 16 16">'
149
- '<path d="M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z"/>'
150
- '</svg>Chat</a>',
151
- chat_url
161
+
162
+ @display(description="Subject", ordering="subject")
163
+ def subject_display(self, obj: Ticket) -> str:
164
+ """Display ticket subject."""
165
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.SUBJECT)
166
+ return StatusBadge.create(
167
+ text=obj.subject,
168
+ variant="primary",
169
+ config=config
170
+ )
171
+
172
+ @display(description="Status")
173
+ def status_display(self, obj: Ticket) -> str:
174
+ """Display ticket status with color coding."""
175
+ status_config = StatusBadgeConfig(
176
+ custom_mappings={
177
+ 'open': 'info',
178
+ 'waiting_for_user': 'warning',
179
+ 'waiting_for_admin': 'primary',
180
+ 'resolved': 'success',
181
+ 'closed': 'secondary'
182
+ },
183
+ show_icons=True,
184
+ icon=Icons.NEW_RELEASES if obj.status == 'open' else Icons.PENDING if obj.status == 'waiting_for_user' else Icons.SUPPORT_AGENT if obj.status == 'waiting_for_admin' else Icons.CHECK_CIRCLE if obj.status == 'resolved' else Icons.ARCHIVE
185
+ )
186
+ return self.display_status_auto(obj, 'status', status_config)
187
+
188
+ @display(description="Last Message")
189
+ def last_message_display(self, obj: Ticket) -> str:
190
+ """Display last message preview."""
191
+ last_message = obj.message_set.order_by('-created_at').first()
192
+ if not last_message:
193
+ return "No messages"
194
+
195
+ preview = last_message.text[:50]
196
+ if len(last_message.text) > 50:
197
+ preview += "..."
198
+
199
+ return preview
200
+
201
+ @display(description="Last Activity")
202
+ def last_message_ago_display(self, obj: Ticket) -> str:
203
+ """Display time since last message."""
204
+ last_message = obj.message_set.order_by('-created_at').first()
205
+ if not last_message:
206
+ return "—"
207
+
208
+ config = DateTimeDisplayConfig(show_relative=True)
209
+ return self.display_datetime_relative(last_message, 'created_at', config)
210
+
211
+ @display(description="Chat")
212
+ def chat_link_display(self, obj: Ticket) -> str:
213
+ """Display chat link."""
214
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHAT)
215
+ return StatusBadge.create(
216
+ text="Open Chat",
217
+ variant="info",
218
+ config=config
152
219
  )
153
- chat_link.short_description = "💬"
154
-
155
- @action(
156
- description=_("💬 Open Chat Interface"),
157
- url_path="chat",
158
- attrs={"target": "_blank", "class": "btn btn-primary"},
159
- )
160
- def open_chat(self, request: HttpRequest, object_id: int):
161
- """Open the beautiful chat interface for this ticket."""
162
- ticket = Ticket.objects.get(pk=object_id)
163
- chat_url = reverse('cfg_support:ticket-chat', kwargs={'ticket_uuid': ticket.uuid})
164
- return redirect(chat_url)
220
+
221
+ @display(description="Created")
222
+ def created_at_display(self, obj: Ticket) -> str:
223
+ """Created time with relative display."""
224
+ config = DateTimeDisplayConfig(show_relative=True)
225
+ return self.display_datetime_relative(obj, 'created_at', config)
226
+
227
+ @action(description="Mark as open", variant=ActionVariant.INFO)
228
+ def mark_as_open(self, request: HttpRequest, queryset) -> None:
229
+ """Mark selected tickets as open."""
230
+ count = queryset.update(status='open')
231
+ messages.info(request, f"Marked {count} tickets as open.")
232
+
233
+ @action(description="Mark as waiting for user", variant=ActionVariant.WARNING)
234
+ def mark_as_waiting_for_user(self, request: HttpRequest, queryset) -> None:
235
+ """Mark selected tickets as waiting for user."""
236
+ count = queryset.update(status='waiting_for_user')
237
+ messages.warning(request, f"Marked {count} tickets as waiting for user.")
238
+
239
+ @action(description="Mark as waiting for admin", variant=ActionVariant.PRIMARY)
240
+ def mark_as_waiting_for_admin(self, request: HttpRequest, queryset) -> None:
241
+ """Mark selected tickets as waiting for admin."""
242
+ count = queryset.update(status='waiting_for_admin')
243
+ messages.info(request, f"Marked {count} tickets as waiting for admin.")
244
+
245
+ @action(description="Mark as resolved", variant=ActionVariant.SUCCESS)
246
+ def mark_as_resolved(self, request: HttpRequest, queryset) -> None:
247
+ """Mark selected tickets as resolved."""
248
+ count = queryset.update(status='resolved')
249
+ messages.success(request, f"Marked {count} tickets as resolved.")
250
+
251
+ @action(description="Mark as closed", variant=ActionVariant.DANGER)
252
+ def mark_as_closed(self, request: HttpRequest, queryset) -> None:
253
+ """Mark selected tickets as closed."""
254
+ count = queryset.update(status='closed')
255
+ messages.error(request, f"Marked {count} tickets as closed.")
256
+
257
+ def changelist_view(self, request, extra_context=None):
258
+ """Add ticket statistics to changelist."""
259
+ extra_context = extra_context or {}
260
+
261
+ queryset = self.get_queryset(request)
262
+ stats = queryset.aggregate(
263
+ total_tickets=Count('uuid'),
264
+ open_tickets=Count('uuid', filter=Q(status='open')),
265
+ waiting_for_user_tickets=Count('uuid', filter=Q(status='waiting_for_user')),
266
+ waiting_for_admin_tickets=Count('uuid', filter=Q(status='waiting_for_admin')),
267
+ resolved_tickets=Count('uuid', filter=Q(status='resolved')),
268
+ closed_tickets=Count('uuid', filter=Q(status='closed'))
269
+ )
270
+
271
+ extra_context['ticket_stats'] = {
272
+ 'total_tickets': stats['total_tickets'] or 0,
273
+ 'open_tickets': stats['open_tickets'] or 0,
274
+ 'waiting_for_user_tickets': stats['waiting_for_user_tickets'] or 0,
275
+ 'waiting_for_admin_tickets': stats['waiting_for_admin_tickets'] or 0,
276
+ 'resolved_tickets': stats['resolved_tickets'] or 0,
277
+ 'closed_tickets': stats['closed_tickets'] or 0
278
+ }
279
+
280
+ return super().changelist_view(request, extra_context)
281
+
165
282
 
166
283
  @admin.register(Message)
167
- class MessageAdmin(ModelAdmin, ExportMixin):
284
+ class MessageAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin, ExportMixin):
285
+ """Admin interface for Message using Django Admin Utilities."""
286
+
287
+ # Performance optimization
288
+ select_related_fields = ['ticket', 'sender']
289
+
168
290
  # Export-only configuration
169
291
  resource_class = MessageResource
170
292
  export_form_class = ExportForm
171
- list_display = ("sender_avatar", "uuid", "ticket", "text_short", "created_at")
172
- list_display_links = ("uuid", "ticket")
173
- search_fields = ("uuid", "ticket__subject", "sender__username", "sender__email", "text")
174
- list_filter = ("created_at", MessageSenderEmailFilter)
175
- ordering = ("-created_at",)
176
- readonly_fields = ("uuid", "ticket", "sender", "created_at")
177
- fieldsets = (
178
- (None, {
179
- "fields": (("uuid", "ticket"), "sender", "text", "created_at")
293
+
294
+ list_display = [
295
+ "ticket_display", "sender_display", "text_preview", "created_at_display"
296
+ ]
297
+ list_display_links = ["text_preview"]
298
+ ordering = ["-created_at"]
299
+ search_fields = ["ticket__uuid", "ticket__subject", "sender__username", "sender__email", "text"]
300
+ list_filter = ["created_at", "ticket__status", MessageSenderEmailFilter]
301
+ readonly_fields = ["ticket", "sender", "created_at"]
302
+
303
+ fieldsets = [
304
+ ('💬 Message Information', {
305
+ 'fields': ['ticket', 'sender', 'text'],
306
+ 'classes': ('tab',)
180
307
  }),
181
- )
182
-
183
- def sender_avatar(self, obj):
184
- """Display sender avatar with fallback to initials."""
185
- if obj.sender.avatar:
186
- return format_html(
187
- '<img src="{}" style="width: 32px; height: 32px; border-radius: 50%; object-fit: cover;" />',
188
- obj.sender.avatar.url
189
- )
308
+ ('⏰ Timestamps', {
309
+ 'fields': ['created_at'],
310
+ 'classes': ('tab', 'collapse')
311
+ })
312
+ ]
313
+
314
+ def has_add_permission(self, request):
315
+ """Disable adding messages through admin - use chat interface instead."""
316
+ return False
317
+
318
+ def has_change_permission(self, request, obj=None):
319
+ """Disable editing messages through admin."""
320
+ return False
321
+
322
+ @display(description="Ticket")
323
+ def ticket_display(self, obj: Message) -> str:
324
+ """Display ticket information."""
325
+ if not obj.ticket:
326
+ return "—"
327
+
328
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CONFIRMATION_NUMBER)
329
+ return StatusBadge.create(
330
+ text=f"{obj.ticket.subject} ({str(obj.ticket.uuid)[:8]}...)",
331
+ variant="primary",
332
+ config=config
333
+ )
334
+
335
+ @display(description="Sender")
336
+ def sender_display(self, obj: Message) -> str:
337
+ """Display sender with role indication."""
338
+ if not obj.sender:
339
+ return "—"
340
+
341
+ # Determine sender type and variant
342
+ if obj.sender.is_superuser:
343
+ variant = "danger"
344
+ icon = Icons.ADMIN_PANEL_SETTINGS
345
+ elif obj.sender.is_staff:
346
+ variant = "primary"
347
+ icon = Icons.SUPPORT_AGENT
190
348
  else:
191
- initials = obj.sender.__class__.objects.get_initials(obj.sender)
192
- bg_color = '#0d6efd' if obj.sender.is_staff else '#6f42c1' if obj.sender.is_superuser else '#198754'
193
-
194
- return format_html(
195
- '<div style="width: 32px; height: 32px; border-radius: 50%; background: {}; '
196
- 'color: white; display: flex; align-items: center; justify-content: center; '
197
- 'font-weight: bold; font-size: 12px;">{}</div>',
198
- bg_color, initials
199
- )
200
- sender_avatar.short_description = "Sender"
349
+ variant = "info"
350
+ icon = Icons.PERSON
351
+
352
+ config = StatusBadgeConfig(show_icons=True, icon=icon)
353
+ return StatusBadge.create(
354
+ text=obj.sender.get_full_name() or obj.sender.username,
355
+ variant=variant,
356
+ config=config
357
+ )
358
+
359
+ @display(description="Message", ordering="text")
360
+ def text_preview(self, obj: Message) -> str:
361
+ """Display message text preview."""
362
+ if not obj.text:
363
+ return "—"
364
+
365
+ preview = obj.text[:100]
366
+ if len(obj.text) > 100:
367
+ preview += "..."
368
+
369
+ return preview
370
+
371
+ @display(description="Created")
372
+ def created_at_display(self, obj: Message) -> str:
373
+ """Created time with relative display."""
374
+ config = DateTimeDisplayConfig(show_relative=True)
375
+ return self.display_datetime_relative(obj, 'created_at', config)
201
376
 
202
- def text_short(self, obj):
203
- """Show shortened message text."""
204
- return (obj.text[:50] + '...') if len(obj.text) > 50 else obj.text
205
- text_short.short_description = "Message"
377
+ def changelist_view(self, request, extra_context=None):
378
+ """Add message statistics to changelist."""
379
+ extra_context = extra_context or {}
380
+
381
+ queryset = self.get_queryset(request)
382
+ stats = queryset.aggregate(
383
+ total_messages=Count('uuid'),
384
+ staff_messages=Count('uuid', filter=Q(sender__is_staff=True)),
385
+ user_messages=Count('uuid', filter=Q(sender__is_staff=False))
386
+ )
387
+
388
+ # Messages by ticket status
389
+ ticket_status_counts = dict(
390
+ queryset.values_list('ticket__status').annotate(
391
+ count=Count('uuid')
392
+ )
393
+ )
394
+
395
+ extra_context['message_stats'] = {
396
+ 'total_messages': stats['total_messages'] or 0,
397
+ 'staff_messages': stats['staff_messages'] or 0,
398
+ 'user_messages': stats['user_messages'] or 0,
399
+ 'ticket_status_counts': ticket_status_counts
400
+ }
401
+
402
+ return super().changelist_view(request, extra_context)
@@ -0,0 +1,11 @@
1
+ """
2
+ Tasks admin interfaces using Django-CFG admin system.
3
+
4
+ Enhanced Dramatiq task management with Material Icons and optimized queries.
5
+ """
6
+
7
+ from .tasks_admin import TaskAdmin
8
+
9
+ __all__ = [
10
+ 'TaskAdmin',
11
+ ]