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,400 @@
1
+ """
2
+ Django-RQ Schedule Management ViewSet.
3
+
4
+ Provides REST API endpoints for managing scheduled jobs using rq-scheduler.
5
+ """
6
+
7
+ from datetime import datetime
8
+
9
+ from django.core.exceptions import ImproperlyConfigured
10
+ from django_cfg.mixins import AdminAPIMixin
11
+ from django_cfg.modules.django_logging import get_logger
12
+ from drf_spectacular.utils import extend_schema, OpenApiParameter
13
+ from rest_framework import status, viewsets
14
+ from rest_framework.decorators import action
15
+ from rest_framework.response import Response
16
+
17
+ from ..serializers import (
18
+ ScheduleCreateSerializer,
19
+ ScheduledJobSerializer,
20
+ ScheduleActionResponseSerializer,
21
+ )
22
+
23
+ logger = get_logger("rq.schedule")
24
+
25
+
26
+ class ScheduleViewSet(AdminAPIMixin, viewsets.ViewSet):
27
+ """
28
+ ViewSet for RQ schedule management.
29
+
30
+ Provides endpoints for:
31
+ - List scheduled jobs
32
+ - Create scheduled jobs (one-time, interval, cron)
33
+ - Get scheduled job details
34
+ - Cancel scheduled jobs
35
+
36
+ Requires admin authentication (JWT, Session, or Basic Auth).
37
+ Requires rq-scheduler to be installed: pip install rq-scheduler
38
+ """
39
+
40
+ @extend_schema(
41
+ tags=["RQ Schedules"],
42
+ summary="List scheduled jobs",
43
+ description="Returns list of all scheduled jobs across all queues.",
44
+ parameters=[
45
+ OpenApiParameter(
46
+ name="queue",
47
+ type=str,
48
+ location=OpenApiParameter.QUERY,
49
+ required=False,
50
+ description="Filter by queue name",
51
+ ),
52
+ ],
53
+ responses={
54
+ 200: ScheduledJobSerializer(many=True),
55
+ },
56
+ )
57
+ def list(self, request):
58
+ """List all scheduled jobs (from rq-scheduler Redis + config preview)."""
59
+ try:
60
+ import django_rq
61
+ from django.conf import settings
62
+
63
+ queue_filter = request.query_params.get('queue')
64
+
65
+ # Get all schedulers
66
+ queue_names = settings.RQ_QUEUES.keys() if hasattr(settings, 'RQ_QUEUES') else []
67
+ if queue_filter:
68
+ queue_names = [q for q in queue_names if q == queue_filter]
69
+
70
+ all_jobs = []
71
+
72
+ for queue_name in queue_names:
73
+ try:
74
+ scheduler = django_rq.get_scheduler(queue_name)
75
+ scheduled_jobs = scheduler.get_jobs()
76
+
77
+ for job in scheduled_jobs:
78
+ job_data = {
79
+ "id": job.id,
80
+ "func": job.func_name,
81
+ "args": list(job.args or []),
82
+ "kwargs": job.kwargs or {},
83
+ "queue_name": queue_name,
84
+ "scheduled_time": job.meta.get('scheduled_time'),
85
+ "interval": job.meta.get('interval'),
86
+ "cron": job.meta.get('cron_string'),
87
+ "timeout": job.timeout,
88
+ "result_ttl": job.result_ttl,
89
+ "repeat": job.meta.get('repeat'),
90
+ "description": job.description,
91
+ "created_at": job.created_at,
92
+ "meta": job.meta or {},
93
+ }
94
+ all_jobs.append(job_data)
95
+
96
+ except ImproperlyConfigured as e:
97
+ logger.warning(f"rq-scheduler not installed: {e}")
98
+ return Response(
99
+ {"error": "rq-scheduler not installed. Install with: pip install rq-scheduler"},
100
+ status=status.HTTP_501_NOT_IMPLEMENTED,
101
+ )
102
+ except Exception as e:
103
+ logger.debug(f"Failed to get scheduled jobs for queue {queue_name}: {e}")
104
+
105
+ # If no jobs in scheduler, return schedules from config as preview
106
+ if not all_jobs:
107
+ from ..services import get_rq_config
108
+
109
+ config = get_rq_config()
110
+ if config and hasattr(config, 'schedules'):
111
+ for idx, schedule in enumerate(config.schedules):
112
+ # Apply queue filter
113
+ if queue_filter and schedule.queue != queue_filter:
114
+ continue
115
+
116
+ job_data = {
117
+ "id": schedule.job_id or f"config_{idx}",
118
+ "func": schedule.func,
119
+ "args": schedule.args,
120
+ "kwargs": schedule.kwargs,
121
+ "queue_name": schedule.queue,
122
+ "scheduled_time": schedule.scheduled_time,
123
+ "interval": schedule.interval,
124
+ "cron": schedule.cron,
125
+ "timeout": schedule.timeout,
126
+ "result_ttl": schedule.result_ttl,
127
+ "repeat": schedule.repeat,
128
+ "description": schedule.description or "",
129
+ "created_at": None,
130
+ "meta": {"source": "config", "status": "not_registered"},
131
+ }
132
+ all_jobs.append(job_data)
133
+
134
+ serializer = ScheduledJobSerializer(data=all_jobs, many=True)
135
+ serializer.is_valid(raise_exception=True)
136
+ return Response(serializer.data)
137
+
138
+ except Exception as e:
139
+ logger.error(f"Failed to list scheduled jobs: {e}", exc_info=True)
140
+ return Response(
141
+ {"error": "Internal server error"},
142
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
143
+ )
144
+
145
+ @extend_schema(
146
+ tags=["RQ Schedules"],
147
+ summary="Create scheduled job",
148
+ description="Schedule a job to run at specific time, interval, or cron schedule.",
149
+ request=ScheduleCreateSerializer,
150
+ responses={
151
+ 201: ScheduleActionResponseSerializer,
152
+ },
153
+ )
154
+ def create(self, request):
155
+ """Create a new scheduled job."""
156
+ try:
157
+ import django_rq
158
+ from datetime import datetime, timezone
159
+
160
+ serializer = ScheduleCreateSerializer(data=request.data)
161
+ serializer.is_valid(raise_exception=True)
162
+ data = serializer.validated_data
163
+
164
+ queue_name = data['queue_name']
165
+
166
+ try:
167
+ scheduler = django_rq.get_scheduler(queue_name)
168
+ except ImproperlyConfigured as e:
169
+ logger.warning(f"rq-scheduler not installed: {e}")
170
+ return Response(
171
+ {"error": "rq-scheduler not installed. Install with: pip install rq-scheduler"},
172
+ status=status.HTTP_501_NOT_IMPLEMENTED,
173
+ )
174
+
175
+ # Prepare job kwargs
176
+ job_kwargs = {
177
+ 'func': data['func'],
178
+ 'args': data.get('args', []),
179
+ 'kwargs': data.get('kwargs', {}),
180
+ 'queue_name': queue_name,
181
+ }
182
+
183
+ if data.get('timeout'):
184
+ job_kwargs['timeout'] = data['timeout']
185
+
186
+ if data.get('result_ttl'):
187
+ job_kwargs['result_ttl'] = data['result_ttl']
188
+
189
+ if data.get('repeat') is not None:
190
+ job_kwargs['repeat'] = data['repeat']
191
+
192
+ if data.get('description'):
193
+ job_kwargs['description'] = data['description']
194
+
195
+ # Schedule based on method
196
+ if data.get('scheduled_time'):
197
+ # One-time schedule at specific time
198
+ scheduled_time = data['scheduled_time']
199
+ if scheduled_time.tzinfo is None:
200
+ scheduled_time = scheduled_time.replace(tzinfo=timezone.utc)
201
+ job = scheduler.enqueue_at(scheduled_time, **job_kwargs)
202
+ schedule_type = "one-time"
203
+
204
+ elif data.get('interval'):
205
+ # Interval-based scheduling
206
+ interval = data['interval']
207
+ scheduled_time = datetime.now(timezone.utc)
208
+ job = scheduler.schedule(
209
+ scheduled_time=scheduled_time,
210
+ interval=interval,
211
+ **job_kwargs
212
+ )
213
+ schedule_type = "interval"
214
+
215
+ elif data.get('cron'):
216
+ # Cron-based scheduling
217
+ cron_string = data['cron']
218
+ job = scheduler.cron(
219
+ cron_string,
220
+ **job_kwargs
221
+ )
222
+ schedule_type = "cron"
223
+
224
+ else:
225
+ return Response(
226
+ {"error": "Must provide scheduled_time, interval, or cron"},
227
+ status=status.HTTP_400_BAD_REQUEST,
228
+ )
229
+
230
+ response_data = {
231
+ "success": True,
232
+ "message": f"Job scheduled successfully ({schedule_type})",
233
+ "job_id": job.id,
234
+ "action": "create",
235
+ }
236
+
237
+ response_serializer = ScheduleActionResponseSerializer(data=response_data)
238
+ response_serializer.is_valid(raise_exception=True)
239
+ return Response(response_serializer.data, status=status.HTTP_201_CREATED)
240
+
241
+ except Exception as e:
242
+ logger.error(f"Failed to create scheduled job: {e}", exc_info=True)
243
+ return Response(
244
+ {"error": str(e)},
245
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
246
+ )
247
+
248
+ @extend_schema(
249
+ tags=["RQ Schedules"],
250
+ summary="Get scheduled job details",
251
+ description="Returns detailed information about a specific scheduled job.",
252
+ parameters=[
253
+ OpenApiParameter(
254
+ name="pk",
255
+ type=str,
256
+ location=OpenApiParameter.PATH,
257
+ description="Job ID",
258
+ ),
259
+ OpenApiParameter(
260
+ name="queue",
261
+ type=str,
262
+ location=OpenApiParameter.QUERY,
263
+ required=False,
264
+ description="Queue name (optional, improves performance)",
265
+ ),
266
+ ],
267
+ responses={
268
+ 200: ScheduledJobSerializer,
269
+ },
270
+ )
271
+ def retrieve(self, request, pk=None):
272
+ """Get scheduled job details by ID."""
273
+ try:
274
+ import django_rq
275
+ from django.conf import settings
276
+ from rq.job import Job
277
+
278
+ queue_filter = request.query_params.get('queue')
279
+ queue_names = settings.RQ_QUEUES.keys() if hasattr(settings, 'RQ_QUEUES') else []
280
+
281
+ if queue_filter:
282
+ queue_names = [q for q in queue_names if q == queue_filter]
283
+
284
+ # Search for job across queues
285
+ for queue_name in queue_names:
286
+ try:
287
+ scheduler = django_rq.get_scheduler(queue_name)
288
+ queue = django_rq.get_queue(queue_name)
289
+
290
+ # Try to fetch job
291
+ job = Job.fetch(pk, connection=queue.connection)
292
+
293
+ # Check if it's a scheduled job
294
+ if job and job.id in [j.id for j in scheduler.get_jobs()]:
295
+ job_data = {
296
+ "id": job.id,
297
+ "func": job.func_name,
298
+ "args": list(job.args or []),
299
+ "kwargs": job.kwargs or {},
300
+ "queue_name": queue_name,
301
+ "scheduled_time": job.meta.get('scheduled_time'),
302
+ "interval": job.meta.get('interval'),
303
+ "cron": job.meta.get('cron_string'),
304
+ "timeout": job.timeout,
305
+ "result_ttl": job.result_ttl,
306
+ "repeat": job.meta.get('repeat'),
307
+ "description": job.description,
308
+ "created_at": job.created_at,
309
+ "meta": job.meta or {},
310
+ }
311
+
312
+ serializer = ScheduledJobSerializer(data=job_data)
313
+ serializer.is_valid(raise_exception=True)
314
+ return Response(serializer.data)
315
+
316
+ except Exception as e:
317
+ logger.debug(f"Job {pk} not found in queue {queue_name}: {e}")
318
+ continue
319
+
320
+ return Response(
321
+ {"error": f"Scheduled job {pk} not found"},
322
+ status=status.HTTP_404_NOT_FOUND,
323
+ )
324
+
325
+ except Exception as e:
326
+ logger.error(f"Failed to retrieve scheduled job: {e}", exc_info=True)
327
+ return Response(
328
+ {"error": "Internal server error"},
329
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
330
+ )
331
+
332
+ @extend_schema(
333
+ tags=["RQ Schedules"],
334
+ summary="Cancel scheduled job",
335
+ description="Cancel a scheduled job by ID.",
336
+ parameters=[
337
+ OpenApiParameter(
338
+ name="pk",
339
+ type=str,
340
+ location=OpenApiParameter.PATH,
341
+ description="Job ID",
342
+ ),
343
+ OpenApiParameter(
344
+ name="queue",
345
+ type=str,
346
+ location=OpenApiParameter.QUERY,
347
+ required=False,
348
+ description="Queue name (optional, improves performance)",
349
+ ),
350
+ ],
351
+ responses={
352
+ 200: ScheduleActionResponseSerializer,
353
+ },
354
+ )
355
+ def destroy(self, request, pk=None):
356
+ """Cancel a scheduled job."""
357
+ try:
358
+ import django_rq
359
+ from django.conf import settings
360
+
361
+ queue_filter = request.query_params.get('queue')
362
+ queue_names = settings.RQ_QUEUES.keys() if hasattr(settings, 'RQ_QUEUES') else []
363
+
364
+ if queue_filter:
365
+ queue_names = [q for q in queue_names if q == queue_filter]
366
+
367
+ # Search for job across queues
368
+ for queue_name in queue_names:
369
+ try:
370
+ scheduler = django_rq.get_scheduler(queue_name)
371
+
372
+ # Try to cancel job
373
+ scheduler.cancel(pk)
374
+
375
+ response_data = {
376
+ "success": True,
377
+ "message": f"Scheduled job {pk} cancelled successfully",
378
+ "job_id": pk,
379
+ "action": "cancel",
380
+ }
381
+
382
+ response_serializer = ScheduleActionResponseSerializer(data=response_data)
383
+ response_serializer.is_valid(raise_exception=True)
384
+ return Response(response_serializer.data)
385
+
386
+ except Exception as e:
387
+ logger.debug(f"Failed to cancel job {pk} in queue {queue_name}: {e}")
388
+ continue
389
+
390
+ return Response(
391
+ {"error": f"Scheduled job {pk} not found"},
392
+ status=status.HTTP_404_NOT_FOUND,
393
+ )
394
+
395
+ except Exception as e:
396
+ logger.error(f"Failed to cancel scheduled job: {e}", exc_info=True)
397
+ return Response(
398
+ {"error": "Internal server error"},
399
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
400
+ )