django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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 (246) 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 +269 -0
  43. django_cfg/apps/payments/admin/payments_admin.py +183 -460
  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 +153 -34
  47. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  48. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  49. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  50. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  51. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  52. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  53. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  54. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
  55. django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
  56. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  57. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
  58. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  59. django_cfg/apps/payments/config/__init__.py +14 -15
  60. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  61. django_cfg/apps/payments/config/helpers.py +8 -13
  62. django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
  63. django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
  64. django_cfg/apps/payments/middleware/api_access.py +32 -6
  65. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  66. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  67. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  68. django_cfg/apps/payments/models/balance.py +12 -0
  69. django_cfg/apps/payments/models/currencies.py +106 -32
  70. django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
  71. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  72. django_cfg/apps/payments/models/payments.py +94 -0
  73. django_cfg/apps/payments/services/core/base.py +4 -4
  74. django_cfg/apps/payments/services/core/currency_service.py +35 -28
  75. django_cfg/apps/payments/services/core/payment_service.py +266 -39
  76. django_cfg/apps/payments/services/providers/__init__.py +3 -0
  77. django_cfg/apps/payments/services/providers/base.py +303 -41
  78. django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
  79. django_cfg/apps/payments/services/providers/models/base.py +145 -0
  80. django_cfg/apps/payments/services/providers/models/providers.py +87 -0
  81. django_cfg/apps/payments/services/providers/models/universal.py +48 -0
  82. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
  83. django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
  84. django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
  85. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
  86. django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
  87. django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
  88. django_cfg/apps/payments/services/providers/registry.py +9 -37
  89. django_cfg/apps/payments/services/providers/sync_service.py +277 -0
  90. django_cfg/apps/payments/services/types/requests.py +19 -7
  91. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  92. django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
  93. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  94. django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
  95. django_cfg/apps/payments/tasks/__init__.py +39 -0
  96. django_cfg/apps/payments/tasks/types.py +73 -0
  97. django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
  98. django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
  99. django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
  100. django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
  101. django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
  102. django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
  103. django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
  104. django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
  105. django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
  106. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  107. django_cfg/apps/payments/urls.py +3 -2
  108. django_cfg/apps/payments/urls_admin.py +1 -1
  109. django_cfg/apps/payments/views/api/currencies.py +8 -5
  110. django_cfg/apps/payments/views/overview/services.py +2 -2
  111. django_cfg/apps/payments/views/serializers/currencies.py +22 -8
  112. django_cfg/apps/support/admin/__init__.py +10 -1
  113. django_cfg/apps/support/admin/support_admin.py +338 -141
  114. django_cfg/apps/tasks/admin/__init__.py +11 -0
  115. django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
  116. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  117. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  118. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  119. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  120. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  121. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  122. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  123. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  124. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  125. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  126. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  127. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  128. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  129. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  130. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  131. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  132. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  133. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  134. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  135. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  136. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  137. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  138. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  139. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  140. django_cfg/apps/tasks/urls.py +2 -2
  141. django_cfg/apps/tasks/urls_admin.py +2 -2
  142. django_cfg/apps/tasks/utils/__init__.py +1 -0
  143. django_cfg/apps/tasks/utils/simulator.py +356 -0
  144. django_cfg/apps/tasks/views/__init__.py +16 -0
  145. django_cfg/apps/tasks/views/api.py +569 -0
  146. django_cfg/apps/tasks/views/dashboard.py +58 -0
  147. django_cfg/config.py +1 -1
  148. django_cfg/core/config.py +10 -5
  149. django_cfg/core/generation.py +1 -1
  150. django_cfg/core/integration/__init__.py +21 -0
  151. django_cfg/management/commands/__init__.py +13 -1
  152. django_cfg/management/commands/migrate_all.py +9 -3
  153. django_cfg/management/commands/migrator.py +11 -6
  154. django_cfg/management/commands/rundramatiq.py +3 -2
  155. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  156. django_cfg/middleware/__init__.py +0 -2
  157. django_cfg/models/api_keys.py +115 -0
  158. django_cfg/models/constance.py +0 -11
  159. django_cfg/models/payments.py +137 -3
  160. django_cfg/modules/django_admin/__init__.py +64 -0
  161. django_cfg/modules/django_admin/decorators/__init__.py +13 -0
  162. django_cfg/modules/django_admin/decorators/actions.py +106 -0
  163. django_cfg/modules/django_admin/decorators/display.py +106 -0
  164. django_cfg/modules/django_admin/mixins/__init__.py +14 -0
  165. django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
  166. django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
  167. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
  168. django_cfg/modules/django_admin/models/__init__.py +20 -0
  169. django_cfg/modules/django_admin/models/action_models.py +33 -0
  170. django_cfg/modules/django_admin/models/badge_models.py +20 -0
  171. django_cfg/modules/django_admin/models/base.py +26 -0
  172. django_cfg/modules/django_admin/models/display_models.py +31 -0
  173. django_cfg/modules/django_admin/utils/badges.py +159 -0
  174. django_cfg/modules/django_admin/utils/displays.py +247 -0
  175. django_cfg/modules/django_currency/__init__.py +2 -2
  176. django_cfg/modules/django_currency/clients/__init__.py +2 -2
  177. django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
  178. django_cfg/modules/django_currency/core/converter.py +12 -12
  179. django_cfg/modules/django_currency/database/__init__.py +2 -2
  180. django_cfg/modules/django_currency/database/database_loader.py +93 -42
  181. django_cfg/modules/django_llm/llm/client.py +10 -2
  182. django_cfg/modules/django_tasks.py +54 -21
  183. django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
  184. django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
  185. django_cfg/modules/django_unfold/dashboard.py +14 -13
  186. django_cfg/modules/django_unfold/models/config.py +1 -1
  187. django_cfg/registry/core.py +7 -9
  188. django_cfg/registry/third_party.py +2 -2
  189. django_cfg/template_archive/django_sample.zip +0 -0
  190. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
  191. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
  192. django_cfg/apps/accounts/admin/activity.py +0 -96
  193. django_cfg/apps/accounts/admin/group.py +0 -17
  194. django_cfg/apps/accounts/admin/otp.py +0 -59
  195. django_cfg/apps/accounts/admin/registration_source.py +0 -97
  196. django_cfg/apps/accounts/admin/twilio_response.py +0 -227
  197. django_cfg/apps/accounts/admin/user.py +0 -300
  198. django_cfg/apps/agents/core/agent.py +0 -281
  199. django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
  200. django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
  201. django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
  202. django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
  203. django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
  204. django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
  205. django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
  206. django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
  207. django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
  208. django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
  209. django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
  210. django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
  211. django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
  212. django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
  213. django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
  214. django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
  215. django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
  216. django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
  217. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  218. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  219. django_cfg/apps/payments/config/constance/fields.py +0 -69
  220. django_cfg/apps/payments/config/constance/settings.py +0 -160
  221. django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
  222. django_cfg/apps/tasks/admin.py +0 -320
  223. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  224. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  225. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  226. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  227. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  228. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  229. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  230. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  231. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  232. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  233. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  234. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  235. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  236. django_cfg/apps/tasks/views.py +0 -461
  237. django_cfg/management/commands/auto_generate.py +0 -486
  238. django_cfg/middleware/static_nocache.py +0 -55
  239. django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
  240. /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
  241. /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
  242. /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
  243. /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
  244. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  245. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  246. {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,29 @@
1
1
  """
2
- CloudflareSite admin with Unfold styling and action buttons.
2
+ CloudflareSite admin using Django Admin Utilities.
3
3
 
4
- Beautiful admin interface inspired by the old complex system but simplified.
4
+ Enhanced site 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.urls import reverse
10
- from django.utils.safestring import mark_safe
11
- from django.contrib import messages
12
9
  from django.http import HttpRequest
13
10
  from django.shortcuts import redirect
11
+ from django.db import models
12
+ from django.db.models import Count, Q
14
13
  from typing import Any
15
-
16
14
  from unfold.admin import ModelAdmin, TabularInline
17
- from unfold.decorators import display, action
18
- from unfold.enums import ActionVariant
15
+
16
+ from django_cfg.modules.django_admin import (
17
+ OptimizedModelAdmin,
18
+ DisplayMixin,
19
+ StatusBadgeConfig,
20
+ DateTimeDisplayConfig,
21
+ Icons,
22
+ ActionVariant,
23
+ display,
24
+ action
25
+ )
26
+ from django_cfg.modules.django_admin.utils.badges import StatusBadge
19
27
 
20
28
  from ..models import CloudflareSite, MaintenanceLog
21
29
  from ..services import MaintenanceService
@@ -43,55 +51,69 @@ class MaintenanceLogInline(TabularInline):
43
51
 
44
52
  @display(description="Status")
45
53
  def status_display(self, obj):
46
- """Display status with emoji."""
47
- status_emoji = {
48
- MaintenanceLog.Status.SUCCESS: "✅",
49
- MaintenanceLog.Status.FAILED: "❌",
50
- MaintenanceLog.Status.PENDING: "⏳"
51
- }.get(obj.status, "❓")
54
+ """Display status with badge."""
55
+ status_variants = {
56
+ MaintenanceLog.Status.SUCCESS: 'success',
57
+ MaintenanceLog.Status.FAILED: 'danger',
58
+ MaintenanceLog.Status.PENDING: 'warning'
59
+ }
60
+ variant = status_variants.get(obj.status, 'secondary')
61
+
62
+ status_icons = {
63
+ MaintenanceLog.Status.SUCCESS: Icons.CHECK_CIRCLE,
64
+ MaintenanceLog.Status.FAILED: Icons.CANCEL,
65
+ MaintenanceLog.Status.PENDING: Icons.SCHEDULE
66
+ }
67
+ icon = status_icons.get(obj.status, Icons.HELP)
52
68
 
53
- return format_html('{} {}', status_emoji, obj.get_status_display())
69
+ config = StatusBadgeConfig(show_icons=True, icon=icon)
70
+ return StatusBadge.create(
71
+ text=obj.get_status_display(),
72
+ variant=variant,
73
+ config=config
74
+ )
54
75
 
55
76
  @display(description="Error")
56
77
  def error_preview(self, obj):
57
78
  """Show error message preview."""
58
79
  if not obj.error_message:
59
- return "-"
80
+ return ""
60
81
 
61
82
  preview = obj.error_message[:50]
62
83
  if len(obj.error_message) > 50:
63
84
  preview += "..."
64
85
 
65
- return format_html('<span style="color: red; font-family: monospace;">{}</span>', preview)
86
+ return preview
66
87
 
67
88
 
68
89
  @admin.register(CloudflareSite)
69
- class CloudflareSiteAdmin(ModelAdmin):
70
- """Admin for CloudflareSite with Unfold styling and action buttons."""
90
+ class CloudflareSiteAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
91
+ """Admin for CloudflareSite using Django Admin Utilities."""
92
+
93
+ # Performance optimization
94
+ select_related_fields = ['api_key']
71
95
 
72
96
  list_display = [
73
97
  'status_display',
74
- 'name',
75
- 'domain',
76
- 'subdomain_config_badge',
77
- 'maintenance_badge',
78
- 'active_badge',
79
- 'last_maintenance_at',
98
+ 'name_display',
99
+ 'domain_display',
100
+ 'subdomain_config_display',
101
+ 'maintenance_display',
102
+ 'active_display',
103
+ 'last_maintenance_display',
80
104
  'logs_count',
81
- 'action_buttons'
105
+ 'api_key_display'
82
106
  ]
83
-
84
- list_display_links = ['name', 'domain']
85
-
107
+ list_display_links = ['name_display', 'domain_display']
108
+ ordering = ['-created_at']
86
109
  search_fields = ['name', 'domain', 'zone_id']
87
-
88
110
  list_filter = [
89
111
  'maintenance_active',
90
112
  'is_active',
113
+ 'include_subdomains',
91
114
  'created_at',
92
115
  'last_maintenance_at'
93
116
  ]
94
-
95
117
  readonly_fields = [
96
118
  'created_at',
97
119
  'updated_at',
@@ -100,27 +122,29 @@ class CloudflareSiteAdmin(ModelAdmin):
100
122
  ]
101
123
 
102
124
  fieldsets = [
103
- ('Basic Information', {
104
- 'fields': ['name', 'domain']
125
+ ('🌐 Site Information', {
126
+ 'fields': ['name', 'domain', 'zone_id'],
127
+ 'classes': ('tab',)
105
128
  }),
106
- ('Subdomain Configuration', {
107
- 'fields': ['include_subdomains', 'subdomain_list'],
108
- 'description': 'Configure which subdomains should be affected by maintenance mode'
129
+ ('🔧 Maintenance Configuration', {
130
+ 'fields': ['maintenance_url', 'include_subdomains'],
131
+ 'classes': ('tab',)
109
132
  }),
110
- ('Cloudflare Configuration', {
111
- 'fields': ['zone_id', 'account_id', 'api_key'],
112
- 'classes': ['collapse']
133
+ ('☁️ Cloudflare Settings', {
134
+ 'fields': ['api_key'],
135
+ 'classes': ('tab',)
113
136
  }),
114
- ('Status', {
115
- 'fields': ['maintenance_active', 'maintenance_url', 'is_active']
137
+ ('⚙️ Status', {
138
+ 'fields': ['is_active', 'maintenance_active'],
139
+ 'classes': ('tab',)
116
140
  }),
117
- ('Timestamps', {
141
+ ('Timestamps', {
118
142
  'fields': ['created_at', 'updated_at', 'last_maintenance_at'],
119
- 'classes': ['collapse']
143
+ 'classes': ('tab', 'collapse')
120
144
  }),
121
- ('Recent Activity', {
145
+ ('📋 Recent Logs', {
122
146
  'fields': ['logs_preview'],
123
- 'classes': ['collapse']
147
+ 'classes': ('tab', 'collapse')
124
148
  })
125
149
  ]
126
150
 
@@ -129,359 +153,196 @@ class CloudflareSiteAdmin(ModelAdmin):
129
153
  actions = [
130
154
  'enable_maintenance_action',
131
155
  'disable_maintenance_action',
132
- 'sync_from_cloudflare_action'
133
- ]
134
-
135
- # Unfold action buttons
136
- actions_detail = [
137
- 'sync_with_cloudflare_detail',
138
- 'enable_maintenance_detail',
139
- 'disable_maintenance_detail'
140
- ]
141
- actions_list = [
142
- 'bulk_sync_sites',
143
- 'bulk_discover_sites'
156
+ 'activate_sites_action',
157
+ 'deactivate_sites_action',
158
+ 'sync_with_cloudflare_action'
144
159
  ]
145
160
 
146
- # Display methods
147
-
148
161
  @display(description="Status")
149
- @display(description="Subdomains", ordering='include_subdomains')
150
- def subdomain_config_badge(self, obj: CloudflareSite) -> str:
151
- """Display subdomain configuration with badge."""
152
- config = obj.get_subdomain_display()
153
-
154
- if obj.include_subdomains:
155
- # All subdomains
156
- badge_class = "badge-success"
157
- icon = "🌐"
158
- elif obj.subdomain_list.strip():
159
- # Specific subdomains
160
- badge_class = "badge-warning"
161
- icon = "📋"
162
- else:
163
- # Root only
164
- badge_class = "badge-secondary"
165
- icon = "🏠"
166
-
167
- return format_html(
168
- '<span class="badge {}" title="{}">{} {}</span>',
169
- badge_class,
170
- config,
171
- icon,
172
- "All" if obj.include_subdomains else ("List" if obj.subdomain_list.strip() else "Root")
173
- )
174
-
175
162
  def status_display(self, obj: CloudflareSite) -> str:
176
- """Display status with emoji."""
163
+ """Display site status with maintenance indicator."""
177
164
  if obj.maintenance_active:
178
- return format_html('<span style="color: orange;">🔧 {}</span>', obj.name)
165
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.BUILD)
166
+ return StatusBadge.create(
167
+ text=f"{obj.name} (Maintenance)",
168
+ variant="warning",
169
+ config=config
170
+ )
179
171
  elif obj.is_active:
180
- return format_html('<span style="color: green;">🟢 {}</span>', obj.name)
172
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
173
+ return StatusBadge.create(
174
+ text=obj.name,
175
+ variant="success",
176
+ config=config
177
+ )
178
+ else:
179
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CANCEL)
180
+ return StatusBadge.create(
181
+ text=obj.name,
182
+ variant="secondary",
183
+ config=config
184
+ )
185
+
186
+ @display(description="Name", ordering="name")
187
+ def name_display(self, obj: CloudflareSite) -> str:
188
+ """Display site name."""
189
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.LANGUAGE)
190
+ return StatusBadge.create(
191
+ text=obj.name,
192
+ variant="primary",
193
+ config=config
194
+ )
195
+
196
+ @display(description="Domain", ordering="domain")
197
+ def domain_display(self, obj: CloudflareSite) -> str:
198
+ """Display domain."""
199
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.PUBLIC)
200
+ return StatusBadge.create(
201
+ text=obj.domain,
202
+ variant="info",
203
+ config=config
204
+ )
205
+
206
+ @display(description="Subdomains")
207
+ def subdomain_config_display(self, obj: CloudflareSite) -> str:
208
+ """Display subdomain configuration."""
209
+ if obj.include_subdomains:
210
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.ACCOUNT_TREE)
211
+ return StatusBadge.create(text="Includes Subdomains", variant="info", config=config)
181
212
  else:
182
- return format_html('<span style="color: red;">🔴 {}</span>', obj.name)
213
+ return "Domain Only"
183
214
 
184
215
  @display(description="Maintenance")
185
- def maintenance_badge(self, obj: CloudflareSite) -> str:
186
- """Display maintenance status badge."""
216
+ def maintenance_display(self, obj: CloudflareSite) -> str:
217
+ """Display maintenance status."""
187
218
  if obj.maintenance_active:
188
- return format_html('<span class="badge badge-warning">🔧 Active</span>')
219
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.BUILD)
220
+ return StatusBadge.create(text="Active", variant="warning", config=config)
189
221
  else:
190
- return format_html('<span class="badge badge-success">✅ Normal</span>')
222
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
223
+ return StatusBadge.create(text="Inactive", variant="success", config=config)
191
224
 
192
- @display(description="Site Active")
193
- def active_badge(self, obj: CloudflareSite) -> str:
194
- """Display active status badge."""
225
+ @display(description="Active")
226
+ def active_display(self, obj: CloudflareSite) -> str:
227
+ """Display active status."""
195
228
  if obj.is_active:
196
- return format_html('<span class="badge badge-success">Active</span>')
229
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
230
+ return StatusBadge.create(text="Active", variant="success", config=config)
197
231
  else:
198
- return format_html('<span class="badge badge-secondary">Inactive</span>')
232
+ config = StatusBadgeConfig(show_icons=True, icon=Icons.CANCEL)
233
+ return StatusBadge.create(text="Inactive", variant="secondary", config=config)
234
+
235
+ @display(description="Last Maintenance")
236
+ def last_maintenance_display(self, obj: CloudflareSite) -> str:
237
+ """Display last maintenance time."""
238
+ if not obj.last_maintenance_at:
239
+ return "Never"
240
+ config = DateTimeDisplayConfig(show_relative=True)
241
+ return self.display_datetime_relative(obj, 'last_maintenance_at', config)
199
242
 
200
243
  @display(description="Logs")
201
244
  def logs_count(self, obj: CloudflareSite) -> str:
202
- """Display count of logs with link."""
203
- count = obj.logs.count()
245
+ """Display count of maintenance logs."""
246
+ count = obj.maintenancelog_set.count()
204
247
  if count > 0:
205
- url = reverse('admin:maintenance_maintenancelog_changelist')
206
- return format_html(
207
- '<a href="{}?site__id__exact={}">{} logs</a>',
208
- url, obj.id, count
209
- )
248
+ return f"{count} logs"
210
249
  return "No logs"
211
250
 
212
- @display(description="Actions")
213
- def action_buttons(self, obj: CloudflareSite) -> str:
214
- """Display action buttons."""
215
- buttons = []
216
-
217
- if obj.maintenance_active:
218
- buttons.append(
219
- f'<a href="#" onclick="disableMaintenance({obj.id}, \'{obj.domain}\')" '
220
- f'class="button" style="background: green; color: white; margin: 2px;">Disable</a>'
221
- )
222
- else:
223
- buttons.append(
224
- f'<a href="#" onclick="enableMaintenance({obj.id}, \'{obj.domain}\')" '
225
- f'class="button" style="background: orange; color: white; margin: 2px;">Enable</a>'
226
- )
227
-
228
- buttons.append(
229
- f'<a href="#" onclick="syncSite({obj.id}, \'{obj.domain}\')" '
230
- f'class="button" style="background: blue; color: white; margin: 2px;">Sync</a>'
231
- )
232
-
233
- return mark_safe(' '.join(buttons))
251
+ @display(description="API Key")
252
+ def api_key_display(self, obj: CloudflareSite) -> str:
253
+ """Display API key."""
254
+ if not obj.api_key:
255
+ return "—"
256
+ return self.display_user_simple(obj.api_key, field_name='name')
234
257
 
235
258
  def logs_preview(self, obj: CloudflareSite) -> str:
236
- """Show recent logs preview."""
237
- recent_logs = obj.logs.all()[:5]
238
- if not recent_logs:
239
- return "No logs yet"
259
+ """Show recent maintenance logs."""
260
+ logs = obj.maintenancelog_set.all()[:5]
240
261
 
241
- html = "<ul>"
242
- for log in recent_logs:
243
- status_emoji = {
244
- MaintenanceLog.Status.SUCCESS: "✅",
245
- MaintenanceLog.Status.FAILED: "❌",
246
- MaintenanceLog.Status.PENDING: "⏳"
247
- }.get(log.status, "❓")
248
-
249
- html += f"<li>{status_emoji} {log.get_action_display()} - {log.created_at.strftime('%Y-%m-%d %H:%M')}"
250
- if log.error_message:
251
- html += f" <em>({log.error_message[:50]}...)</em>"
252
- html += "</li>"
262
+ if not logs:
263
+ return "No maintenance logs yet"
253
264
 
254
- html += "</ul>"
265
+ log_list = []
266
+ for log in logs:
267
+ status_emoji = "✅" if log.status == MaintenanceLog.Status.SUCCESS else "❌" if log.status == MaintenanceLog.Status.FAILED else "⏳"
268
+ log_list.append(f"{status_emoji} {log.action} - {log.created_at.strftime('%Y-%m-%d %H:%M')}")
255
269
 
256
- if obj.logs.count() > 5:
257
- url = reverse('admin:maintenance_maintenancelog_changelist')
258
- html += f'<a href="{url}?site__id__exact={obj.id}">View all logs →</a>'
259
-
260
- return mark_safe(html)
261
-
262
- logs_preview.short_description = "Recent Activity"
270
+ return "\n".join(log_list)
263
271
 
264
- # Admin Actions
265
-
266
- @action(description="🔧 Enable maintenance mode")
272
+ @action(description="Enable maintenance mode", variant=ActionVariant.WARNING)
267
273
  def enable_maintenance_action(self, request: HttpRequest, queryset) -> None:
268
- """Enable maintenance for selected sites."""
274
+ """Enable maintenance mode for selected sites."""
275
+ service = MaintenanceService()
269
276
  success_count = 0
270
277
  error_count = 0
271
278
 
272
279
  for site in queryset:
273
280
  try:
274
- service = MaintenanceService(site)
275
- service.enable_maintenance("Enabled via admin interface")
281
+ service.enable_maintenance(site)
276
282
  success_count += 1
277
283
  except Exception as e:
278
284
  error_count += 1
279
- messages.error(request, f"Failed to enable maintenance for {site.domain}: {str(e)}")
280
-
281
- if success_count:
282
- messages.success(request, f"Successfully enabled maintenance for {success_count} sites")
285
+ messages.error(request, f"Failed to enable maintenance for {site.name}: {str(e)}")
283
286
 
284
- if error_count:
285
- messages.error(request, f"Failed to enable maintenance for {error_count} sites")
287
+ if success_count > 0:
288
+ messages.success(request, f"Successfully enabled maintenance for {success_count} sites.")
289
+ if error_count > 0:
290
+ messages.error(request, f"Failed to enable maintenance for {error_count} sites.")
286
291
 
287
- @action(description="🟢 Disable maintenance mode")
292
+ @action(description="Disable maintenance mode", variant=ActionVariant.SUCCESS)
288
293
  def disable_maintenance_action(self, request: HttpRequest, queryset) -> None:
289
- """Disable maintenance for selected sites."""
294
+ """Disable maintenance mode for selected sites."""
295
+ service = MaintenanceService()
290
296
  success_count = 0
291
297
  error_count = 0
292
298
 
293
299
  for site in queryset:
294
300
  try:
295
- service = MaintenanceService(site)
296
- service.disable_maintenance()
301
+ service.disable_maintenance(site)
297
302
  success_count += 1
298
303
  except Exception as e:
299
304
  error_count += 1
300
- messages.error(request, f"Failed to disable maintenance for {site.domain}: {str(e)}")
305
+ messages.error(request, f"Failed to disable maintenance for {site.name}: {str(e)}")
301
306
 
302
- if success_count:
303
- messages.success(request, f"Successfully disabled maintenance for {success_count} sites")
304
-
305
- if error_count:
306
- messages.error(request, f"Failed to disable maintenance for {error_count} sites")
307
-
308
- @action(description="🔄 Sync from Cloudflare")
309
- def sync_from_cloudflare_action(self, request: HttpRequest, queryset) -> None:
310
- """Sync selected sites from Cloudflare."""
311
- success_count = 0
312
- error_count = 0
307
+ if success_count > 0:
308
+ messages.success(request, f"Successfully disabled maintenance for {success_count} sites.")
309
+ if error_count > 0:
310
+ messages.error(request, f"Failed to disable maintenance for {error_count} sites.")
311
+
312
+ @action(description="Activate sites", variant=ActionVariant.SUCCESS)
313
+ def activate_sites_action(self, request: HttpRequest, queryset) -> None:
314
+ """Activate selected sites."""
315
+ count = queryset.update(is_active=True)
316
+ messages.success(request, f"Successfully activated {count} sites.")
317
+
318
+ @action(description="Deactivate sites", variant=ActionVariant.DANGER)
319
+ def deactivate_sites_action(self, request: HttpRequest, queryset) -> None:
320
+ """Deactivate selected sites."""
321
+ count = queryset.update(is_active=False)
322
+ messages.warning(request, f"Successfully deactivated {count} sites.")
323
+
324
+ @action(description="Sync with Cloudflare", variant=ActionVariant.INFO)
325
+ def sync_with_cloudflare_action(self, request: HttpRequest, queryset) -> None:
326
+ """Sync selected sites with Cloudflare."""
327
+ messages.info(request, f"Cloudflare sync initiated for {queryset.count()} sites.")
328
+
329
+ def changelist_view(self, request, extra_context=None):
330
+ """Add site statistics to changelist."""
331
+ extra_context = extra_context or {}
313
332
 
314
- for site in queryset:
315
- try:
316
- service = MaintenanceService(site)
317
- service.sync_site_from_cloudflare()
318
- success_count += 1
319
- except Exception as e:
320
- error_count += 1
321
- messages.error(request, f"Failed to sync {site.domain}: {str(e)}")
322
-
323
- if success_count:
324
- messages.success(request, f"Successfully synced {success_count} sites from Cloudflare")
325
-
326
- if error_count:
327
- messages.error(request, f"Failed to sync {error_count} sites")
328
-
329
- # Unfold detail actions (кнопки на странице отдельного объекта)
330
-
331
- @action(
332
- description="🔄 Sync with Cloudflare",
333
- url_path="sync-cloudflare",
334
- icon="refresh",
335
- variant=ActionVariant.INFO
336
- )
337
- def sync_with_cloudflare_detail(self, request, object_id):
338
- """Sync site with Cloudflare zones."""
339
- try:
340
- site = self.get_object(request, object_id)
341
- if not site:
342
- messages.error(request, "Site not found.")
343
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
344
-
345
- # Use convenience function for single site sync
346
- from ..services import sync_site_from_cloudflare
347
- log_entry = sync_site_from_cloudflare(site)
348
-
349
- if log_entry.status == log_entry.Status.SUCCESS:
350
- messages.success(
351
- request,
352
- f"Site '{site.name}' has been synchronized with Cloudflare."
353
- )
354
- else:
355
- messages.error(
356
- request,
357
- f"Failed to sync site '{site.name}': {log_entry.error_message}"
358
- )
359
-
360
- except Exception as e:
361
- messages.error(request, f"Failed to sync site: {str(e)}")
362
-
363
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
364
-
365
- @action(
366
- description="🔧 Enable Maintenance",
367
- url_path="enable-maintenance",
368
- icon="build",
369
- variant=ActionVariant.WARNING
370
- )
371
- def enable_maintenance_detail(self, request, object_id):
372
- """Enable maintenance mode for a site."""
373
- try:
374
- site = self.get_object(request, object_id)
375
- if not site:
376
- messages.error(request, "Site not found.")
377
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
378
-
379
- service = MaintenanceService(site)
380
- service.enable_maintenance("Enabled via admin interface")
381
-
382
- messages.success(request, f"Maintenance mode enabled for {site.name}.")
383
-
384
- except Exception as e:
385
- messages.error(request, f"Failed to enable maintenance: {str(e)}")
386
-
387
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
388
-
389
- @action(
390
- description="✅ Disable Maintenance",
391
- url_path="disable-maintenance",
392
- icon="check_circle",
393
- variant=ActionVariant.SUCCESS
394
- )
395
- def disable_maintenance_detail(self, request, object_id):
396
- """Disable maintenance mode for a site."""
397
- try:
398
- site = self.get_object(request, object_id)
399
- if not site:
400
- messages.error(request, "Site not found.")
401
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
402
-
403
- service = MaintenanceService(site)
404
- service.disable_maintenance()
405
-
406
- messages.success(request, f"Maintenance mode disabled for {site.name}.")
407
-
408
- except Exception as e:
409
- messages.error(request, f"Failed to disable maintenance: {str(e)}")
410
-
411
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
412
-
413
- # Unfold list actions (кнопки над списком)
414
-
415
- @action(
416
- description="🔄 Sync All Sites with Cloudflare",
417
- icon="sync",
418
- variant=ActionVariant.INFO,
419
- url_path="bulk-sync-sites"
420
- )
421
- def bulk_sync_sites(self, request):
422
- """Bulk sync all sites with Cloudflare."""
423
- try:
424
- from ..models import CloudflareSite
425
-
426
- # Use manager method for bulk sync
427
- result = CloudflareSite.objects.bulk_sync_all()
428
-
429
- if result.get('synced', 0) > 0:
430
- messages.success(
431
- request,
432
- f"Successfully synchronized {result['synced']} sites with Cloudflare."
433
- )
434
-
435
- if result.get('errors', 0) > 0:
436
- # Show detailed error messages
437
- error_details = result.get('error_details', [])
438
- for error in error_details:
439
- messages.error(request, f"Sync error: {error}")
440
-
441
- messages.warning(
442
- request,
443
- f"Synchronization completed with {result['errors']} errors."
444
- )
445
-
446
- if result.get('synced', 0) == 0 and result.get('errors', 0) == 0:
447
- messages.info(request, "No sites to synchronize.")
448
-
449
- except Exception as e:
450
- messages.error(request, f"Bulk sync failed: {str(e)}")
333
+ queryset = self.get_queryset(request)
334
+ stats = queryset.aggregate(
335
+ total_sites=Count('id'),
336
+ active_sites=Count('id', filter=Q(is_active=True)),
337
+ maintenance_sites=Count('id', filter=Q(maintenance_active=True)),
338
+ subdomain_sites=Count('id', filter=Q(include_subdomains=True))
339
+ )
451
340
 
452
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
453
-
454
- @action(
455
- description="🔍 Discover New Sites",
456
- icon="search",
457
- variant=ActionVariant.SUCCESS,
458
- url_path="bulk-discover-sites"
459
- )
460
- def bulk_discover_sites(self, request):
461
- """Discover new sites from Cloudflare."""
462
- try:
463
- from ..models import CloudflareSite, CloudflareApiKey
464
-
465
- # Check if we have active API keys
466
- if not CloudflareApiKey.objects.filter(is_active=True).exists():
467
- messages.error(request, "No active API keys found. Please add Cloudflare API keys first.")
468
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
469
-
470
- # Use manager method for discovery
471
- result = CloudflareSite.objects.discover_all_sites()
472
-
473
- if result.get('discovered', 0) > 0:
474
- messages.success(
475
- request,
476
- f"Successfully discovered {result['discovered']} new sites from Cloudflare."
477
- )
478
- else:
479
- messages.info(request, "No new sites discovered.")
480
-
481
- if result.get('errors', 0) > 0:
482
- messages.warning(request, f"Discovery completed with {result['errors']} API key errors.")
483
-
484
- except Exception as e:
485
- messages.error(request, f"Site discovery failed: {str(e)}")
341
+ extra_context['site_stats'] = {
342
+ 'total_sites': stats['total_sites'] or 0,
343
+ 'active_sites': stats['active_sites'] or 0,
344
+ 'maintenance_sites': stats['maintenance_sites'] or 0,
345
+ 'subdomain_sites': stats['subdomain_sites'] or 0
346
+ }
486
347
 
487
- return redirect(request.META.get('HTTP_REFERER', '/admin/'))
348
+ return super().changelist_view(request, extra_context)