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,46 +1,54 @@
1
1
  """
2
- CloudflareApiKey admin with Unfold styling.
2
+ CloudflareApiKey admin using Django Admin Utilities.
3
3
 
4
- Admin interface for managing Cloudflare API keys.
4
+ Enhanced API key management with Material Icons and optimized queries.
5
5
  """
6
6
 
7
- from django.contrib import admin
8
- from django.utils.html import format_html
7
+ from django.contrib import admin, messages
9
8
  from django.http import HttpRequest
9
+ from django.db import models
10
+ from django.db.models import Count
10
11
  from typing import Any
11
-
12
12
  from unfold.admin import ModelAdmin
13
- from unfold.decorators import display, action
13
+
14
+ from django_cfg.modules.django_admin import (
15
+ OptimizedModelAdmin,
16
+ DisplayMixin,
17
+ StatusBadgeConfig,
18
+ DateTimeDisplayConfig,
19
+ Icons,
20
+ ActionVariant,
21
+ display,
22
+ action
23
+ )
24
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
14
25
 
15
26
  from ..models import CloudflareApiKey
16
27
 
17
28
 
18
29
  @admin.register(CloudflareApiKey)
19
- class CloudflareApiKeyAdmin(ModelAdmin):
20
- """Admin interface for CloudflareApiKey model."""
30
+ class CloudflareApiKeyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
31
+ """Admin interface for CloudflareApiKey model using Django Admin Utilities."""
21
32
 
22
33
  list_display = [
23
34
  'status_display',
24
- 'name',
35
+ 'name_display',
25
36
  'description_preview',
26
- 'active_badge',
27
- 'default_badge',
37
+ 'active_display',
38
+ 'default_display',
28
39
  'sites_count',
29
- 'last_used_at',
30
- 'created_at'
40
+ 'last_used_display',
41
+ 'created_at_display'
31
42
  ]
32
-
33
- list_display_links = ['name']
34
-
43
+ list_display_links = ['name_display']
44
+ ordering = ['-created_at']
35
45
  search_fields = ['name', 'description', 'account_id']
36
-
37
46
  list_filter = [
38
47
  'is_active',
39
48
  'is_default',
40
49
  'created_at',
41
50
  'last_used_at'
42
51
  ]
43
-
44
52
  readonly_fields = [
45
53
  'created_at',
46
54
  'updated_at',
@@ -49,23 +57,25 @@ class CloudflareApiKeyAdmin(ModelAdmin):
49
57
  ]
50
58
 
