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,621 @@
1
+ """
2
+ Django-RQ Configuration for django-cfg.
3
+
4
+ Type-safe configuration for django-rq with automatic Django settings generation
5
+ and support for scheduled tasks via rq-scheduler.
6
+
7
+ Django-RQ is a Redis-based task queue: https://github.com/rq/django-rq
8
+
9
+ Features:
10
+ - Type-safe queue and scheduler configuration
11
+ - Redis connection management (standard, Sentinel, SSL)
12
+ - Job timeout and TTL configuration
13
+ - Built-in Prometheus metrics support
14
+ - Exception handler configuration
15
+ - Admin interface with monitoring
16
+ - RQ Scheduler for cron-like scheduling
17
+ - High performance (10,000+ jobs/sec)
18
+
19
+ Example:
20
+ ```python
21
+ from django_cfg.models.django.django_rq import DjangoRQConfig, RQQueueConfig
22
+
23
+ django_rq_config = DjangoRQConfig(
24
+ enabled=True,
25
+ queues=[
26
+ RQQueueConfig(
27
+ queue="default",
28
+ host="localhost",
29
+ port=6379,
30
+ db=0,
31
+ default_timeout=360,
32
+ ),
33
+ RQQueueConfig(
34
+ queue="high",
35
+ host="localhost",
36
+ port=6379,
37
+ db=0,
38
+ default_timeout=180,
39
+ ),
40
+ ],
41
+ show_admin_link=True,
42
+ prometheus_enabled=True,
43
+ )
44
+ ```
45
+
46
+ Scheduler Support:
47
+ Use rq-scheduler for cron-like scheduled tasks:
48
+
49
+ ```bash
50
+ pip install rq-scheduler
51
+ python manage.py rqscheduler
52
+ ```
53
+
54
+ ```python
55
+ import django_rq
56
+ scheduler = django_rq.get_scheduler('default')
57
+
58
+ # Schedule job for specific time
59
+ from datetime import datetime
60
+ scheduler.enqueue_at(datetime(2025, 12, 31, 23, 59), my_task)
61
+
62
+ # Schedule job with interval
63
+ scheduler.schedule(
64
+ scheduled_time=datetime.utcnow(),
65
+ func=my_task,
66
+ interval=60, # Every 60 seconds
67
+ repeat=None, # Repeat forever
68
+ )
69
+
70
+ # Cron-style scheduling
71
+ scheduler.cron(
72
+ "0 0 * * *", # Every day at midnight
73
+ func=my_task,
74
+ queue_name='default'
75
+ )
76
+ ```
77
+ """
78
+
79
+ from typing import Any, Dict, List, Optional
80
+
81
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
82
+
83
+
84
+ class RQQueueConfig(BaseModel):
85
+ """
86
+ Configuration for a single RQ queue.
87
+
88
+ Supports standard Redis, Redis Sentinel, and SSL connections.
89
+ """
90
+
91
+ model_config = ConfigDict(
92
+ validate_assignment=True,
93
+ extra="forbid",
94
+ )
95
+
96
+ # Queue name
97
+ queue: str = Field(
98
+ ...,
99
+ min_length=1,
100
+ max_length=100,
101
+ pattern=r'^[a-zA-Z0-9_-]+$',
102
+ description="Queue name (alphanumeric, hyphens, underscores)",
103
+ )
104
+
105
+ # Redis URL (alternative to host/port/db)
106
+ url: Optional[str] = Field(
107
+ default=None,
108
+ description="Redis URL (redis://localhost:6379/0). If provided, overrides host/port/db.",
109
+ )
110
+
111
+ # Standard Redis connection
112
+ host: str = Field(
113
+ default="localhost",
114
+ description="Redis host",
115
+ )
116
+
117
+ port: int = Field(
118
+ default=6379,
119
+ ge=1,
120
+ le=65535,
121
+ description="Redis port",
122
+ )
123
+
124
+ db: int = Field(
125
+ default=0,
126
+ ge=0,
127
+ le=15,
128
+ description="Redis database number (0-15)",
129
+ )
130
+
131
+ username: Optional[str] = Field(
132
+ default=None,
133
+ description="Redis username (Redis 6+)",
134
+ )
135
+
136
+ password: Optional[str] = Field(
137
+ default=None,
138
+ description="Redis password",
139
+ )
140
+
141
+ # Job defaults
142
+ default_timeout: int = Field(
143
+ default=360,
144
+ ge=1,
145
+ description="Default job timeout in seconds",
146
+ )
147
+
148
+ default_result_ttl: int = Field(
149
+ default=800,
150
+ ge=0,
151
+ description="Default result TTL in seconds (0 = no expiry)",
152
+ )
153
+
154
+ # Redis Sentinel support
155
+ sentinels: Optional[List[tuple[str, int]]] = Field(
156
+ default=None,
157
+ description="List of Sentinel (host, port) tuples",
158
+ )
159
+
160
+ master_name: Optional[str] = Field(
161
+ default=None,
162
+ description="Redis Sentinel master name",
163
+ )
164
+
165
+ socket_timeout: Optional[float] = Field(
166
+ default=None,
167
+ ge=0.1,
168
+ description="Redis socket timeout in seconds",
169
+ )
170
+
171
+ # Advanced connection options
172
+ connection_kwargs: Dict[str, Any] = Field(
173
+ default_factory=dict,
174
+ description="Additional Redis connection arguments (e.g., ssl=True)",
175
+ )
176
+
177
+ redis_client_kwargs: Dict[str, Any] = Field(
178
+ default_factory=dict,
179
+ description="Additional Redis client arguments (e.g., ssl_cert_reqs)",
180
+ )
181
+
182
+ sentinel_kwargs: Dict[str, Any] = Field(
183
+ default_factory=dict,
184
+ description="Sentinel-specific connection arguments (username/password for Sentinel auth)",
185
+ )
186
+
187
+ def to_django_rq_format(self, redis_url: Optional[str] = None) -> Dict[str, Any]:
188
+ """
189
+ Convert to Django-RQ queue configuration format.
190
+
191
+ Args:
192
+ redis_url: Redis URL from parent DjangoConfig (if available)
193
+
194
+ Returns:
195
+ Dictionary for RQ_QUEUES[queue_name] in settings.py
196
+ """
197
+ config: Dict[str, Any] = {}
198
+
199
+ # Priority: url field > redis_url from parent > Sentinel > host/port/db
200
+ if self.url:
201
+ config["URL"] = self.url
202
+ elif redis_url:
203
+ # Use redis_url from parent DjangoConfig
204
+ config["URL"] = redis_url
205
+ # Use Sentinel if configured
206
+ elif self.sentinels and self.master_name:
207
+ config["SENTINELS"] = self.sentinels
208
+ config["MASTER_NAME"] = self.master_name
209
+ config["DB"] = self.db
210
+
211
+ if self.socket_timeout:
212
+ config["SOCKET_TIMEOUT"] = self.socket_timeout
213
+
214
+ if self.connection_kwargs:
215
+ config["CONNECTION_KWARGS"] = self.connection_kwargs
216
+
217
+ if self.sentinel_kwargs:
218
+ config["SENTINEL_KWARGS"] = self.sentinel_kwargs
219
+
220
+ else:
221
+ # Standard Redis connection
222
+ config["HOST"] = self.host
223
+ config["PORT"] = self.port
224
+ config["DB"] = self.db
225
+
226
+ if self.redis_client_kwargs:
227
+ config["REDIS_CLIENT_KWARGS"] = self.redis_client_kwargs
228
+
229
+ # Common options
230
+ if self.username:
231
+ config["USERNAME"] = self.username
232
+
233
+ if self.password:
234
+ config["PASSWORD"] = self.password
235
+
236
+ config["DEFAULT_TIMEOUT"] = self.default_timeout
237
+ config["DEFAULT_RESULT_TTL"] = self.default_result_ttl
238
+
239
+ return config
240
+
241
+
242
+ class DjangoRQConfig(BaseModel):
243
+ """
244
+ Complete Django-RQ configuration container.
245
+
246
+ Integrates with django-rq for Redis-based task queuing with high performance.
247
+ Automatically adds django_rq to INSTALLED_APPS when enabled.
248
+
249
+ Installation:
250
+ ```bash
251
+ pip install django-rq rq-scheduler
252
+ ```
253
+
254
+ Running workers:
255
+ ```bash
256
+ # Start worker for default queue
257
+ python manage.py rqworker default
258
+
259
+ # Start worker for multiple queues (priority order)
260
+ python manage.py rqworker high default low
261
+
262
+ # Start scheduler for scheduled tasks
263
+ python manage.py rqscheduler
264
+ ```
265
+
266
+ Admin interface:
267
+ - Visit /django-rq/ to view queues, workers, and jobs
268
+ - Monitor job execution, failures, and performance
269
+ - Manually requeue or delete jobs
270
+ - View worker statistics
271
+
272
+ Prometheus metrics:
273
+ When prometheus_enabled=True, metrics are exposed at /django-rq/metrics/
274
+ - rq_jobs_total{queue, status}
275
+ - rq_job_duration_seconds{queue}
276
+ - rq_workers_total{queue}
277
+ - rq_queue_length{queue}
278
+ """
279
+
280
+ model_config = ConfigDict(
281
+ validate_assignment=True,
282
+ extra="forbid",
283
+ )
284
+
285
+ enabled: bool = Field(
286
+ default=True,
287
+ description="Enable Django-RQ (auto-adds django_rq to INSTALLED_APPS)",
288
+ )
289
+
290
+ queues: List[RQQueueConfig] = Field(
291
+ default_factory=lambda: [
292
+ RQQueueConfig(queue="default"),
293
+ ],
294
+ description="Queue configurations (at least 'default' required)",
295
+ )
296
+
297
+ # Admin interface
298
+ show_admin_link: bool = Field(
299
+ default=True,
300
+ description="Show link to RQ admin in Django admin",
301
+ )
302
+
303
+ # Exception handlers
304
+ exception_handlers: List[str] = Field(
305
+ default_factory=list,
306
+ description="List of exception handler function paths (e.g., 'myapp.handlers.log_exception')",
307
+ )
308
+
309
+ # API access
310
+ api_token: Optional[str] = Field(
311
+ default=None,
312
+ description="API token for statistics endpoint authentication",
313
+ )
314
+
315
+ # Prometheus metrics
316
+ prometheus_enabled: bool = Field(
317
+ default=True,
318
+ description="Enable Prometheus metrics at /django-rq/metrics/",
319
+ )
320
+
321
+ # RQ Scheduler - scheduled jobs configuration
322
+ schedules: List["RQScheduleConfig"] = Field(
323
+ default_factory=list,
324
+ description="Scheduled jobs for rq-scheduler (cron-style, interval, or one-time)",
325
+ )
326
+
327
+ @field_validator("queues")
328
+ @classmethod
329
+ def validate_unique_queue_names(cls, queues: List[RQQueueConfig]) -> List[RQQueueConfig]:
330
+ """Validate that all queue names are unique."""
331
+ queue_names = [q.queue for q in queues]
332
+ if len(queue_names) != len(set(queue_names)):
333
+ duplicates = [name for name in queue_names if queue_names.count(name) > 1]
334
+ raise ValueError(
335
+ f"Duplicate queue names found: {set(duplicates)}. "
336
+ "Each queue must have a unique name."
337
+ )
338
+
339
+ # Ensure 'default' queue exists
340
+ if 'default' not in queue_names:
341
+ raise ValueError(
342
+ "A queue named 'default' is required. "
343
+ "Add RQQueueConfig(queue='default', ...) to the queues list."
344
+ )
345
+
346
+ return queues
347
+
348
+ def to_django_settings(self, parent_config: Optional[Any] = None) -> Dict[str, Any]:
349
+ """
350
+ Convert to Django settings dictionary.
351
+
352
+ Generates RQ_QUEUES and related configuration for Django-RQ.
353
+
354
+ Args:
355
+ parent_config: Optional parent DjangoConfig for accessing redis_url
356
+
357
+ Returns:
358
+ Dictionary with RQ_QUEUES, RQ_SHOW_ADMIN_LINK, etc.
359
+ """
360
+ if not self.enabled:
361
+ return {}
362
+
363
+ settings: Dict[str, Any] = {}
364
+
365
+ # Get redis_url from parent config if available
366
+ redis_url = None
367
+ if parent_config and hasattr(parent_config, 'redis_url'):
368
+ redis_url = parent_config.redis_url
369
+
370
+ # Generate RQ_QUEUES configuration from list
371
+ rq_queues = {}
372
+ for queue_config in self.queues:
373
+ rq_queues[queue_config.queue] = queue_config.to_django_rq_format(redis_url=redis_url)
374
+
375
+ settings["RQ_QUEUES"] = rq_queues
376
+ settings["RQ_SHOW_ADMIN_LINK"] = self.show_admin_link
377
+
378
+ if self.exception_handlers:
379
+ settings["RQ_EXCEPTION_HANDLERS"] = self.exception_handlers
380
+
381
+ if self.api_token:
382
+ settings["RQ_API_TOKEN"] = self.api_token
383
+
384
+ return settings
385
+
386
+ def get_queue_names(self) -> List[str]:
387
+ """Get list of configured queue names."""
388
+ return [q.queue for q in self.queues]
389
+
390
+ def get_queue_config(self, queue_name: str) -> Optional[RQQueueConfig]:
391
+ """Get configuration for specific queue."""
392
+ for queue in self.queues:
393
+ if queue.queue == queue_name:
394
+ return queue
395
+ return None
396
+
397
+ def add_queue(self, config: RQQueueConfig) -> None:
398
+ """
399
+ Add a new queue configuration.
400
+
401
+ Args:
402
+ config: RQQueueConfig instance with queue name set
403
+
404
+ Raises:
405
+ ValueError: If queue with this name already exists
406
+ """
407
+ if config.queue in self.get_queue_names():
408
+ raise ValueError(f"Queue '{config.queue}' already exists")
409
+ self.queues.append(config)
410
+
411
+ def remove_queue(self, queue_name: str) -> bool:
412
+ """
413
+ Remove a queue configuration.
414
+
415
+ Args:
416
+ queue_name: Name of the queue to remove
417
+
418
+ Returns:
419
+ True if queue was removed, False if not found
420
+ """
421
+ for i, queue in enumerate(self.queues):
422
+ if queue.queue == queue_name:
423
+ self.queues.pop(i)
424
+ return True
425
+ return False
426
+
427
+
428
+ class RQScheduleConfig(BaseModel):
429
+ """
430
+ Configuration for RQ Scheduler scheduled job.
431
+
432
+ RQ Scheduler supports:
433
+ - Cron-style scheduling
434
+ - Interval-based scheduling
435
+ - One-time scheduled jobs
436
+ - Declarative task parameters (limit, verbosity, report_type, days, force)
437
+
438
+ Example:
439
+ ```python
440
+ # Cron schedule with declarative parameters
441
+ RQScheduleConfig(
442
+ func="myapp.tasks.update_prices",
443
+ cron="*/5 * * * *", # Every 5 minutes
444
+ queue="default",
445
+ limit=50, # Type-safe field, automatically added to kwargs
446
+ verbosity=0, # Type-safe field, automatically added to kwargs
447
+ description="Update coin prices",
448
+ )
449
+
450
+ # Interval schedule with declarative parameters
451
+ RQScheduleConfig(
452
+ func="myapp.tasks.cleanup",
453
+ interval=3600, # Every hour
454
+ queue="low",
455
+ days=7, # Type-safe field, automatically added to kwargs
456
+ force=True, # Type-safe field, automatically added to kwargs
457
+ )
458
+
459
+ # Traditional way (still works for custom parameters)
460
+ RQScheduleConfig(
461
+ func="myapp.tasks.my_task",
462
+ interval=60,
463
+ kwargs={"custom_param": "value"},
464
+ )
465
+ ```
466
+ """
467
+
468
+ model_config = ConfigDict(
469
+ validate_assignment=True,
470
+ extra="forbid", # Strict validation - use declared fields only
471
+ )
472
+
473
+ func: str = Field(
474
+ ...,
475
+ description="Function path (e.g., 'myapp.tasks.my_task')",
476
+ )
477
+
478
+ # Schedule type (one of: cron, interval, or scheduled_time)
479
+ cron: Optional[str] = Field(
480
+ default=None,
481
+ description="Cron expression (e.g., '0 0 * * *' for daily at midnight)",
482
+ )
483
+
484
+ interval: Optional[int] = Field(
485
+ default=None,
486
+ ge=1,
487
+ description="Interval in seconds for recurring jobs",
488
+ )
489
+
490
+ scheduled_time: Optional[str] = Field(
491
+ default=None,
492
+ description="ISO datetime for one-time scheduled job (e.g., '2025-12-31T23:59:59')",
493
+ )
494
+
495
+ # Job configuration
496
+ queue: str = Field(
497
+ default="default",
498
+ description="Queue name to enqueue job",
499
+ )
500
+
501
+ timeout: Optional[int] = Field(
502
+ default=None,
503
+ ge=1,
504
+ description="Job timeout in seconds (overrides queue default)",
505
+ )
506
+
507
+ result_ttl: Optional[int] = Field(
508
+ default=None,
509
+ ge=0,
510
+ description="Result TTL in seconds (overrides queue default)",
511
+ )
512
+
513
+ # Function arguments
514
+ args: List[Any] = Field(
515
+ default_factory=list,
516
+ description="Positional arguments for function",
517
+ )
518
+
519
+ kwargs: Dict[str, Any] = Field(
520
+ default_factory=dict,
521
+ description="Keyword arguments for function",
522
+ )
523
+
524
+ # Metadata
525
+ job_id: Optional[str] = Field(
526
+ default=None,
527
+ description="Custom job ID (generated if not provided)",
528
+ )
529
+
530
+ description: Optional[str] = Field(
531
+ default=None,
532
+ description="Human-readable description of the job",
533
+ )
534
+
535
+ repeat: Optional[int] = Field(
536
+ default=None,
537
+ ge=1,
538
+ description="Number of times to repeat (None = repeat forever for interval jobs)",
539
+ )
540
+
541
+ # Common task parameters (automatically added to kwargs)
542
+ limit: Optional[int] = Field(
543
+ default=None,
544
+ ge=1,
545
+ description="Limit parameter for task (automatically added to kwargs)",
546
+ )
547
+
548
+ verbosity: Optional[int] = Field(
549
+ default=None,
550
+ ge=0,
551
+ le=3,
552
+ description="Verbosity level 0-3 (automatically added to kwargs)",
553
+ )
554
+
555
+ report_type: Optional[str] = Field(
556
+ default=None,
557
+ description="Report type parameter (automatically added to kwargs)",
558
+ )
559
+
560
+ days: Optional[int] = Field(
561
+ default=None,
562
+ ge=1,
563
+ description="Days parameter for task (automatically added to kwargs)",
564
+ )
565
+
566
+ force: Optional[bool] = Field(
567
+ default=None,
568
+ description="Force parameter for task (automatically added to kwargs)",
569
+ )
570
+
571
+ @field_validator("cron", "interval", "scheduled_time")
572
+ @classmethod
573
+ def validate_schedule_type(cls, v, info):
574
+ """Ensure at least one schedule type is provided."""
575
+ # This validator is called for each field, so we check after all fields are set
576
+ return v
577
+
578
+ @model_validator(mode="after")
579
+ def validate_one_schedule_type(self):
580
+ """Ensure exactly one schedule type is provided and collect task parameters into kwargs."""
581
+ schedule_types = [
582
+ self.cron is not None,
583
+ self.interval is not None,
584
+ self.scheduled_time is not None,
585
+ ]
586
+
587
+ if sum(schedule_types) == 0:
588
+ raise ValueError(
589
+ "At least one schedule type must be provided: cron, interval, or scheduled_time"
590
+ )
591
+
592
+ if sum(schedule_types) > 1:
593
+ raise ValueError(
594
+ "Only one schedule type can be provided: cron, interval, or scheduled_time"
595
+ )
596
+
597
+ # Collect task parameters into kwargs (declarative syntax support)
598
+ task_params = {}
599
+
600
+ # Common task parameters that should go into kwargs
601
+ param_fields = ['limit', 'verbosity', 'report_type', 'days', 'force']
602
+
603
+ for field_name in param_fields:
604
+ field_value = getattr(self, field_name, None)
605
+ if field_value is not None:
606
+ task_params[field_name] = field_value
607
+
608
+ if task_params:
609
+ # Merge task params with existing kwargs
610
+ # Use object.__setattr__ to avoid recursion with validate_assignment=True
611
+ merged_kwargs = {**self.kwargs, **task_params}
612
+ object.__setattr__(self, 'kwargs', merged_kwargs)
613
+
614
+ return self
615
+
616
+
617
+ __all__ = [
618
+ "RQQueueConfig",
619
+ "DjangoRQConfig",
620
+ "RQScheduleConfig",
621
+ ]
@@ -106,7 +106,7 @@ class ExtendedRevolutionConfig(BaseDjangoRevolutionConfig):
106
106
  leads_enabled = base_module.is_leads_enabled()
