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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {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
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(
|
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())
|
django_cfg/core/generation.py
CHANGED
@@ -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":
|
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
|