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
@@ -0,0 +1,569 @@
1
+ """
2
+ API Views for Django CFG Tasks app.
3
+
4
+ Provides DRF ViewSets for task management with nested router structure.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, Any
9
+
10
+ from rest_framework import viewsets, status
11
+ from rest_framework.response import Response
12
+ from rest_framework.permissions import IsAdminUser
13
+ from rest_framework.authentication import SessionAuthentication, BasicAuthentication
14
+ from rest_framework.decorators import action
15
+ from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiParameter
16
+
17
+ from django_cfg.modules.django_tasks import DjangoTasks
18
+ from ..serializers import (
19
+ QueueStatusSerializer,
20
+ TaskStatisticsSerializer,
21
+ WorkerActionSerializer,
22
+ QueueActionSerializer,
23
+ APIResponseSerializer
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class TaskManagementViewSet(viewsets.GenericViewSet):
29
+ """
30
+ Main ViewSet for comprehensive task management.
31
+
32
+ Provides all task-related operations in a single ViewSet with nested actions.
33
+ """
34
+
35
+ authentication_classes = [SessionAuthentication, BasicAuthentication]
36
+ permission_classes = [IsAdminUser]
37
+ serializer_class = APIResponseSerializer # Default serializer for the viewset
38
+
39
+ def get_serializer_class(self):
40
+ """Return the appropriate serializer class based on the action."""
41
+ if self.action == 'queue_status':
42
+ return QueueStatusSerializer
43
+ elif self.action == 'queue_manage':
44
+ return QueueActionSerializer
45
+ elif self.action == 'worker_manage':
46
+ return WorkerActionSerializer
47
+ elif self.action == 'task_statistics':
48
+ return TaskStatisticsSerializer
49
+ return APIResponseSerializer
50
+
51
+ @action(detail=False, methods=['get'], url_path='queues/status')
52
+ @extend_schema(
53
+ summary="Get queue status",
54
+ description="Retrieve current status of all task queues including pending and failed task counts",
55
+ responses={
56
+ 200: OpenApiResponse(response=QueueStatusSerializer, description="Queue status retrieved successfully"),
57
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Internal server error")
58
+ },
59
+ tags=["Queue Management"]
60
+ )
61
+ def queue_status(self, request):
62
+ """Get current status of all queues."""
63
+ try:
64
+ from ..utils.simulator import TaskSimulator
65
+ simulator = TaskSimulator()
66
+ status_data = simulator.get_current_queue_status()
67
+
68
+ return Response({
69
+ 'success': True,
70
+ 'data': status_data
71
+ })
72
+
73
+ except Exception as e:
74
+ logger.error(f"Queue status API error: {e}")
75
+ return Response({
76
+ 'success': False,
77
+ 'error': str(e)
78
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
79
+
80
+ @action(detail=False, methods=['post'], url_path='queues/manage')
81
+ @extend_schema(
82
+ summary="Manage queues",
83
+ description="Perform management operations on queues (clear, purge, etc.)",
84
+ request=QueueActionSerializer,
85
+ responses={
86
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Queue operation completed successfully"),
87
+ 400: OpenApiResponse(response=APIResponseSerializer, description="Invalid request data"),
88
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Internal server error")
89
+ },
90
+ tags=["Queue Management"]
91
+ )
92
+ def queue_manage(self, request):
93
+ """Manage queue operations (clear, purge, etc.)."""
94
+ try:
95
+ serializer = QueueActionSerializer(data=request.data)
96
+ if not serializer.is_valid():
97
+ return Response({
98
+ 'success': False,
99
+ 'error': 'Invalid request data',
100
+ 'details': serializer.errors
101
+ }, status=status.HTTP_400_BAD_REQUEST)
102
+
103
+ action_type = serializer.validated_data['action']
104
+ queue_name = serializer.validated_data.get('queue_name')
105
+
106
+ tasks_service = DjangoTasks()
107
+
108
+ if action_type == 'clear_all':
109
+ result = self._clear_all_queues(tasks_service)
110
+ elif action_type == 'clear_queue' and queue_name:
111
+ result = self._clear_queue(tasks_service, queue_name)
112
+ elif action_type == 'purge_failed':
113
+ result = self._purge_failed_tasks(tasks_service, queue_name)
114
+ else:
115
+ return Response({
116
+ 'success': False,
117
+ 'error': 'Invalid action or missing queue_name'
118
+ }, status=status.HTTP_400_BAD_REQUEST)
119
+
120
+ return Response({
121
+ 'success': True,
122
+ 'data': result
123
+ })
124
+
125
+ except Exception as e:
126
+ logger.error(f"Queue management API error: {e}")
127
+ return Response({
128
+ 'success': False,
129
+ 'error': str(e)
130
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
131
+
132
+ @action(detail=False, methods=['get'], url_path='tasks/stats')
133
+ @extend_schema(
134
+ summary="Get task statistics",
135
+ description="Retrieve comprehensive task execution statistics and recent task history",
136
+ responses={
137
+ 200: OpenApiResponse(response=TaskStatisticsSerializer, description="Task statistics retrieved successfully"),
138
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Internal server error")
139
+ },
140
+ tags=["Task Management"]
141
+ )
142
+ def task_statistics(self, request):
143
+ """Get task execution statistics."""
144
+ try:
145
+ from ..utils.simulator import TaskSimulator
146
+ simulator = TaskSimulator()
147
+ stats_data = simulator.get_current_task_statistics()
148
+
149
+ return Response({
150
+ 'success': True,
151
+ 'data': stats_data
152
+ })
153
+
154
+ except Exception as e:
155
+ logger.error(f"Task statistics API error: {e}")
156
+ return Response({
157
+ 'success': False,
158
+ 'error': str(e)
159
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
160
+
161
+ @action(detail=False, methods=['get'], url_path='tasks/list')
162
+ @extend_schema(
163
+ summary="Get task list",
164
+ description="Retrieve paginated list of tasks with optional filtering",
165
+ parameters=[
166
+ OpenApiParameter(name='status', description='Filter by task status', required=False, type=str),
167
+ OpenApiParameter(name='actor', description='Filter by actor name', required=False, type=str),
168
+ OpenApiParameter(name='page', description='Page number', required=False, type=int),
169
+ OpenApiParameter(name='page_size', description='Items per page', required=False, type=int),
170
+ ],
171
+ responses={
172
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Task list retrieved successfully"),
173
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Internal server error")
174
+ },
175
+ tags=["Task Management"]
176
+ )
177
+ def task_list(self, request):
178
+ """Get paginated task list with filtering."""
179
+ try:
180
+ from ..utils.simulator import TaskSimulator
181
+ simulator = TaskSimulator()
182
+
183
+ # For now, return mock task data since we don't have real task list in simulator
184
+ # This could be enhanced later to support real task data
185
+ mock_data = {
186
+ 'tasks': [
187
+ {
188
+ 'id': 1,
189
+ 'actor_name': 'send_email_task',
190
+ 'status': 'completed',
191
+ 'queue_name': 'default',
192
+ 'created_at': '2025-09-29T12:30:00Z',
193
+ 'updated_at': '2025-09-29T12:30:05Z',
194
+ 'message_id': 'msg_001'
195
+ },
196
+ {
197
+ 'id': 2,
198
+ 'actor_name': 'process_payment',
199
+ 'status': 'running',
200
+ 'queue_name': 'payments',
201
+ 'created_at': '2025-09-29T12:45:00Z',
202
+ 'updated_at': '2025-09-29T12:46:00Z',
203
+ 'message_id': 'msg_002'
204
+ },
205
+ {
206
+ 'id': 3,
207
+ 'actor_name': 'generate_report',
208
+ 'status': 'pending',
209
+ 'queue_name': 'background',
210
+ 'created_at': '2025-09-29T13:00:00Z',
211
+ 'updated_at': '2025-09-29T13:00:00Z',
212
+ 'message_id': 'msg_003'
213
+ },
214
+ {
215
+ 'id': 4,
216
+ 'actor_name': 'sync_data',
217
+ 'status': 'failed',
218
+ 'queue_name': 'high',
219
+ 'created_at': '2025-09-29T11:30:00Z',
220
+ 'updated_at': '2025-09-29T11:35:00Z',
221
+ 'message_id': 'msg_004'
222
+ }
223
+ ],
224
+ 'pagination': {
225
+ 'page': 1,
226
+ 'page_size': 20,
227
+ 'total_pages': 1,
228
+ 'total_count': 4,
229
+ 'has_next': False,
230
+ 'has_previous': False
231
+ },
232
+ 'simulated': True
233
+ }
234
+
235
+ return Response({
236
+ 'success': True,
237
+ 'data': mock_data
238
+ })
239
+
240
+ except Exception as e:
241
+ logger.error(f"Task list API error: {e}")
242
+ return Response({
243
+ 'success': False,
244
+ 'error': str(e)
245
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
246
+
247
+ @action(detail=False, methods=['get'], url_path='workers/list')
248
+ @extend_schema(
249
+ summary="Get workers list",
250
+ description="Retrieve detailed list of active workers",
251
+ responses={
252
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Workers list retrieved successfully"),
253
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Internal server error")
254
+ },
255
+ tags=["Worker Management"]
256
+ )
257
+ def workers_list(self, request):
258
+ """Get detailed list of workers."""
259
+ try:
260
+ from ..utils.simulator import TaskSimulator
261
+ simulator = TaskSimulator()
262
+ workers_data = simulator.get_current_workers_list()
263
+
264
+ return Response({
265
+ 'success': True,
266
+ 'data': workers_data
267
+ })
268
+
269
+ except Exception as e:
270
+ logger.error(f"Workers list API error: {e}")
271
+ return Response({
272
+ 'success': False,
273
+ 'error': str(e)
274
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
275
+
276
+ @action(detail=False, methods=['post'], url_path='workers/manage')
277
+ @extend_schema(
278
+ summary="Manage workers",
279
+ description="Perform management operations on workers (restart, stop, etc.)",
280
+ request=WorkerActionSerializer,
281
+ responses={
282
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Worker operation completed successfully"),
283
+ 400: OpenApiResponse(response=APIResponseSerializer, description="Invalid request data"),
284
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Internal server error")
285
+ },
286
+ tags=["Worker Management"]
287
+ )
288
+ def worker_manage(self, request):
289
+ """Manage worker operations."""
290
+ try:
291
+ serializer = WorkerActionSerializer(data=request.data)
292
+ if not serializer.is_valid():
293
+ return Response({
294
+ 'success': False,
295
+ 'error': 'Invalid request data',
296
+ 'details': serializer.errors
297
+ }, status=status.HTTP_400_BAD_REQUEST)
298
+
299
+ action_type = serializer.validated_data['action']
300
+ worker_id = serializer.validated_data.get('worker_id')
301
+
302
+ # Worker management operations would go here
303
+ # For now, return a placeholder response
304
+ return Response({
305
+ 'success': True,
306
+ 'data': {
307
+ 'message': f'Worker {action_type} operation initiated',
308
+ 'worker_id': worker_id,
309
+ 'action': action_type
310
+ }
311
+ })
312
+
313
+ except Exception as e:
314
+ logger.error(f"Worker management API error: {e}")
315
+ return Response({
316
+ 'success': False,
317
+ 'error': str(e)
318
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
319
+
320
+ @action(detail=False, methods=['post'], url_path='simulate')
321
+ @extend_schema(
322
+ summary="Simulate test data",
323
+ description="Create test data in Redis for dashboard testing",
324
+ request=None,
325
+ responses={
326
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Simulation completed successfully"),
327
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Simulation failed")
328
+ },
329
+ tags=["Task Management"]
330
+ )
331
+ def simulate_data(self, request):
332
+ """Simulate test data for dashboard testing."""
333
+ try:
334
+ from ..utils.simulator import TaskSimulator
335
+
336
+ simulator = TaskSimulator()
337
+ result = simulator.run_simulation(workers=3, clear_first=True)
338
+
339
+ return Response({
340
+ 'success': True,
341
+ 'data': {
342
+ 'message': 'Test data simulation completed successfully',
343
+ 'details': result
344
+ }
345
+ })
346
+
347
+ except Exception as e:
348
+ logger.error(f"Simulation API error: {e}")
349
+ return Response({
350
+ 'success': False,
351
+ 'error': str(e)
352
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
353
+
354
+ @action(detail=False, methods=['post'], url_path='clear')
355
+ @extend_schema(
356
+ summary="Clear test data",
357
+ description="Clear all test data from Redis",
358
+ request=None,
359
+ responses={
360
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Data cleared successfully"),
361
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Clear operation failed")
362
+ },
363
+ tags=["Task Management"]
364
+ )
365
+ def clear_data(self, request):
366
+ """Clear all test data from Redis."""
367
+ try:
368
+ from ..utils.simulator import TaskSimulator
369
+
370
+ simulator = TaskSimulator()
371
+ simulator.clear_all_data()
372
+
373
+ return Response({
374
+ 'success': True,
375
+ 'data': {
376
+ 'message': 'All test data cleared successfully'
377
+ }
378
+ })
379
+
380
+ except Exception as e:
381
+ logger.error(f"Clear data API error: {e}")
382
+ return Response({
383
+ 'success': False,
384
+ 'error': str(e)
385
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
386
+
387
+ @action(detail=False, methods=['post'], url_path='clear-queues')
388
+ @extend_schema(
389
+ summary="Clear all queues",
390
+ description="Clear all tasks from all Dramatiq queues",
391
+ request=None,
392
+ responses={
393
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Queues cleared successfully"),
394
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Clear operation failed")
395
+ },
396
+ tags=["Queue Management"]
397
+ )
398
+ def clear_all_queues(self, request):
399
+ """Clear all tasks from all Dramatiq queues."""
400
+ try:
401
+ from django_cfg.modules.django_tasks import DjangoTasks
402
+
403
+ tasks_service = DjangoTasks()
404
+ redis_client = tasks_service.get_redis_client()
405
+
406
+ if not redis_client:
407
+ return Response({
408
+ 'success': False,
409
+ 'error': 'Redis connection not available'
410
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
411
+
412
+ # Get all Dramatiq queue keys
413
+ queue_keys = redis_client.keys('dramatiq:*.msgs') # Main queues
414
+ failed_keys = redis_client.keys('dramatiq:*.DQ') # Failed queues
415
+ ack_keys = redis_client.keys('dramatiq:__acks__*') # Acknowledgments
416
+
417
+ cleared_count = 0
418
+
419
+ # Clear main queues
420
+ for key in queue_keys:
421
+ redis_client.delete(key)
422
+ cleared_count += 1
423
+
424
+ # Clear failed queues
425
+ for key in failed_keys:
426
+ redis_client.delete(key)
427
+ cleared_count += 1
428
+
429
+ # Clear acknowledgments
430
+ for key in ack_keys:
431
+ redis_client.delete(key)
432
+ cleared_count += 1
433
+
434
+ return Response({
435
+ 'success': True,
436
+ 'message': f'Cleared {cleared_count} Dramatiq keys from Redis'
437
+ })
438
+
439
+ except Exception as e:
440
+ logger.error(f"Clear queues API error: {e}")
441
+ return Response({
442
+ 'success': False,
443
+ 'error': str(e)
444
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
445
+
446
+ @action(detail=False, methods=['post'], url_path='purge-failed')
447
+ @extend_schema(
448
+ summary="Purge failed tasks",
449
+ description="Remove all failed tasks from queues",
450
+ request=None,
451
+ responses={
452
+ 200: OpenApiResponse(response=APIResponseSerializer, description="Failed tasks purged successfully"),
453
+ 500: OpenApiResponse(response=APIResponseSerializer, description="Purge operation failed")
454
+ },
455
+ tags=["Queue Management"]
456
+ )
457
+ def purge_failed_tasks(self, request):
458
+ """Purge all failed tasks from queues."""
459
+ try:
460
+ from django_cfg.modules.django_tasks import DjangoTasks
461
+
462
+ tasks_service = DjangoTasks()
463
+ redis_client = tasks_service.get_redis_client()
464
+
465
+ if not redis_client:
466
+ return Response({
467
+ 'success': False,
468
+ 'error': 'Redis connection not available'
469
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
470
+
471
+ # Get only failed queue keys (DQ = Dead Queue)
472
+ failed_keys = redis_client.keys('dramatiq:*.DQ')
473
+
474
+ cleared_count = 0
475
+
476
+ # Clear only failed queues
477
+ for key in failed_keys:
478
+ failed_count = redis_client.llen(key)
479
+ if failed_count > 0:
480
+ redis_client.delete(key)
481
+ cleared_count += failed_count
482
+
483
+ return Response({
484
+ 'success': True,
485
+ 'message': f'Purged {cleared_count} failed tasks from queues'
486
+ })
487
+
488
+ except Exception as e:
489
+ logger.error(f"Purge failed tasks API error: {e}")
490
+ return Response({
491
+ 'success': False,
492
+ 'error': str(e)
493
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
494
+
495
+ # Helper methods
496
+ def _clear_all_queues(self, tasks_service: DjangoTasks) -> Dict[str, Any]:
497
+ """Clear all queues."""
498
+ try:
499
+ redis_client = tasks_service.get_redis_client()
500
+ if not redis_client:
501
+ return {'error': 'Redis connection not available'}
502
+
503
+ # Get all queue keys and clear them
504
+ queue_keys = redis_client.keys("dramatiq:queue:*")
505
+ cleared_count = 0
506
+
507
+ for key in queue_keys:
508
+ redis_client.delete(key)
509
+ cleared_count += 1
510
+
511
+ return {
512
+ 'message': f'Cleared {cleared_count} queues',
513
+ 'cleared_count': cleared_count
514
+ }
515
+
516
+ except Exception as e:
517
+ return {'error': str(e)}
518
+
519
+ def _clear_queue(self, tasks_service: DjangoTasks, queue_name: str) -> Dict[str, Any]:
520
+ """Clear specific queue."""
521
+ try:
522
+ redis_client = tasks_service.get_redis_client()
523
+ if not redis_client:
524
+ return {'error': 'Redis connection not available'}
525
+
526
+ queue_key = f"dramatiq:queue:{queue_name}"
527
+ cleared_count = redis_client.delete(queue_key)
528
+
529
+ return {
530
+ 'message': f'Cleared queue {queue_name}',
531
+ 'queue_name': queue_name,
532
+ 'cleared': cleared_count > 0
533
+ }
534
+
535
+ except Exception as e:
536
+ return {'error': str(e)}
537
+
538
+ def _purge_failed_tasks(self, tasks_service: DjangoTasks, queue_name: str = None) -> Dict[str, Any]:
539
+ """Purge failed tasks."""
540
+ try:
541
+ redis_client = tasks_service.get_redis_client()
542
+ if not redis_client:
543
+ return {'error': 'Redis connection not available'}
544
+
545
+ if queue_name:
546
+ # Clear specific queue's failed tasks
547
+ failed_key = f"dramatiq:queue:{queue_name}.DLQ"
548
+ cleared_count = redis_client.delete(failed_key)
549
+ return {
550
+ 'message': f'Purged failed tasks from {queue_name}',
551
+ 'queue_name': queue_name,
552
+ 'cleared_count': cleared_count
553
+ }
554
+ else:
555
+ # Clear all failed task queues
556
+ failed_keys = redis_client.keys("dramatiq:queue:*.DLQ")
557
+ cleared_count = 0
558
+
559
+ for key in failed_keys:
560
+ redis_client.delete(key)
561
+ cleared_count += 1
562
+
563
+ return {
564
+ 'message': f'Purged failed tasks from {cleared_count} queues',
565
+ 'cleared_count': cleared_count
566
+ }
567
+
568
+ except Exception as e:
569
+ return {'error': str(e)}
@@ -0,0 +1,58 @@
1
+ """
2
+ Dashboard views for Django CFG Tasks app.
3
+
4
+ Provides template-based dashboard views for task monitoring.
5
+ """
6
+
7
+ import logging
8
+ from django.shortcuts import render
9
+ from django.contrib.admin.views.decorators import staff_member_required
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @staff_member_required
15
+ def dashboard_view(request):
16
+ """
17
+ Main dashboard view for task monitoring.
18
+
19
+ Provides a comprehensive overview of:
20
+ - Queue status and statistics
21
+ - Worker information
22
+ - Task execution metrics
23
+ - Recent task history
24
+ """
25
+ try:
26
+ # Use simulator to get data
27
+ from ..utils.simulator import TaskSimulator
28
+ simulator = TaskSimulator()
29
+
30
+ # Prepare context data
31
+ context = {
32
+ 'queue_status': simulator.get_current_queue_status(),
33
+ 'task_stats': simulator.get_current_task_statistics(),
34
+ }
35
+
36
+ return render(request, 'tasks/pages/dashboard.html', context)
37
+
38
+ except Exception as e:
39
+ logger.error(f"Dashboard view error: {e}")
40
+
41
+ # Provide fallback context for error cases
42
+ context = {
43
+ 'queue_status': {
44
+ 'error': str(e),
45
+ 'queues': {},
46
+ 'workers': 0,
47
+ 'redis_connected': False,
48
+ 'timestamp': None
49
+ },
50
+ 'task_stats': {
51
+ 'error': str(e),
52
+ 'statistics': {'total': 0},
53
+ 'recent_tasks': [],
54
+ 'timestamp': None
55
+ }
56
+ }
57
+
58
+ return render(request, 'tasks/pages/dashboard.html', context)
django_cfg/config.py CHANGED
@@ -5,7 +5,7 @@ Configuration settings for the django-cfg library itself.
5
5
  """
