django-cfg 1.5.1__py3-none-any.whl → 1.5.2__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +2 -3
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +6 -7
- django_cfg/core/base/config_model.py +10 -26
- django_cfg/core/builders/apps_builder.py +4 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/orchestrator.py +9 -19
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +4 -6
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/html/composition.py +9 -2
- django_cfg/modules/django_unfold/navigation.py +1 -26
- django_cfg/pyproject.toml +4 -4
- django_cfg/registry/core.py +4 -7
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/METADATA +5 -6
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/RECORD +77 -82
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/config.py +0 -98
- django_cfg/apps/tasks/admin/task_log.py +0 -238
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/migrations/__init__.py +0 -0
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django-RQ Worker Management ViewSet.
|
|
3
|
+
|
|
4
|
+
Provides REST API endpoints for monitoring RQ workers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django_cfg.mixins import AdminAPIMixin
|
|
8
|
+
from django_cfg.modules.django_logging import get_logger
|
|
9
|
+
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
|
10
|
+
from rest_framework import status, viewsets
|
|
11
|
+
from rest_framework.decorators import action
|
|
12
|
+
from rest_framework.response import Response
|
|
13
|
+
|
|
14
|
+
from ..serializers import WorkerSerializer, WorkerStatsSerializer
|
|
15
|
+
from ..services import worker_to_model
|
|
16
|
+
|
|
17
|
+
logger = get_logger("rq.workers")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkerViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
21
|
+
"""
|
|
22
|
+
ViewSet for RQ worker monitoring.
|
|
23
|
+
|
|
24
|
+
Provides endpoints for:
|
|
25
|
+
- Listing all workers with statistics
|
|
26
|
+
- Getting worker aggregated stats
|
|
27
|
+
- Getting individual worker details
|
|
28
|
+
|
|
29
|
+
Requires admin authentication (JWT, Session, or Basic Auth).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@extend_schema(
|
|
33
|
+
tags=["RQ Workers"],
|
|
34
|
+
summary="List all workers",
|
|
35
|
+
description="Returns list of all RQ workers with their current state. Supports filtering by state and queue.",
|
|
36
|
+
parameters=[
|
|
37
|
+
OpenApiParameter(
|
|
38
|
+
name="state",
|
|
39
|
+
type=str,
|
|
40
|
+
location=OpenApiParameter.QUERY,
|
|
41
|
+
required=False,
|
|
42
|
+
description="Filter by worker state (idle, busy, suspended)",
|
|
43
|
+
),
|
|
44
|
+
OpenApiParameter(
|
|
45
|
+
name="queue",
|
|
46
|
+
type=str,
|
|
47
|
+
location=OpenApiParameter.QUERY,
|
|
48
|
+
required=False,
|
|
49
|
+
description="Filter by queue name",
|
|
50
|
+
),
|
|
51
|
+
],
|
|
52
|
+
responses={
|
|
53
|
+
200: WorkerSerializer(many=True),
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
def list(self, request):
|
|
57
|
+
"""List all workers with optional filtering."""
|
|
58
|
+
try:
|
|
59
|
+
import django_rq
|
|
60
|
+
from rq import Worker
|
|
61
|
+
|
|
62
|
+
# Get query params for filtering
|
|
63
|
+
state_filter = request.query_params.get('state')
|
|
64
|
+
queue_filter = request.query_params.get('queue')
|
|
65
|
+
|
|
66
|
+
# Get all workers using connection from default queue
|
|
67
|
+
# All queues share the same Redis connection, so we only need one
|
|
68
|
+
queue = django_rq.get_queue('default')
|
|
69
|
+
all_workers = Worker.all(connection=queue.connection)
|
|
70
|
+
|
|
71
|
+
workers_data = []
|
|
72
|
+
for worker in all_workers:
|
|
73
|
+
try:
|
|
74
|
+
# Convert RQ Worker to Pydantic model
|
|
75
|
+
worker_model = worker_to_model(worker)
|
|
76
|
+
|
|
77
|
+
# Apply filters
|
|
78
|
+
if state_filter and worker_model.state != state_filter:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if queue_filter and queue_filter not in worker_model.get_queue_list():
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
# Convert Pydantic model to dict for DRF serializer
|
|
85
|
+
worker_dict = worker_model.model_dump()
|
|
86
|
+
|
|
87
|
+
# DRF serializer expects 'queues' as list, not comma-separated string
|
|
88
|
+
worker_dict['queues'] = worker_model.get_queue_list()
|
|
89
|
+
|
|
90
|
+
# DRF serializer expects 'current_job' not 'current_job_id'
|
|
91
|
+
worker_dict['current_job'] = worker_dict.pop('current_job_id')
|
|
92
|
+
|
|
93
|
+
serializer = WorkerSerializer(data=worker_dict)
|
|
94
|
+
serializer.is_valid(raise_exception=True)
|
|
95
|
+
workers_data.append(serializer.data)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.debug(f"Failed to get worker {worker.name}: {e}")
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
return Response(workers_data)
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
import traceback
|
|
105
|
+
logger.error(f"Worker list error: {e}", exc_info=True)
|
|
106
|
+
return Response(
|
|
107
|
+
{
|
|
108
|
+
"error": str(e),
|
|
109
|
+
"traceback": traceback.format_exc(),
|
|
110
|
+
},
|
|
111
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@extend_schema(
|
|
115
|
+
tags=["RQ Workers"],
|
|
116
|
+
summary="Get worker statistics",
|
|
117
|
+
description="Returns aggregated statistics for all workers.",
|
|
118
|
+
responses={
|
|
119
|
+
200: WorkerStatsSerializer,
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
@action(detail=False, methods=["get"], url_path="stats")
|
|
123
|
+
def stats(self, request):
|
|
124
|
+
"""Get aggregated worker statistics."""
|
|
125
|
+
try:
|
|
126
|
+
import django_rq
|
|
127
|
+
from rq import Worker
|
|
128
|
+
|
|
129
|
+
# Get all workers using connection from default queue
|
|
130
|
+
# All queues share the same Redis connection, so we only need one
|
|
131
|
+
queue = django_rq.get_queue('default')
|
|
132
|
+
all_workers = Worker.all(connection=queue.connection)
|
|
133
|
+
|
|
134
|
+
# Count by state
|
|
135
|
+
busy_workers = 0
|
|
136
|
+
idle_workers = 0
|
|
137
|
+
suspended_workers = 0
|
|
138
|
+
total_successful = 0
|
|
139
|
+
total_failed = 0
|
|
140
|
+
total_working_time = 0.0
|
|
141
|
+
|
|
142
|
+
workers_list = []
|
|
143
|
+
|
|
144
|
+
for worker in all_workers:
|
|
145
|
+
try:
|
|
146
|
+
# Convert RQ Worker to Pydantic model
|
|
147
|
+
worker_model = worker_to_model(worker)
|
|
148
|
+
|
|
149
|
+
# Count by state
|
|
150
|
+
if worker_model.is_busy:
|
|
151
|
+
busy_workers += 1
|
|
152
|
+
elif worker_model.is_idle:
|
|
153
|
+
idle_workers += 1
|
|
154
|
+
elif worker_model.state == 'suspended':
|
|
155
|
+
suspended_workers += 1
|
|
156
|
+
|
|
157
|
+
total_successful += worker_model.successful_job_count
|
|
158
|
+
total_failed += worker_model.failed_job_count
|
|
159
|
+
total_working_time += worker_model.total_working_time
|
|
160
|
+
|
|
161
|
+
# Convert to dict for serializer
|
|
162
|
+
worker_dict = worker_model.model_dump()
|
|
163
|
+
worker_dict['queues'] = worker_model.get_queue_list()
|
|
164
|
+
worker_dict['current_job'] = worker_dict.pop('current_job_id')
|
|
165
|
+
workers_list.append(worker_dict)
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.debug(f"Failed to process worker: {e}")
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
stats_data = {
|
|
172
|
+
"total_workers": len(all_workers),
|
|
173
|
+
"busy_workers": busy_workers,
|
|
174
|
+
"idle_workers": idle_workers,
|
|
175
|
+
"suspended_workers": suspended_workers,
|
|
176
|
+
"total_successful_jobs": total_successful,
|
|
177
|
+
"total_failed_jobs": total_failed,
|
|
178
|
+
"total_working_time": total_working_time,
|
|
179
|
+
"workers": workers_list,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
serializer = WorkerStatsSerializer(data=stats_data)
|
|
183
|
+
serializer.is_valid(raise_exception=True)
|
|
184
|
+
return Response(serializer.data)
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
import traceback
|
|
188
|
+
logger.error(f"Worker stats error: {e}", exc_info=True)
|
|
189
|
+
return Response(
|
|
190
|
+
{
|
|
191
|
+
"error": str(e),
|
|
192
|
+
"traceback": traceback.format_exc(),
|
|
193
|
+
},
|
|
194
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
195
|
+
)
|
django_cfg/apps/urls.py
CHANGED
|
@@ -44,15 +44,15 @@ def get_enabled_cfg_apps() -> List[str]:
|
|
|
44
44
|
if base_module.is_agents_enabled():
|
|
45
45
|
enabled_apps.append("django_cfg.apps.agents")
|
|
46
46
|
|
|
47
|
-
if base_module.should_enable_tasks():
|
|
48
|
-
enabled_apps.append("django_cfg.apps.tasks")
|
|
49
|
-
|
|
50
47
|
if base_module.is_payments_enabled():
|
|
51
48
|
enabled_apps.append("django_cfg.apps.payments")
|
|
52
49
|
|
|
53
50
|
if base_module.is_centrifugo_enabled():
|
|
54
51
|
enabled_apps.append("django_cfg.apps.centrifugo")
|
|
55
52
|
|
|
53
|
+
if base_module.should_enable_rq():
|
|
54
|
+
enabled_apps.append("django_cfg.apps.rq")
|
|
55
|
+
|
|
56
56
|
if base_module.is_grpc_enabled():
|
|
57
57
|
enabled_apps.append("django_cfg.apps.grpc")
|
|
58
58
|
|
|
@@ -176,16 +176,15 @@ APP_URL_MAP = {
|
|
|
176
176
|
"django_cfg.apps.agents": [
|
|
177
177
|
("cfg/agents/", "django_cfg.apps.agents.urls"),
|
|
178
178
|
],
|
|
179
|
-
"django_cfg.apps.tasks": [
|
|
180
|
-
("cfg/tasks/", "django_cfg.apps.tasks.urls"),
|
|
181
|
-
],
|
|
182
179
|
"django_cfg.apps.payments": [
|
|
183
180
|
("cfg/payments/", "django_cfg.apps.payments.urls"),
|
|
184
|
-
# Payments v2.0: No separate urls_admin (uses Django Admin only)
|
|
185
181
|
],
|
|
186
182
|
"django_cfg.apps.centrifugo": [
|
|
187
183
|
("cfg/centrifugo/", "django_cfg.apps.centrifugo.urls"),
|
|
188
184
|
],
|
|
185
|
+
"django_cfg.apps.rq": [
|
|
186
|
+
("cfg/rq/", "django_cfg.apps.rq.urls"),
|
|
187
|
+
],
|
|
189
188
|
"django_cfg.apps.grpc": [
|
|
190
189
|
("cfg/grpc/", "django_cfg.apps.grpc.urls"),
|
|
191
190
|
],
|
|
@@ -20,9 +20,9 @@ from ...models import (
|
|
|
20
20
|
ApiKeys,
|
|
21
21
|
AxesConfig,
|
|
22
22
|
CacheConfig,
|
|
23
|
-
DjangoQ2Config,
|
|
24
23
|
CryptoFieldsConfig,
|
|
25
24
|
DatabaseConfig,
|
|
25
|
+
DjangoRQConfig,
|
|
26
26
|
DRFConfig,
|
|
27
27
|
EmailConfig,
|
|
28
28
|
LimitsConfig,
|
|
@@ -33,7 +33,6 @@ from ...models import (
|
|
|
33
33
|
from ...models.api.grpc import GRPCConfig
|
|
34
34
|
from ...models.ngrok import NgrokConfig
|
|
35
35
|
from ...models.payments import PaymentsConfig
|
|
36
|
-
from ...models.tasks import TaskConfig
|
|
37
36
|
from ...modules.nextjs_admin import NextJsAdminConfig
|
|
38
37
|
from ..exceptions import ConfigurationError
|
|
39
38
|
from ..types.enums import EnvironmentMode, StartupInfoMode
|
|
@@ -224,7 +223,7 @@ class DjangoConfig(BaseModel):
|
|
|
224
223
|
description=(
|
|
225
224
|
"Redis connection URL (redis://host:port/db). "
|
|
226
225
|
"If set and cache_default is None, automatically creates RedisCache backend. "
|
|
227
|
-
"Also used by
|
|
226
|
+
"Also used by Django-RQ if queues don't specify connection details."
|
|
228
227
|
),
|
|
229
228
|
)
|
|
230
229
|
|
|
@@ -324,16 +323,10 @@ class DjangoConfig(BaseModel):
|
|
|
324
323
|
description="Enable modern Tailwind CSS theme for Django REST Framework Browsable API",
|
|
325
324
|
)
|
|
326
325
|
|
|
327
|
-
# ===
|
|
328
|
-
|
|
326
|
+
# === Django-RQ Task Queue & Scheduler ===
|
|
327
|
+
django_rq: Optional[DjangoRQConfig] = Field(
|
|
329
328
|
default=None,
|
|
330
|
-
description="
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
# === Django-Q2 Task Scheduling ===
|
|
334
|
-
django_q2: Optional[DjangoQ2Config] = Field(
|
|
335
|
-
default=None,
|
|
336
|
-
description="Django-Q2 task scheduling and queue configuration",
|
|
329
|
+
description="Django-RQ task queue and scheduler configuration (sync tasks, cron scheduling)",
|
|
337
330
|
)
|
|
338
331
|
|
|
339
332
|
# === Centrifugo Configuration ===
|
|
@@ -641,28 +634,19 @@ class DjangoConfig(BaseModel):
|
|
|
641
634
|
**kwargs
|
|
642
635
|
)
|
|
643
636
|
|
|
644
|
-
def
|
|
637
|
+
def should_enable_rq(self) -> bool:
|
|
645
638
|
"""
|
|
646
|
-
Determine if
|
|
639
|
+
Determine if Django-RQ should be enabled.
|
|
647
640
|
|
|
648
|
-
|
|
649
|
-
1. Explicitly configured via tasks field
|
|
650
|
-
2. Knowledge base is enabled (requires background processing)
|
|
651
|
-
3. Agents are enabled (requires background processing)
|
|
641
|
+
Django-RQ is enabled if explicitly configured via django_rq field.
|
|
652
642
|
|
|
653
643
|
Returns:
|
|
654
|
-
True if
|
|
644
|
+
True if Django-RQ should be enabled, False otherwise
|
|
655
645
|
"""
|
|
656
|
-
|
|
657
|
-
if hasattr(self, 'tasks') and self.tasks and self.tasks.enabled:
|
|
658
|
-
return True
|
|
659
|
-
|
|
660
|
-
# Check if features that require tasks are enabled
|
|
661
|
-
if self.enable_knowbase or self.enable_agents:
|
|
646
|
+
if hasattr(self, 'django_rq') and self.django_rq and self.django_rq.enabled:
|
|
662
647
|
return True
|
|
663
648
|
|
|
664
649
|
return False
|
|
665
650
|
|
|
666
|
-
|
|
667
651
|
# Export main class
|
|
668
652
|
__all__ = ["DjangoConfig"]
|
|
@@ -23,7 +23,6 @@ class InstalledAppsBuilder:
|
|
|
23
23
|
- Combine default Django/third-party apps
|
|
24
24
|
- Add django-cfg apps based on enabled features
|
|
25
25
|
- Handle special ordering (accounts before admin)
|
|
26
|
-
- Auto-enable tasks if needed
|
|
27
26
|
- Auto-detect dashboard apps from Unfold
|
|
28
27
|
- Add project-specific apps
|
|
29
28
|
- Remove duplicates while preserving order
|
|
@@ -161,16 +160,10 @@ class InstalledAppsBuilder:
|
|
|
161
160
|
"""
|
|
162
161
|
apps = []
|
|
163
162
|
|
|
164
|
-
#
|
|
165
|
-
if self.config.
|
|
166
|
-
#
|
|
167
|
-
apps.append("django_cfg.apps.
|
|
168
|
-
|
|
169
|
-
# Add django-q2 if enabled
|
|
170
|
-
if hasattr(self.config, "django_q2") and self.config.django_q2 and self.config.django_q2.enabled:
|
|
171
|
-
apps.append("django_q")
|
|
172
|
-
# Auto-add django_q2 module for automatic schedule synchronization
|
|
173
|
-
apps.append("django_cfg.modules.django_q2")
|
|
163
|
+
# Add Django-RQ if enabled
|
|
164
|
+
if hasattr(self.config, "django_rq") and self.config.django_rq and self.config.django_rq.enabled:
|
|
165
|
+
apps.append("django_rq") # Core django-rq package
|
|
166
|
+
apps.append("django_cfg.apps.rq") # Django-CFG monitoring & API
|
|
174
167
|
|
|
175
168
|
# Add DRF Tailwind theme module (uses Tailwind via CDN)
|
|
176
169
|
if self.config.enable_drf_tailwind:
|
|
@@ -5,21 +5,18 @@ Contains generators for third-party integrations and frameworks:
|
|
|
5
5
|
- Session configuration
|
|
6
6
|
- External services (Telegram, Unfold, Constance)
|
|
7
7
|
- API frameworks (JWT, DRF, Spectacular, OpenAPI Client)
|
|
8
|
-
-
|
|
9
|
-
- Task scheduling (django-q2)
|
|
8
|
+
- Task scheduling (django-rq)
|
|
10
9
|
- Tailwind CSS configuration
|
|
11
10
|
"""
|
|
12
11
|
|
|
13
12
|
from .api import APIFrameworksGenerator
|
|
14
|
-
from .
|
|
13
|
+
from .django_rq import DjangoRQSettingsGenerator
|
|
15
14
|
from .sessions import SessionSettingsGenerator
|
|
16
|
-
from .tasks import TasksSettingsGenerator
|
|
17
15
|
from .third_party import ThirdPartyIntegrationsGenerator
|
|
18
16
|
|
|
19
17
|
__all__ = [
|
|
20
18
|
"SessionSettingsGenerator",
|
|
21
19
|
"ThirdPartyIntegrationsGenerator",
|
|
22
20
|
"APIFrameworksGenerator",
|
|
23
|
-
"
|
|
24
|
-
"DjangoQ2SettingsGenerator",
|
|
21
|
+
"DjangoRQSettingsGenerator",
|
|
25
22
|
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django-RQ Settings Generator.
|
|
3
|
+
|
|
4
|
+
Generates Django settings for django-rq task queue and scheduler.
|
|
5
|
+
Converts DjangoRQConfig Pydantic model to Django RQ_QUEUES and related settings.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
from ....models.django.django_rq import DjangoRQConfig
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ...base.config_model import DjangoConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DjangoRQSettingsGenerator:
|
|
17
|
+
"""
|
|
18
|
+
Settings generator for Django-RQ task queue.
|
|
19
|
+
|
|
20
|
+
Converts DjangoRQConfig to Django settings:
|
|
21
|
+
- RQ_QUEUES: Queue configurations
|
|
22
|
+
- RQ_SHOW_ADMIN_LINK: Show link in admin
|
|
23
|
+
- RQ_EXCEPTION_HANDLERS: Exception handlers
|
|
24
|
+
- RQ_API_TOKEN: API authentication token
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
>>> from django_cfg.models.django.django_rq import DjangoRQConfig
|
|
28
|
+
>>> config = DjangoRQConfig(enabled=True)
|
|
29
|
+
>>> generator = DjangoRQSettingsGenerator(config)
|
|
30
|
+
>>> settings = generator.generate()
|
|
31
|
+
>>> 'RQ_QUEUES' in settings
|
|
32
|
+
True
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config: DjangoRQConfig, parent_config: Optional["DjangoConfig"] = None):
|
|
36
|
+
"""
|
|
37
|
+
Initialize Django-RQ settings generator.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
config: DjangoRQConfig instance
|
|
41
|
+
parent_config: Parent DjangoConfig for accessing global settings like redis_url
|
|
42
|
+
"""
|
|
43
|
+
self.config = config
|
|
44
|
+
self.parent_config = parent_config
|
|
45
|
+
|
|
46
|
+
def generate(self) -> Dict[str, Any]:
|
|
47
|
+
"""
|
|
48
|
+
Generate Django-RQ settings.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dictionary with RQ_QUEUES and related configuration
|
|
52
|
+
"""
|
|
53
|
+
if not self.config.enabled:
|
|
54
|
+
return {}
|
|
55
|
+
|
|
56
|
+
# Use the model's built-in to_django_settings method
|
|
57
|
+
settings = self.config.to_django_settings(parent_config=self.parent_config)
|
|
58
|
+
|
|
59
|
+
return settings
|
|
60
|
+
|
|
61
|
+
def validate(self) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Validate Django-RQ configuration.
|
|
64
|
+
|
|
65
|
+
Ensures:
|
|
66
|
+
- At least 'default' queue exists (validated by Pydantic)
|
|
67
|
+
- Queue names are unique (validated by Pydantic)
|
|
68
|
+
- Queue configurations are valid
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ValueError: If configuration is invalid
|
|
72
|
+
"""
|
|
73
|
+
if not self.config.enabled:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Validation is now handled by Pydantic field validators
|
|
77
|
+
# This method is kept for potential custom validation logic
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = ['DjangoRQSettingsGenerator']
|
|
@@ -77,9 +77,8 @@ class SettingsOrchestrator:
|
|
|
77
77
|
settings.update(self._generate_session_settings())
|
|
78
78
|
settings.update(self._generate_third_party_settings())
|
|
79
79
|
settings.update(self._generate_api_settings())
|
|
80
|
-
settings.update(self.
|
|
80
|
+
settings.update(self._generate_django_rq_settings())
|
|
81
81
|
settings.update(self._generate_grpc_settings())
|
|
82
|
-
settings.update(self._generate_django_q2_settings())
|
|
83
82
|
settings.update(self._generate_tailwind_settings())
|
|
84
83
|
|
|
85
84
|
# Apply additional settings (user overrides)
|
|
@@ -216,14 +215,17 @@ class SettingsOrchestrator:
|
|
|
216
215
|
except Exception as e:
|
|
217
216
|
raise ConfigurationError(f"Failed to generate API settings: {e}") from e
|
|
218
217
|
|
|
219
|
-
def
|
|
220
|
-
"""Generate
|
|
218
|
+
def _generate_django_rq_settings(self) -> Dict[str, Any]:
|
|
219
|
+
"""Generate Django-RQ task queue and scheduler settings."""
|
|
220
|
+
if not hasattr(self.config, "django_rq") or not self.config.django_rq:
|
|
221
|
+
return {}
|
|
222
|
+
|
|
221
223
|
try:
|
|
222
|
-
from .integration_generators.
|
|
223
|
-
generator =
|
|
224
|
+
from .integration_generators.django_rq import DjangoRQSettingsGenerator
|
|
225
|
+
generator = DjangoRQSettingsGenerator(self.config.django_rq, parent_config=self.config)
|
|
224
226
|
return generator.generate()
|
|
225
227
|
except Exception as e:
|
|
226
|
-
raise ConfigurationError(f"Failed to generate
|
|
228
|
+
raise ConfigurationError(f"Failed to generate Django-RQ settings: {e}") from e
|
|
227
229
|
|
|
228
230
|
def _generate_grpc_settings(self) -> Dict[str, Any]:
|
|
229
231
|
"""Generate gRPC framework settings."""
|
|
@@ -234,18 +236,6 @@ class SettingsOrchestrator:
|
|
|
234
236
|
except Exception as e:
|
|
235
237
|
raise ConfigurationError(f"Failed to generate gRPC settings: {e}") from e
|
|
236
238
|
|
|
237
|
-
def _generate_django_q2_settings(self) -> Dict[str, Any]:
|
|
238
|
-
"""Generate Django-Q2 task scheduling settings."""
|
|
239
|
-
if not hasattr(self.config, "django_q2") or not self.config.django_q2:
|
|
240
|
-
return {}
|
|
241
|
-
|
|
242
|
-
try:
|
|
243
|
-
from .integration_generators.django_q2 import DjangoQ2SettingsGenerator
|
|
244
|
-
generator = DjangoQ2SettingsGenerator(self.config.django_q2, parent_config=self.config)
|
|
245
|
-
return generator.generate()
|
|
246
|
-
except Exception as e:
|
|
247
|
-
raise ConfigurationError(f"Failed to generate Django-Q2 settings: {e}") from e
|
|
248
|
-
|
|
249
239
|
def _generate_tailwind_settings(self) -> Dict[str, Any]:
|
|
250
240
|
"""Generate Tailwind CSS settings."""
|
|
251
241
|
try:
|
|
@@ -416,20 +416,20 @@ class StartupDisplayManager(BaseDisplayManager):
|
|
|
416
416
|
task_table.add_column("Value", style="white")
|
|
417
417
|
|
|
418
418
|
# Show real tasks status
|
|
419
|
-
tasks_enabled = self.config.
|
|
419
|
+
tasks_enabled = self.config.should_enable_rq()
|
|
420
420
|
if tasks_enabled:
|
|
421
421
|
task_table.add_row("Tasks Enabled", "[green]True[/green]")
|
|
422
422
|
else:
|
|
423
423
|
task_table.add_row("Tasks Enabled", "[yellow]False[/yellow]")
|
|
424
424
|
|
|
425
|
-
if hasattr(self.config, '
|
|
426
|
-
|
|
427
|
-
task_table.add_row("
|
|
425
|
+
if hasattr(self.config, 'django_rq') and self.config.django_rq:
|
|
426
|
+
queue_names = ', '.join(self.config.django_rq.get_queue_names())
|
|
427
|
+
task_table.add_row("Queues", f"[yellow]{queue_names}[/yellow]")
|
|
428
428
|
else:
|
|
429
|
-
task_table.add_row("
|
|
429
|
+
task_table.add_row("Queues", "[yellow]default[/yellow]")
|
|
430
430
|
|
|
431
431
|
# Add worker command
|
|
432
|
-
task_table.add_row("Start Workers", "[bright_blue]poetry run python manage.py
|
|
432
|
+
task_table.add_row("Start Workers", "[bright_blue]poetry run python manage.py rqworker default[/bright_blue]")
|
|
433
433
|
|
|
434
434
|
task_panel = self.create_full_width_panel(
|
|
435
435
|
task_table,
|
|
@@ -465,13 +465,6 @@ class StartupDisplayManager(BaseDisplayManager):
|
|
|
465
465
|
payments_count = 0
|
|
466
466
|
|
|
467
467
|
config = self.config
|
|
468
|
-
if config and config.should_enable_tasks():
|
|
469
|
-
try:
|
|
470
|
-
from django_cfg.modules.django_tasks import extend_constance_config_with_tasks
|
|
471
|
-
tasks_fields = extend_constance_config_with_tasks()
|
|
472
|
-
tasks_count = len(tasks_fields)
|
|
473
|
-
except:
|
|
474
|
-
pass
|
|
475
468
|
|
|
476
469
|
if config and config.enable_knowbase:
|
|
477
470
|
try:
|
|
@@ -623,13 +616,6 @@ class StartupDisplayManager(BaseDisplayManager):
|
|
|
623
616
|
|
|
624
617
|
# Try to get individual app field counts
|
|
625
618
|
config = self.config
|
|
626
|
-
if config and config.should_enable_tasks():
|
|
627
|
-
try:
|
|
628
|
-
from django_cfg.modules.django_tasks import extend_constance_config_with_tasks
|
|
629
|
-
tasks_fields = extend_constance_config_with_tasks()
|
|
630
|
-
tasks_count = len(tasks_fields)
|
|
631
|
-
except:
|
|
632
|
-
pass
|
|
633
619
|
|
|
634
620
|
if config and config.enable_knowbase:
|
|
635
621
|
try:
|
django_cfg/mixins/__init__.py
CHANGED
|
@@ -5,8 +5,10 @@ Shared mixins for DRF views and viewsets.
|
|
|
5
5
|
"""
|
|
6
6
|
from .admin_api import AdminAPIMixin
|
|
7
7
|
from .client_api import ClientAPIMixin
|
|
8
|
+
from .superadmin_api import SuperAdminAPIMixin
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
10
11
|
"AdminAPIMixin",
|
|
11
12
|
"ClientAPIMixin",
|
|
13
|
+
"SuperAdminAPIMixin",
|
|
12
14
|
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperAdmin API Mixin.
|
|
3
|
+
|
|
4
|
+
Common configuration for superuser-only API endpoints.
|
|
5
|
+
More restrictive than AdminAPIMixin - requires is_superuser flag.
|
|
6
|
+
"""
|
|
7
|
+
from rest_framework.authentication import BasicAuthentication, SessionAuthentication
|
|
8
|
+
from rest_framework.permissions import BasePermission
|
|
9
|
+
from rest_framework_simplejwt.authentication import JWTAuthentication
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IsSuperUser(BasePermission):
|
|
13
|
+
"""
|
|
14
|
+
Permission that allows access only to superusers.
|
|
15
|
+
|
|
16
|
+
More restrictive than IsAdminUser - requires is_superuser flag.
|
|
17
|
+
Use for sensitive operations like command execution.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def has_permission(self, request, view):
|
|
21
|
+
"""Check if user is authenticated and is a superuser."""
|
|
22
|
+
return bool(
|
|
23
|
+
request.user and
|
|
24
|
+
request.user.is_authenticated and
|
|
25
|
+
request.user.is_superuser
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SuperAdminAPIMixin:
|
|
30
|
+
"""
|
|
31
|
+
Mixin for superuser-only API endpoints.
|
|
32
|
+
|
|
33
|
+
Provides:
|
|
34
|
+
- JWT, Session, and Basic authentication
|
|
35
|
+
- IsSuperUser permission requirement (is_superuser=True)
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
class MyViewSet(SuperAdminAPIMixin, viewsets.ModelViewSet):
|
|
39
|
+
queryset = MyModel.objects.all()
|
|
40
|
+
serializer_class = MySerializer
|
|
41
|
+
|
|
42
|
+
Authentication Methods:
|
|
43
|
+
1. JWT Token (Bearer): For frontend SPA authentication
|
|
44
|
+
2. Session: For Django admin integration
|
|
45
|
+
3. Basic Auth: For testing and scripts
|
|
46
|
+
|
|
47
|
+
All endpoints require superuser privileges.
|
|
48
|
+
Only use this for sensitive operations like:
|
|
49
|
+
- Command execution
|
|
50
|
+
- System configuration changes
|
|
51
|
+
- Direct database operations
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
authentication_classes = [
|
|
55
|
+
JWTAuthentication, # JWT tokens (Bearer)
|
|
56
|
+
SessionAuthentication, # Django session (for admin)
|
|
57
|
+
BasicAuthentication, # HTTP Basic (for testing)
|
|
58
|
+
]
|
|
59
|
+
permission_classes = [IsSuperUser]
|
django_cfg/models/__init__.py
CHANGED
|
@@ -35,7 +35,7 @@ from .base.module import BaseCfgAutoModule
|
|
|
35
35
|
from .django.axes import AxesConfig
|
|
36
36
|
from .django.constance import ConstanceConfig, ConstanceField
|
|
37
37
|
from .django.crypto_fields import CryptoFieldsConfig
|
|
38
|
-
from .django.
|
|
38
|
+
from .django.django_rq import DjangoRQConfig, RQQueueConfig
|
|
39
39
|
from .django.environment import EnvironmentConfig
|
|
40
40
|
from .django.openapi import OpenAPIClientConfig
|
|
41
41
|
from .infrastructure.cache import CacheConfig
|
|
@@ -80,8 +80,8 @@ __all__ = [
|
|
|
80
80
|
"EnvironmentConfig",
|
|
81
81
|
"ConstanceConfig",
|
|
82
82
|
"ConstanceField",
|
|
83
|
-
"
|
|
84
|
-
"
|
|
83
|
+
"DjangoRQConfig",
|
|
84
|
+
"RQQueueConfig",
|
|
85
85
|
"OpenAPIClientConfig",
|
|
86
86
|
"UnfoldConfig",
|
|
87
87
|
"AxesConfig",
|
|
@@ -6,7 +6,7 @@ Django integrations and extensions.
|
|
|
6
6
|
|
|
7
7
|
from .axes import AxesConfig
|
|
8
8
|
from .constance import ConstanceConfig, ConstanceField
|
|
9
|
-
from .
|
|
9
|
+
from .django_rq import DjangoRQConfig, RQQueueConfig
|
|
10
10
|
from .environment import EnvironmentConfig
|
|
11
11
|
from .openapi import OpenAPIClientConfig
|
|
12
12
|
|
|
@@ -14,8 +14,8 @@ __all__ = [
|
|
|
14
14
|
"EnvironmentConfig",
|
|
15
15
|
"ConstanceConfig",
|
|
16
16
|
"ConstanceField",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"DjangoRQConfig",
|
|
18
|
+
"RQQueueConfig",
|
|
19
19
|
"OpenAPIClientConfig",
|
|
20
20
|
"AxesConfig",
|
|
21
21
|
]
|