django-cfg 1.4.120__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 +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- 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/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
- django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
- django_cfg/apps/grpc/apps.py +28 -0
- django_cfg/apps/grpc/auth/__init__.py +9 -0
- django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
- django_cfg/apps/grpc/interceptors/__init__.py +19 -0
- django_cfg/apps/grpc/interceptors/errors.py +241 -0
- django_cfg/apps/grpc/interceptors/logging.py +270 -0
- django_cfg/apps/grpc/interceptors/metrics.py +306 -0
- django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
- django_cfg/apps/grpc/management/__init__.py +1 -0
- django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
- django_cfg/apps/grpc/managers/__init__.py +10 -0
- django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
- django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
- django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
- django_cfg/apps/grpc/models/__init__.py +9 -0
- django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
- django_cfg/apps/grpc/serializers/__init__.py +23 -0
- django_cfg/apps/grpc/serializers/health.py +18 -0
- django_cfg/apps/grpc/serializers/requests.py +18 -0
- django_cfg/apps/grpc/serializers/services.py +50 -0
- django_cfg/apps/grpc/serializers/stats.py +22 -0
- django_cfg/apps/grpc/services/__init__.py +16 -0
- django_cfg/apps/grpc/services/base.py +375 -0
- django_cfg/apps/grpc/services/discovery.py +415 -0
- django_cfg/apps/grpc/urls.py +23 -0
- django_cfg/apps/grpc/utils/__init__.py +13 -0
- django_cfg/apps/grpc/utils/proto_gen.py +423 -0
- django_cfg/apps/grpc/views/__init__.py +9 -0
- django_cfg/apps/grpc/views/monitoring.py +497 -0
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/payments/admin/balance_admin.py +26 -36
- django_cfg/apps/payments/admin/payment_admin.py +65 -85
- django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
- 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 +13 -8
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +16 -26
- django_cfg/core/builders/apps_builder.py +7 -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/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +15 -15
- 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/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- 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 +19 -6
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/__init__.py +41 -3
- django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
- django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
- django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
- django_cfg/modules/django_admin/utils/html/badges.py +47 -0
- django_cfg/modules/django_admin/utils/html/base.py +167 -0
- django_cfg/modules/django_admin/utils/html/code.py +87 -0
- django_cfg/modules/django_admin/utils/html/composition.py +205 -0
- django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
- django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
- django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
- django_cfg/modules/django_admin/utils/html/progress.py +127 -0
- django_cfg/modules/django_admin/utils/html_builder.py +55 -408
- django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
- django_cfg/modules/django_unfold/navigation.py +21 -18
- django_cfg/pyproject.toml +4 -6
- django_cfg/registry/core.py +4 -7
- django_cfg/registry/modules.py +6 -0
- 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.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
- 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/task_log.py +0 -265
- 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/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_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- 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/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
- /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
- /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
- /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.120.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,18 @@ 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
|
+
if base_module.is_grpc_enabled():
|
|
57
|
+
enabled_apps.append("django_cfg.apps.grpc")
|
|
58
|
+
|
|
56
59
|
return enabled_apps
|
|
57
60
|
|
|
58
61
|
|
|
@@ -84,7 +87,7 @@ def get_default_cfg_group():
|
|
|
84
87
|
name="cfg",
|
|
85
88
|
apps=get_enabled_cfg_apps(),
|
|
86
89
|
title="Django-CFG API",
|
|
87
|
-
description="Authentication (OTP), Support, Newsletter, Leads, Knowledge Base, AI Agents, Tasks, Payments, Dashboard",
|
|
90
|
+
description="Authentication (OTP), Support, Newsletter, Leads, Knowledge Base, AI Agents, Tasks, Payments, Centrifugo, gRPC, Dashboard",
|
|
88
91
|
version="1.0.0",
|
|
89
92
|
)
|
|
90
93
|
|
|
@@ -173,16 +176,18 @@ APP_URL_MAP = {
|
|
|
173
176
|
"django_cfg.apps.agents": [
|
|
174
177
|
("cfg/agents/", "django_cfg.apps.agents.urls"),
|
|
175
178
|
],
|
|
176
|
-
"django_cfg.apps.tasks": [
|
|
177
|
-
("cfg/tasks/", "django_cfg.apps.tasks.urls"),
|
|
178
|
-
],
|
|
179
179
|
"django_cfg.apps.payments": [
|
|
180
180
|
("cfg/payments/", "django_cfg.apps.payments.urls"),
|
|
181
|
-
# Payments v2.0: No separate urls_admin (uses Django Admin only)
|
|
182
181
|
],
|
|
183
182
|
"django_cfg.apps.centrifugo": [
|
|
184
183
|
("cfg/centrifugo/", "django_cfg.apps.centrifugo.urls"),
|
|
185
184
|
],
|
|
185
|
+
"django_cfg.apps.rq": [
|
|
186
|
+
("cfg/rq/", "django_cfg.apps.rq.urls"),
|
|
187
|
+
],
|
|
188
|
+
"django_cfg.apps.grpc": [
|
|
189
|
+
("cfg/grpc/", "django_cfg.apps.grpc.urls"),
|
|
190
|
+
],
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
# Register URLs for enabled apps only
|
django_cfg/config.py
CHANGED
|
@@ -45,3 +45,109 @@ def get_default_dropdown_items() -> List[SiteDropdownItem]:
|
|
|
45
45
|
link=LIB_SITE_URL,
|
|
46
46
|
),
|
|
47
47
|
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ==============================================================================
|
|
51
|
+
# Feature Detection System
|
|
52
|
+
# ==============================================================================
|
|
53
|
+
|
|
54
|
+
import logging
|
|
55
|
+
from typing import Dict, Callable
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
# Feature registry
|
|
60
|
+
FEATURES: Dict[str, Callable[[], bool]] = {}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def register_feature(name: str, check_func: Callable[[], bool]) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Register a feature check function.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: Feature name (e.g., 'grpc', 'graphql')
|
|
69
|
+
check_func: Function that returns True if feature dependencies are installed
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
>>> def check_grpc():
|
|
73
|
+
... try:
|
|
74
|
+
... import grpcio
|
|
75
|
+
... return True
|
|
76
|
+
... except ImportError:
|
|
77
|
+
... return False
|
|
78
|
+
>>> register_feature('grpc', check_grpc)
|
|
79
|
+
"""
|
|
80
|
+
FEATURES[name] = check_func
|
|
81
|
+
logger.debug(f"Registered feature: {name}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def is_feature_available(feature: str) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Check if optional feature is available.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
feature: Feature name (e.g., 'grpc', 'graphql')
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if feature dependencies are installed, False otherwise
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> if is_feature_available('grpc'):
|
|
96
|
+
... from django_cfg.models.api.grpc import GRPCConfig
|
|
97
|
+
"""
|
|
98
|
+
check_func = FEATURES.get(feature)
|
|
99
|
+
if not check_func:
|
|
100
|
+
logger.warning(f"Unknown feature: {feature}")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
result = check_func()
|
|
105
|
+
logger.debug(f"Feature '{feature}' available: {result}")
|
|
106
|
+
return result
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.debug(f"Feature '{feature}' not available: {e}")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def require_feature(feature: str, error_message: str = None) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Require a feature or raise ImportError.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
feature: Feature name
|
|
118
|
+
error_message: Custom error message
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ImportError: If feature not available
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> require_feature('grpc') # Raises if not installed
|
|
125
|
+
>>> from django_cfg.models.api.grpc import GRPCConfig # Safe to import
|
|
126
|
+
"""
|
|
127
|
+
if not is_feature_available(feature):
|
|
128
|
+
if error_message is None:
|
|
129
|
+
error_message = (
|
|
130
|
+
f"Feature '{feature}' requires additional dependencies. "
|
|
131
|
+
f"Install with: pip install django-cfg[{feature}]"
|
|
132
|
+
)
|
|
133
|
+
raise ImportError(error_message)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ==============================================================================
|
|
137
|
+
# Built-in Feature Checks
|
|
138
|
+
# ==============================================================================
|
|
139
|
+
|
|
140
|
+
def _check_grpc_available() -> bool:
|
|
141
|
+
"""Check if gRPC dependencies are installed."""
|
|
142
|
+
try:
|
|
143
|
+
import grpc as _grpc # noqa: F401
|
|
144
|
+
import grpc_tools as _grpc_tools # noqa: F401
|
|
145
|
+
import django_grpc_framework as _dgf # noqa: F401
|
|
146
|
+
import google.protobuf as _protobuf # noqa: F401
|
|
147
|
+
return True
|
|
148
|
+
except ImportError:
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Register built-in features
|
|
153
|
+
register_feature('grpc', _check_grpc_available)
|
|
@@ -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,
|
|
@@ -30,9 +30,9 @@ from ...models import (
|
|
|
30
30
|
TelegramConfig,
|
|
31
31
|
UnfoldConfig,
|
|
32
32
|
)
|
|
33
|
+
from ...models.api.grpc import GRPCConfig
|
|
33
34
|
from ...models.ngrok import NgrokConfig
|
|
34
35
|
from ...models.payments import PaymentsConfig
|
|
35
|
-
from ...models.tasks import TaskConfig
|
|
36
36
|
from ...modules.nextjs_admin import NextJsAdminConfig
|
|
37
37
|
from ..exceptions import ConfigurationError
|
|
38
38
|
from ..types.enums import EnvironmentMode, StartupInfoMode
|
|
@@ -223,7 +223,7 @@ class DjangoConfig(BaseModel):
|
|
|
223
223
|
description=(
|
|
224
224
|
"Redis connection URL (redis://host:port/db). "
|
|
225
225
|
"If set and cache_default is None, automatically creates RedisCache backend. "
|
|
226
|
-
"Also used by
|
|
226
|
+
"Also used by Django-RQ if queues don't specify connection details."
|
|
227
227
|
),
|
|
228
228
|
)
|
|
229
229
|
|
|
@@ -323,16 +323,10 @@ class DjangoConfig(BaseModel):
|
|
|
323
323
|
description="Enable modern Tailwind CSS theme for Django REST Framework Browsable API",
|
|
324
324
|
)
|
|
325
325
|
|
|
326
|
-
# ===
|
|
327
|
-
|
|
326
|
+
# === Django-RQ Task Queue & Scheduler ===
|
|
327
|
+
django_rq: Optional[DjangoRQConfig] = Field(
|
|
328
328
|
default=None,
|
|
329
|
-
description="
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
# === Django-Q2 Task Scheduling ===
|
|
333
|
-
django_q2: Optional[DjangoQ2Config] = Field(
|
|
334
|
-
default=None,
|
|
335
|
-
description="Django-Q2 task scheduling and queue configuration",
|
|
329
|
+
description="Django-RQ task queue and scheduler configuration (sync tasks, cron scheduling)",
|
|
336
330
|
)
|
|
337
331
|
|
|
338
332
|
# === Centrifugo Configuration ===
|
|
@@ -352,6 +346,11 @@ class DjangoConfig(BaseModel):
|
|
|
352
346
|
description="Extended DRF Spectacular configuration (supplements OpenAPI Client)",
|
|
353
347
|
)
|
|
354
348
|
|
|
349
|
+
grpc: Optional[GRPCConfig] = Field(
|
|
350
|
+
default=None,
|
|
351
|
+
description="gRPC framework configuration (server, authentication, proto generation)",
|
|
352
|
+
)
|
|
353
|
+
|
|
355
354
|
# === Limits Configuration ===
|
|
356
355
|
limits: Optional[LimitsConfig] = Field(
|
|
357
356
|
default=None,
|
|
@@ -635,28 +634,19 @@ class DjangoConfig(BaseModel):
|
|
|
635
634
|
**kwargs
|
|
636
635
|
)
|
|
637
636
|
|
|
638
|
-
def
|
|
637
|
+
def should_enable_rq(self) -> bool:
|
|
639
638
|
"""
|
|
640
|
-
Determine if
|
|
639
|
+
Determine if Django-RQ should be enabled.
|
|
641
640
|
|
|
642
|
-
|
|
643
|
-
1. Explicitly configured via tasks field
|
|
644
|
-
2. Knowledge base is enabled (requires background processing)
|
|
645
|
-
3. Agents are enabled (requires background processing)
|
|
641
|
+
Django-RQ is enabled if explicitly configured via django_rq field.
|
|
646
642
|
|
|
647
643
|
Returns:
|
|
648
|
-
True if
|
|
644
|
+
True if Django-RQ should be enabled, False otherwise
|
|
649
645
|
"""
|
|
650
|
-
|
|
651
|
-
if hasattr(self, 'tasks') and self.tasks and self.tasks.enabled:
|
|
652
|
-
return True
|
|
653
|
-
|
|
654
|
-
# Check if features that require tasks are enabled
|
|
655
|
-
if self.enable_knowbase or self.enable_agents:
|
|
646
|
+
if hasattr(self, 'django_rq') and self.django_rq and self.django_rq.enabled:
|
|
656
647
|
return True
|
|
657
648
|
|
|
658
649
|
return False
|
|
659
650
|
|
|
660
|
-
|
|
661
651
|
# Export main class
|
|
662
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
|
|
@@ -140,6 +139,9 @@ class InstalledAppsBuilder:
|
|
|
140
139
|
if self.config.centrifugo and self.config.centrifugo.enabled:
|
|
141
140
|
apps.append("django_cfg.apps.centrifugo")
|
|
142
141
|
|
|
142
|
+
if self.config.grpc and self.config.grpc.enabled:
|
|
143
|
+
apps.append("django_cfg.apps.grpc")
|
|
144
|
+
|
|
143
145
|
if self.config.crypto_fields and self.config.crypto_fields.enabled:
|
|
144
146
|
apps.append("django_crypto_fields.apps.AppConfig")
|
|
145
147
|
|
|
@@ -158,16 +160,10 @@ class InstalledAppsBuilder:
|
|
|
158
160
|
"""
|
|
159
161
|
apps = []
|
|
160
162
|
|
|
161
|
-
#
|
|
162
|
-
if self.config.
|
|
163
|
-
#
|
|
164
|
-
apps.append("django_cfg.apps.
|
|
165
|
-
|
|
166
|
-
# Add django-q2 if enabled
|
|
167
|
-
if hasattr(self.config, "django_q2") and self.config.django_q2 and self.config.django_q2.enabled:
|
|
168
|
-
apps.append("django_q")
|
|
169
|
-
# Auto-add django_q2 module for automatic schedule synchronization
|
|
170
|
-
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
|
|
171
167
|
|
|
172
168
|
# Add DRF Tailwind theme module (uses Tailwind via CDN)
|
|
173
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']
|