6
6
 
7
7
  from typing import List
8
- from .modules.django_unfold.icons import Icons
8
+ from .modules.django_admin.icons import Icons
9
9
  from .modules.django_unfold.models.dropdown import SiteDropdownItem
10
10
 
11
11
 
django_cfg/core/config.py CHANGED
@@ -19,13 +19,15 @@ from urllib.parse import urlparse
19
19
  from django_cfg import (
20
20
  ConfigurationError, ValidationError, EnvironmentError,
21
21
  DatabaseConfig, CacheConfig, EmailConfig, TelegramConfig,
22
- UnfoldConfig, DRFConfig, SpectacularConfig, LimitsConfig
22
+ UnfoldConfig, DRFConfig, SpectacularConfig, LimitsConfig, ApiKeys
23
23
  )
24
24
  from django_cfg.models.tasks import TaskConfig
25
25
  from django_cfg.models.payments import PaymentsConfig
26
26
 
27
27
  # Default apps
28
28
  DEFAULT_APPS = [
29
+ # WhiteNoise for static files (must be before django.contrib.staticfiles)
30
+ "whitenoise.runserver_nostatic",
29
31
  # Unfold
30
32
  "unfold",
31
33
  "unfold.contrib.filters", # optional, if special filters are needed
@@ -329,6 +331,12 @@ class DjangoConfig(BaseModel):
329
331
  description="Application limits configuration (file uploads, requests, etc.)",
330
332
  )