51
59
  fieldsets = [
52
- ('Basic Information', {
53
- 'fields': ['name', 'description']
60
+ ('🔑 Basic Information', {
61
+ 'fields': ['name', 'description'],
62
+ 'classes': ('tab',)
54
63
  }),
55
- ('Cloudflare Configuration', {
64
+ ('☁️ Cloudflare Configuration', {
56
65
  'fields': ['api_token', 'account_id'],
57
- 'classes': ['collapse']
66
+ 'classes': ('tab', 'collapse')
58
67
  }),
59
- ('Settings', {
60
- 'fields': ['is_active', 'is_default']
68
+ ('⚙️ Settings', {
69
+ 'fields': ['is_active', 'is_default'],
70
+ 'classes': ('tab',)
61
71
  }),
62
- ('Timestamps', {
72
+ ('Timestamps', {
63
73
  'fields': ['created_at', 'updated_at', 'last_used_at'],
64
- 'classes': ['collapse']
74
+ 'classes': ('tab', 'collapse')
65
75
  }),
66
- ('Usage', {
76
+ ('📊 Usage', {
67
77
  'fields': ['sites_using_key'],
68
- 'classes': ['collapse']
78
+ 'classes': ('tab', 'collapse')
69
79
  })
70
80
  ]
71
81
 
@@ -75,24 +85,47 @@ class CloudflareApiKeyAdmin(ModelAdmin):
75
85
  'deactivate_keys_action'
76
86
  ]
77
87
 
78
- # Display methods
79
-
80
88
  @display(description="Status")
81
89
  def status_display(self, obj: CloudflareApiKey) -> str:
82
- """Display status with emoji."""
90
+ """Display status with badge."""
83
91
  if obj.is_active:
84
92
  if obj.is_default:
85
- return format_html('<span style="color: green;">🔑 {} (Default)</span>', obj.name)
93
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.STAR)
94
+ return StatusBadge.create(
95
+ text=f"{obj.name} (Default)",
96
+ variant="success",
97
+ config=config
98
+ )
86
99
  else:
87
- return format_html('<span style="color: green;">🔑 {}</span>', obj.name)
100
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.KEY)
101
+ return StatusBadge.create(
102
+ text=obj.name,
103
+ variant="success",
104
+ config=config
105
+ )
88
106
  else:
89
- return format_html('<span style="color: red;">🔒 {}</span>', obj.name)
107
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.LOCK)
108
+ return StatusBadge.create(
109
+ text=obj.name,
110
+ variant="danger",
111
+ config=config
112
+ )
113
+
114
+ @display(description="Name", ordering="name")
115
+ def name_display(self, obj: CloudflareApiKey) -> str:
116
+ """Display API key name."""
117
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.KEY)
118
+ return StatusBadge.create(
119
+ text=obj.name,
120
+ variant="primary",
121
+ config=config
122
+ )
90
123
 
91
124
  @display(description="Description")
92
125
  def description_preview(self, obj: CloudflareApiKey) -> str:
93
126
  """Show description preview."""
94
127
  if not obj.description:
95
- return "-"
128
+ return ""
96
129
 
97
130
  preview = obj.description[:50]
98
131
  if len(obj.description) > 50:
@@ -101,20 +134,23 @@ class CloudflareApiKeyAdmin(ModelAdmin):
101
134
  return preview
102
135
 
103
136
  @display(description="Active")
104
- def active_badge(self, obj: CloudflareApiKey) -> str:
137
+ def active_display(self, obj: CloudflareApiKey) -> str:
105
138
  """Display active status badge."""
106
139
  if obj.is_active:
107
- return format_html('<span class="badge badge-success">Active</span>')
140
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
141
+ return StatusBadge.create(text="Active", variant="success", config=config)
108
142
  else:
109
- return format_html('<span class="badge badge-secondary">Inactive</span>')
143
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CANCEL)
144
+ return StatusBadge.create(text="Inactive", variant="secondary", config=config)
110
145
 
111
146
  @display(description="Default")
112
- def default_badge(self, obj: CloudflareApiKey) -> str:
147
+ def default_display(self, obj: CloudflareApiKey) -> str:
113
148
  """Display default status badge."""
114
149
  if obj.is_default:
115
- return format_html('<span class="badge badge-primary">Default</span>')
150
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.STAR)
151
+ return StatusBadge.create(text="Default", variant="primary", config=config)
116
152
  else:
117
- return "-"
153
+ return ""
118
154
 
119
155
  @display(description="Sites")
120
156
  def sites_count(self, obj: CloudflareApiKey) -> str:
@@ -124,62 +160,88 @@ class CloudflareApiKeyAdmin(ModelAdmin):
124
160
  return f"{count} sites"
125
161
  return "No sites"
126
162
 
163
+ @display(description="Last Used")
164
+ def last_used_display(self, obj: CloudflareApiKey) -> str:
165
+ """Display last used time."""
166
+ if not obj.last_used_at:
167
+ return "Never"
168
+ config = DateTimeDisplayConfig(show_relative=True)
169
+ return self.display_datetime_relative(obj, 'last_used_at', config)
170
+
171
+ @display(description="Created")
172
+ def created_at_display(self, obj: CloudflareApiKey) -> str:
173
+ """Created time with relative display."""
174
+ config = DateTimeDisplayConfig(show_relative=True)
175
+ return self.display_datetime_relative(obj, 'created_at', config)
176
+
127
177
  def sites_using_key(self, obj: CloudflareApiKey) -> str:
128
178
  """Show sites using this API key."""
129
- sites = obj.cloudflaresite_set.all()[:10] # Limit to 10 for display
179
+ sites = obj.cloudflaresite_set.all()[:10]
130
180
 
131
181
  if not sites:
132
182
  return "No sites using this key"
133
183
 
134
- html = "<ul>"
184
+ site_list = []
135
185
  for site in sites:
136
186
  status_emoji = "🔧" if site.maintenance_active else "🟢"
137
- html += f"<li>{status_emoji} {site.name} ({site.domain})</li>"
187
+ site_list.append(f"{status_emoji} {site.name} ({site.domain})")
138
188
 
139
- html += "</ul>"
189
+ result = "\n".join(site_list)
140
190
 
141
191
  total_count = obj.cloudflaresite_set.count()
142
192
  if total_count > 10:
143
- html += f"<p><em>... and {total_count - 10} more sites</em></p>"
193
+ result += f"\n... and {total_count - 10} more sites"
144
194
 
145
- return format_html(html)
146
-
147
- sites_using_key.short_description = "Sites Using This Key"
148
-
149
- # Admin Actions
195
+ return result
150
196
 
151
- @action(description="🔑 Make default API key")
197
+ @action(description="Make default API key", variant=ActionVariant.PRIMARY)
152
198
  def make_default_action(self, request: HttpRequest, queryset) -> None:
153
199
  """Make selected key the default."""
154
200
  if queryset.count() > 1:
155
- self.message_user(request, "Please select only one API key to make default.", level='error')
201
+ messages.error(request, "Please select only one API key to make default.")
156
202
  return
157
203
 
158
204
  key = queryset.first()
159
205
  if key:
160
- # This will automatically set others to non-default via the model's save method
161
206
  key.is_default = True
162
207
  key.save()
163
- self.message_user(request, f"'{key.name}' is now the default API key.")
208
+ messages.success(request, f"'{key.name}' is now the default API key.")
164
209
 
165
- @action(description="Activate API keys")
210
+ @action(description="Activate API keys", variant=ActionVariant.SUCCESS)
166
211
  def activate_keys_action(self, request: HttpRequest, queryset) -> None:
167
212
  """Activate selected API keys."""
168
213
  count = queryset.update(is_active=True)
169
- self.message_user(request, f"Successfully activated {count} API keys.")
214
+ messages.success(request, f"Successfully activated {count} API keys.")
170
215
 
171
- @action(description="Deactivate API keys")
216
+ @action(description="Deactivate API keys", variant=ActionVariant.DANGER)
172
217
  def deactivate_keys_action(self, request: HttpRequest, queryset) -> None:
173
218
  """Deactivate selected API keys."""
174
- # Don't allow deactivating the default key
175
219
  default_keys = queryset.filter(is_default=True)
176
220
  if default_keys.exists():
177
- self.message_user(
221
+ messages.error(
178
222
  request,
179
- "Cannot deactivate default API key. Please set another key as default first.",
180
- level='error'
223
+ "Cannot deactivate default API key. Please set another key as default first."
181
224
  )
182
225
  return
183
226
 
184
227
  count = queryset.update(is_active=False)
185
- self.message_user(request, f"Successfully deactivated {count} API keys.")
228
+ messages.warning(request, f"Successfully deactivated {count} API keys.")
229
+
230
+ def changelist_view(self, request, extra_context=None):
231
+ """Add API key statistics to changelist."""
232
+ extra_context = extra_context or {}
233
+
234
+ queryset = self.get_queryset(request)
235
+ stats = queryset.aggregate(
236
+ total_keys=Count('id'),
237
+ active_keys=Count('id', filter=models.Q(is_active=True)),
238
+ default_keys=Count('id', filter=models.Q(is_default=True))
239
+ )
240
+
241
+ extra_context['api_key_stats'] = {
242
+ 'total_keys': stats['total_keys'] or 0,
243
+ 'active_keys': stats['active_keys'] or 0,
244
+ 'default_keys': stats['default_keys'] or 0
245
+ }
246
+
247
+ return super().changelist_view(request, extra_context)
@@ -1,48 +1,59 @@
1
1
  """
2
- MaintenanceLog admin with Unfold styling.
2
+ MaintenanceLog admin using Django Admin Utilities.
3
3
 
4
4
  Read-only admin interface for viewing maintenance operation logs.
5
5
  """
6
6
 
7
7
  from django.contrib import admin
8
- from django.utils.html import format_html
9
8
  from django.http import HttpRequest
9
+ from django.db import models
10
+ from django.db.models import Count, Q
10
11
  from typing import Any
11
12
  import json
12
-
13
13
  from unfold.admin import ModelAdmin
14
- from unfold.decorators import display
14
+
15
+ from django_cfg.modules.django_admin import (
16
+ OptimizedModelAdmin,
17
+ DisplayMixin,
18
+ StatusBadgeConfig,
19
+ DateTimeDisplayConfig,
20
+ Icons,
21
+ display
22
+ )
23
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
15
24
 
16
25
  from ..models import MaintenanceLog
17
26
 
18
27
 
19
28
  @admin.register(MaintenanceLog)
20
- class MaintenanceLogAdmin(ModelAdmin):
21
- """Admin interface for MaintenanceLog model."""
29
+ class MaintenanceLogAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
30
+ """Admin interface for MaintenanceLog model using Django Admin Utilities."""
31
+
32
+ # Performance optimization
33
+ select_related_fields = ['site']
22
34
 
23
35
  list_display = [
24
36
  'status_display',
25
- 'site',
37
+ 'site_display',
26
38
  'action_display',
27
- 'created_at',
39
+ 'created_at_display',
28
40
  'duration_display',
29
41
  'error_preview'
30
42
  ]
31
-
43
+ list_display_links = ['site_display']
44
+ ordering = ['-created_at']
32
45
  list_filter = [
33
46
  'action',
34
47
  'status',
35
48
  'created_at',
36
49
  'site'
37
50
  ]
38
-
39
51
  search_fields = [
40
52
  'site__name',
41
53
  'site__domain',
42
54
  'reason',
43
55
  'error_message'
44
56
  ]
45
-
46
57
  readonly_fields = [
47
58
  'site',
48
59
  'action',
@@ -56,101 +67,172 @@ class MaintenanceLogAdmin(ModelAdmin):
56
67
  ]
57
68
 
58
69
  fieldsets = [
59
- ('Operation Details', {
60
- 'fields': ['site', 'action', 'status', 'created_at', 'duration_seconds']
70
+ ('📋 Log Information', {
71
+ 'fields': ['site', 'action', 'status', 'reason'],
72
+ 'classes': ('tab',)
73
+ }),
74
+ ('⏱️ Timing', {
75
+ 'fields': ['created_at', 'duration_seconds'],
76
+ 'classes': ('tab',)
61
77
  }),
62
- ('Additional Information', {
63
- 'fields': ['reason', 'error_message']
78
+ (' Error Details', {
79
+ 'fields': ['error_message'],
80
+ 'classes': ('tab', 'collapse')
64
81
  }),
65
- ('Cloudflare Response', {
82
+ ('📊 Cloudflare Response', {
66
83
  'fields': ['cloudflare_response_formatted'],
67
- 'classes': ['collapse']
84
+ 'classes': ('tab', 'collapse')
68
85
  })
69
86
  ]
70
87
 
71
- ordering = ['-created_at']
72
-
73
- def has_add_permission(self, request: HttpRequest) -> bool:
74
- """Logs are created automatically, no manual adding."""
88
+ def has_add_permission(self, request):
89
+ """Disable adding new logs through admin."""
75
90
  return False
76
91
 
77
- def has_change_permission(self, request: HttpRequest, obj: Any = None) -> bool:
78
- """Logs are read-only."""
92
+ def has_change_permission(self, request, obj=None):
93
+ """Disable editing logs through admin."""
79
94
  return False
80
95
 
81
- def has_delete_permission(self, request: HttpRequest, obj: Any = None) -> bool:
82
- """Allow deletion of old logs."""
96
+ def has_delete_permission(self, request, obj=None):
97
+ """Allow deleting old logs."""
83
98
  return True
84
99
 
85
- # Display methods
86
-
87
- @display(description="Status", ordering="status")
100
+ @display(description="Status")
88
101
  def status_display(self, obj: MaintenanceLog) -> str:
89
- """Display status with emoji and badge."""
90
- status_emoji = {
91
- MaintenanceLog.Status.SUCCESS: "✅",
92
- MaintenanceLog.Status.FAILED: "❌",
93
- MaintenanceLog.Status.PENDING: "⏳"
94
- }.get(obj.status, "❓")
102
+ """Display status with badge."""
103
+ status_variants = {
104
+ MaintenanceLog.Status.SUCCESS: 'success',
105
+ MaintenanceLog.Status.FAILED: 'danger',
106
+ MaintenanceLog.Status.PENDING: 'warning'
107
+ }
108
+ variant = status_variants.get(obj.status, 'secondary')
109
+
110
+ status_icons = {
111
+ MaintenanceLog.Status.SUCCESS: Icons.CHECK_CIRCLE,
112
+ MaintenanceLog.Status.FAILED: Icons.CANCEL,
113
+ MaintenanceLog.Status.PENDING: Icons.SCHEDULE
114
+ }
115
+ icon = status_icons.get(obj.status, Icons.HELP)
95
116
 
96
- color_class = {
97
- MaintenanceLog.Status.SUCCESS: "badge-success",
98
- MaintenanceLog.Status.FAILED: "badge-danger",
99
- MaintenanceLog.Status.PENDING: "badge-warning"
100
- }.get(obj.status, "badge-secondary")
117
+ config = StatusBadgeConfig(show_icons=True, icon=icon)
118
+ return StatusBadge.create(
119
+ text=obj.get_status_display(),
120
+ variant=variant,
121
+ config=config
122
+ )
123
+
124
+ @display(description="Site", ordering="site__name")
125
+ def site_display(self, obj: MaintenanceLog) -> str:
126
+ """Display site name."""
127
+ if not obj.site:
128
+ return "—"
101
129
 
102
- return format_html(
103
- '<span class="badge {}">{} {}</span>',
104
- color_class, status_emoji, obj.get_status_display()
130
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.LANGUAGE)
131
+ return StatusBadge.create(
132
+ text=f"{obj.site.name} ({obj.site.domain})",
133
+ variant="info",
134
+ config=config
105
135
  )
106
136
 
107
- @display(description="Action", ordering="action")
137
+ @display(description="Action")
108
138
  def action_display(self, obj: MaintenanceLog) -> str:
109
- """Display action with icon."""
139
+ """Display action with badge."""
140
+ action_variants = {
141
+ MaintenanceLog.Action.ENABLE: 'warning',
142
+ MaintenanceLog.Action.DISABLE: 'success',
143
+ MaintenanceLog.Action.CHECK_STATUS: 'info'
144
+ }
145
+ variant = action_variants.get(obj.action, 'secondary')
146
+
110
147
  action_icons = {
111
- MaintenanceLog.Action.ENABLE: "🔧",
112
- MaintenanceLog.Action.DISABLE: "🟢",
113
- MaintenanceLog.Action.SYNC: "🔄",
114
- MaintenanceLog.Action.ERROR: "❌"
148
+ MaintenanceLog.Action.ENABLE: Icons.BUILD,
149
+ MaintenanceLog.Action.DISABLE: Icons.CHECK_CIRCLE,
150
+ MaintenanceLog.Action.CHECK_STATUS: Icons.VISIBILITY
115
151
  }
152
+ icon = action_icons.get(obj.action, Icons.SETTINGS)
116
153
 
117
- icon = action_icons.get(obj.action, "❓")
118
- return f"{icon} {obj.get_action_display()}"
154
+ config = StatusBadgeConfig(show_icons=True, icon=icon)
155
+ return StatusBadge.create(
156
+ text=obj.get_action_display(),
157
+ variant=variant,
158
+ config=config
159
+ )
160
+
161
+ @display(description="Created")
162
+ def created_at_display(self, obj: MaintenanceLog) -> str:
163
+ """Created time with relative display."""
164
+ config = DateTimeDisplayConfig(show_relative=True)
165
+ return self.display_datetime_relative(obj, 'created_at', config)
119
166
 
120
167
  @display(description="Duration")
121
168
  def duration_display(self, obj: MaintenanceLog) -> str:
122
169
  """Display operation duration."""
123
- if not obj.duration_seconds:
124
- return "-"
170
+ if obj.duration_seconds is None:
171
+ return ""
125
172
 
126
- if obj.duration_seconds < 60:
127
- return f"{obj.duration_seconds}s"
173
+ if obj.duration_seconds < 1:
174
+ return f"{obj.duration_seconds * 1000:.0f}ms"
175
+ elif obj.duration_seconds < 60:
176
+ return f"{obj.duration_seconds:.1f}s"
128
177
  else:
129
178
  minutes = obj.duration_seconds // 60
130
179
  seconds = obj.duration_seconds % 60
131
- return f"{minutes}m {seconds}s"
180
+ return f"{minutes:.0f}m {seconds:.0f}s"
132
181
 
133
182
  @display(description="Error")
134
183
  def error_preview(self, obj: MaintenanceLog) -> str:
135
184
  """Show error message preview."""
136
185
  if not obj.error_message:
137
- return "-"
186
+ return ""
138
187
 
139
188
  preview = obj.error_message[:100]
140
189
  if len(obj.error_message) > 100:
141
190
  preview += "..."
142
191
 
143
- return format_html('<span style="color: red; font-family: monospace;">{}</span>', preview)
192
+ return preview
144
193
 
145
194
  def cloudflare_response_formatted(self, obj: MaintenanceLog) -> str:
146
- """Format Cloudflare response for display."""
195
+ """Format cloudflare response for display."""
147
196
  if not obj.cloudflare_response:
148
197
  return "No response data"
149
198
 
150
199
  try:
151
- formatted = json.dumps(obj.cloudflare_response, indent=2)
152
- return format_html('<pre style="background: #f8f8f8; padding: 10px; overflow: auto;">{}</pre>', formatted)
153
- except Exception:
200
+ if isinstance(obj.cloudflare_response, str):
201
+ data = json.loads(obj.cloudflare_response)
202
+ else:
203
+ data = obj.cloudflare_response
204
+
205
+ return json.dumps(data, indent=2, ensure_ascii=False)
206
+ except (json.JSONDecodeError, TypeError):
154
207
  return str(obj.cloudflare_response)
155
208
 
156
209
  cloudflare_response_formatted.short_description = "Cloudflare Response (Formatted)"
210
+
211
+ def changelist_view(self, request, extra_context=None):
212
+ """Add log statistics to changelist."""
213
+ extra_context = extra_context or {}
214
+
215
+ queryset = self.get_queryset(request)
216
+ stats = queryset.aggregate(
217
+ total_logs=Count('id'),
218
+ success_logs=Count('id', filter=Q(status=MaintenanceLog.Status.SUCCESS)),
219
+ failed_logs=Count('id', filter=Q(status=MaintenanceLog.Status.FAILED)),
220
+ pending_logs=Count('id', filter=Q(status=MaintenanceLog.Status.PENDING))
221
+ )
222
+
223
+ # Action breakdown
224
+ action_counts = dict(
225
+ queryset.values_list('action').annotate(
226
+ count=Count('id')
227
+ )
228
+ )
229
+
230
+ extra_context['log_stats'] = {
231
+ 'total_logs': stats['total_logs'] or 0,
232
+ 'success_logs': stats['success_logs'] or 0,
233
+ 'failed_logs': stats['failed_logs'] or 0,
234
+ 'pending_logs': stats['pending_logs'] or 0,
235
+ 'action_counts': action_counts
236
+ }
237
+
238
+ return super().changelist_view(request, extra_context)