107
107
  knowbase_enabled = base_module.is_knowbase_enabled()
108
108
  agents_enabled = base_module.is_agents_enabled()
109
- tasks_enabled = base_module.should_enable_tasks()
109
+ tasks_enabled = base_module.should_enable_rearq()
110
110
  payments_enabled = base_module.is_payments_enabled()
111
111
 
112
112
  # Add Support zone if enabled
@@ -156,16 +156,14 @@ class BaseCfgModule(ABC):
156
156
  """
157
157
  return self._get_config_key('enable_knowbase', False)
158
158
 
159
- def should_enable_tasks(self) -> bool:
159
+ def should_enable_rq(self) -> bool:
160
160
  """
161
- Check if django-cfg Tasks is enabled.
162
- Auto-enables if knowbase or agents are enabled.
161
+ Check if django-cfg RQ is enabled.
163
162
 
164
163
  Returns:
165
- True if Tasks is enabled, False otherwise
164
+ True if RQ is enabled, False otherwise
166
165
  """
167
-
168
- return self.get_config().should_enable_tasks()
166
+ return self.get_config().should_enable_rq()
169
167
 
170
168
  def is_maintenance_enabled(self) -> bool:
171
169
  """
@@ -206,6 +204,21 @@ class BaseCfgModule(ABC):
206
204
 