331
333
 
334
+ # === API Keys Configuration ===
335
+ api_keys: Optional[ApiKeys] = Field(
336
+ default=None,
337
+ description="API keys for external services (OpenAI, OpenRouter, etc.)",
338
+ )
339
+
332
340
  # === Middleware Configuration ===
333
341
  custom_middleware: List[str] = Field(
334
342
  default_factory=list,
@@ -776,15 +784,12 @@ class DjangoConfig(BaseModel):
776
784
 
777
785
  # Add CORS middleware if security domains are configured
778
786
  if self.security_domains:
779
- middleware.insert(1, "corsheaders.middleware.CorsMiddleware")
787
+ middleware.insert(2, "corsheaders.middleware.CorsMiddleware") # Insert after WhiteNoise
780
788
 
781
789
  # Add Django CFG middleware based on enabled features
782
790
  if self.enable_accounts:
783
791
  middleware.append("django_cfg.middleware.UserActivityMiddleware")
784
792
 
785
- # Add static no-cache middleware (auto-detects development mode)
786
- middleware.append("django_cfg.middleware.StaticNoCacheMiddleware")
787
-
788
793
  # Add payments middleware if enabled
789
794
  if self.payments and self.payments.enabled:
790
795
  middleware.extend(self.payments.get_middleware_classes())
@@ -332,7 +332,7 @@ class SettingsGenerator:
332
332
  "STATICFILES_STORAGE": "whitenoise.storage.CompressedManifestStaticFilesStorage",
333
333
  "WHITENOISE_USE_FINDERS": True,
334
334
  "WHITENOISE_AUTOREFRESH": config.debug,
335
- "WHITENOISE_MAX_AGE": 31536000, # 1 year
335
+ "WHITENOISE_MAX_AGE": 0 if config.debug else 3600, # No cache in debug, 1 hour
336
336
  }
337
337
 
338
338
  # Set paths relative to base directory