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.

Files changed (182) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +0 -12
  5. django_cfg/apps/dashboard/serializers/activity.py +1 -1
  6. django_cfg/apps/dashboard/services/__init__.py +0 -2
  7. django_cfg/apps/dashboard/services/charts_service.py +4 -3
  8. django_cfg/apps/dashboard/services/statistics_service.py +11 -2
  9. django_cfg/apps/dashboard/services/system_health_service.py +64 -106
  10. django_cfg/apps/dashboard/urls.py +0 -2
  11. django_cfg/apps/dashboard/views/__init__.py +0 -2
  12. django_cfg/apps/dashboard/views/commands_views.py +3 -6
  13. django_cfg/apps/dashboard/views/overview_views.py +14 -13
  14. django_cfg/apps/grpc/__init__.py +9 -0
  15. django_cfg/apps/grpc/admin/__init__.py +11 -0
  16. django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
  17. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  18. django_cfg/apps/grpc/apps.py +28 -0
  19. django_cfg/apps/grpc/auth/__init__.py +9 -0
  20. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  21. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  22. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  23. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  24. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  25. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  26. django_cfg/apps/grpc/management/__init__.py +1 -0
  27. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  28. django_cfg/apps/grpc/managers/__init__.py +10 -0
  29. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  30. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  31. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  32. django_cfg/apps/grpc/models/__init__.py +9 -0
  33. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  34. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  35. django_cfg/apps/grpc/serializers/health.py +18 -0
  36. django_cfg/apps/grpc/serializers/requests.py +18 -0
  37. django_cfg/apps/grpc/serializers/services.py +50 -0
  38. django_cfg/apps/grpc/serializers/stats.py +22 -0
  39. django_cfg/apps/grpc/services/__init__.py +16 -0
  40. django_cfg/apps/grpc/services/base.py +375 -0
  41. django_cfg/apps/grpc/services/discovery.py +415 -0
  42. django_cfg/apps/grpc/urls.py +23 -0
  43. django_cfg/apps/grpc/utils/__init__.py +13 -0
  44. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  45. django_cfg/apps/grpc/views/__init__.py +9 -0
  46. django_cfg/apps/grpc/views/monitoring.py +497 -0
  47. django_cfg/apps/knowbase/apps.py +2 -2
  48. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
  49. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  50. django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
  51. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  52. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  53. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  54. django_cfg/apps/rq/__init__.py +9 -0
  55. django_cfg/apps/rq/apps.py +80 -0
  56. django_cfg/apps/rq/management/__init__.py +1 -0
  57. django_cfg/apps/rq/management/commands/__init__.py +1 -0
  58. django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
  59. django_cfg/apps/rq/management/commands/rqstats.py +33 -0
  60. django_cfg/apps/rq/management/commands/rqworker.py +31 -0
  61. django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
  62. django_cfg/apps/rq/serializers/__init__.py +40 -0
  63. django_cfg/apps/rq/serializers/health.py +60 -0
  64. django_cfg/apps/rq/serializers/job.py +100 -0
  65. django_cfg/apps/rq/serializers/queue.py +80 -0
  66. django_cfg/apps/rq/serializers/schedule.py +178 -0
  67. django_cfg/apps/rq/serializers/testing.py +139 -0
  68. django_cfg/apps/rq/serializers/worker.py +58 -0
  69. django_cfg/apps/rq/services/__init__.py +25 -0
  70. django_cfg/apps/rq/services/config_helper.py +233 -0
  71. django_cfg/apps/rq/services/models/README.md +417 -0
  72. django_cfg/apps/rq/services/models/__init__.py +30 -0
  73. django_cfg/apps/rq/services/models/event.py +123 -0
  74. django_cfg/apps/rq/services/models/job.py +99 -0
  75. django_cfg/apps/rq/services/models/queue.py +92 -0
  76. django_cfg/apps/rq/services/models/worker.py +104 -0
  77. django_cfg/apps/rq/services/rq_converters.py +183 -0
  78. django_cfg/apps/rq/tasks/__init__.py +23 -0
  79. django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
  80. django_cfg/apps/rq/urls.py +54 -0
  81. django_cfg/apps/rq/views/__init__.py +19 -0
  82. django_cfg/apps/rq/views/jobs.py +882 -0
  83. django_cfg/apps/rq/views/monitoring.py +248 -0
  84. django_cfg/apps/rq/views/queues.py +261 -0
  85. django_cfg/apps/rq/views/schedule.py +400 -0
  86. django_cfg/apps/rq/views/testing.py +761 -0
  87. django_cfg/apps/rq/views/workers.py +195 -0
  88. django_cfg/apps/urls.py +13 -8
  89. django_cfg/config.py +106 -0
  90. django_cfg/core/base/config_model.py +16 -26
  91. django_cfg/core/builders/apps_builder.py +7 -11
  92. django_cfg/core/generation/integration_generators/__init__.py +3 -6
  93. django_cfg/core/generation/integration_generators/django_rq.py +80 -0
  94. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  95. django_cfg/core/generation/orchestrator.py +15 -15
  96. django_cfg/core/integration/display/startup.py +6 -20
  97. django_cfg/mixins/__init__.py +2 -0
  98. django_cfg/mixins/superadmin_api.py +59 -0
  99. django_cfg/models/__init__.py +3 -3
  100. django_cfg/models/api/grpc/__init__.py +59 -0
  101. django_cfg/models/api/grpc/config.py +364 -0
  102. django_cfg/models/django/__init__.py +3 -3
  103. django_cfg/models/django/django_rq.py +621 -0
  104. django_cfg/models/django/revolution_legacy.py +1 -1
  105. django_cfg/modules/base.py +19 -6
  106. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  107. django_cfg/modules/django_admin/config/background_task_config.py +4 -4
  108. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  109. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  110. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  111. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  112. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  113. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  114. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  115. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  116. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  117. django_cfg/modules/django_admin/utils/html/composition.py +205 -0
  118. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  119. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  120. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  121. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  122. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  123. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  124. django_cfg/modules/django_unfold/navigation.py +21 -18
  125. django_cfg/pyproject.toml +4 -6
  126. django_cfg/registry/core.py +4 -7
  127. django_cfg/registry/modules.py +6 -0
  128. django_cfg/static/frontend/admin.zip +0 -0
  129. django_cfg/templates/admin/constance/includes/results_list.html +73 -0
  130. django_cfg/templates/admin/index.html +187 -62
  131. django_cfg/templatetags/django_cfg.py +61 -1
  132. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
  133. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
  134. django_cfg/apps/dashboard/permissions.py +0 -48
  135. django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
  136. django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
  137. django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
  138. django_cfg/apps/tasks/__init__.py +0 -64
  139. django_cfg/apps/tasks/admin/__init__.py +0 -4
  140. django_cfg/apps/tasks/admin/task_log.py +0 -265
  141. django_cfg/apps/tasks/apps.py +0 -15
  142. django_cfg/apps/tasks/filters/__init__.py +0 -10
  143. django_cfg/apps/tasks/filters/task_log.py +0 -121
  144. django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
  145. django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
  146. django_cfg/apps/tasks/models/__init__.py +0 -4
  147. django_cfg/apps/tasks/models/task_log.py +0 -246
  148. django_cfg/apps/tasks/serializers/__init__.py +0 -28
  149. django_cfg/apps/tasks/serializers/task_log.py +0 -249
  150. django_cfg/apps/tasks/services/__init__.py +0 -10
  151. django_cfg/apps/tasks/services/client/__init__.py +0 -7
  152. django_cfg/apps/tasks/services/client/client.py +0 -234
  153. django_cfg/apps/tasks/services/config_helper.py +0 -63
  154. django_cfg/apps/tasks/services/sync.py +0 -204
  155. django_cfg/apps/tasks/urls.py +0 -16
  156. django_cfg/apps/tasks/views/__init__.py +0 -10
  157. django_cfg/apps/tasks/views/task_log.py +0 -41
  158. django_cfg/apps/tasks/views/task_log_base.py +0 -41
  159. django_cfg/apps/tasks/views/task_log_overview.py +0 -100
  160. django_cfg/apps/tasks/views/task_log_related.py +0 -41
  161. django_cfg/apps/tasks/views/task_log_stats.py +0 -91
  162. django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
  163. django_cfg/core/generation/integration_generators/django_q2.py +0 -133
  164. django_cfg/core/generation/integration_generators/tasks.py +0 -88
  165. django_cfg/models/django/django_q2.py +0 -514
  166. django_cfg/models/tasks/__init__.py +0 -49
  167. django_cfg/models/tasks/backends.py +0 -122
  168. django_cfg/models/tasks/config.py +0 -209
  169. django_cfg/models/tasks/utils.py +0 -162
  170. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  171. django_cfg/modules/django_q2/README.md +0 -140
  172. django_cfg/modules/django_q2/__init__.py +0 -8
  173. django_cfg/modules/django_q2/apps.py +0 -107
  174. django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
  176. /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
  177. /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
  178. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  179. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  180. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
  181. {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
  182. {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 tasks (ReArq, Django-Q2) if they don't specify broker_url."
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
- # === Background Task Processing ===
327
- tasks: Optional[TaskConfig] = Field(
326
+ # === Django-RQ Task Queue & Scheduler ===
327
+ django_rq: Optional[DjangoRQConfig] = Field(
328
328
  default=None,
329
- description="Background task processing configuration (ReArq)",
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 should_enable_tasks(self) -> bool:
637
+ def should_enable_rq(self) -> bool:
639
638
  """
640
- Determine if background tasks should be enabled.
639
+ Determine if Django-RQ should be enabled.
641
640
 
642
- Tasks are enabled if:
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 tasks should be enabled, False otherwise
644
+ True if Django-RQ should be enabled, False otherwise
649
645
  """
650
- # Check if explicitly configured
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
- # Auto-enable tasks if needed by other features
162
- if self.config.should_enable_tasks():
163
- # No external app needed - ReArq is embedded
164
- apps.append("django_cfg.apps.tasks")
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
- - Background tasks (ReArq)
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 .django_q2 import DjangoQ2SettingsGenerator
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
- "TasksSettingsGenerator",
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']