207
205
  return False
208
206
 
207
+ def is_grpc_enabled(self) -> bool:
208
+ """
209
+ Check if django-cfg gRPC is enabled.
210
+
211
+ Returns:
212
+ True if gRPC is enabled, False otherwise
213
+ """
214
+ grpc_config = self._get_config_key('grpc', None)
215
+
216
+ # Check if grpc config exists and is enabled
217
+ if grpc_config and hasattr(grpc_config, 'enabled'):
218
+ return grpc_config.enabled
219
+
220
+ return False
221
+
209
222
 
210
223
  # Export the base class
211
224
  __all__ = [
@@ -523,7 +523,7 @@ class PydanticAdminMixin:
523
523
 
524
524
  # Add Mermaid resources if plugins enabled
525
525
  if doc_config.enable_plugins:
526
- from django_cfg.modules.django_admin.utils.mermaid_plugin import get_mermaid_resources
526
+ from django_cfg.modules.django_admin.utils.markdown.mermaid_plugin import get_mermaid_resources
527
527
  extra_context['mermaid_resources'] = get_mermaid_resources()
528
528
 
529
529
  return super().changelist_view(request, extra_context)
@@ -548,7 +548,7 @@ class PydanticAdminMixin:
548
548
 
549
549
  # Add Mermaid resources if plugins enabled
550
550
  if doc_config.enable_plugins:
551
- from django_cfg.modules.django_admin.utils.mermaid_plugin import get_mermaid_resources
551
+ from django_cfg.modules.django_admin.utils.markdown.mermaid_plugin import get_mermaid_resources
552
552
  extra_context['mermaid_resources'] = get_mermaid_resources()
553
553
 
554
554
  return super().changeform_view(request, object_id, form_url, extra_context)