django-cfg 1.5.1__py3-none-any.whl → 1.5.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +2 -3
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +6 -7
- django_cfg/core/base/config_model.py +10 -26
- django_cfg/core/builders/apps_builder.py +4 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/orchestrator.py +9 -19
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +4 -6
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/html/composition.py +9 -2
- django_cfg/modules/django_unfold/navigation.py +1 -26
- django_cfg/pyproject.toml +4 -4
- django_cfg/registry/core.py +4 -7
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.dist-info}/METADATA +5 -6
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.dist-info}/RECORD +77 -82
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/config.py +0 -98
- django_cfg/apps/tasks/admin/task_log.py +0 -238
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/migrations/__init__.py +0 -0
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.3.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
|
+
)
|