dj-queue 0.2.3__tar.gz → 0.3.0__tar.gz
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.
- {dj_queue-0.2.3 → dj_queue-0.3.0}/PKG-INFO +6 -3
- {dj_queue-0.2.3 → dj_queue-0.3.0}/README.md +5 -2
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/admin.py +94 -43
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/dashboard.py +248 -169
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/jobs.py +17 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/base.py +39 -13
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/supervisor.py +30 -5
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +14 -0
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +11 -0
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +23 -0
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +9 -0
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_paginator.html +19 -0
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_queue_controls.html +18 -0
- dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +13 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/change_form.html +1 -1
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/dashboard.html +9 -279
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +5 -46
- {dj_queue-0.2.3 → dj_queue-0.3.0}/pyproject.toml +1 -1
- {dj_queue-0.2.3 → dj_queue-0.3.0}/LICENSE +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/api.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/apps.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/backend.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/config.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/db.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/log.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/concurrency.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Database-backed task queue backend for Django's django.tasks framework
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -164,6 +164,8 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
164
164
|
- queue, process, recurring-task, and semaphore overview
|
|
165
165
|
- backend-aware dashboard and raw changelists
|
|
166
166
|
- queue controls: pause, resume, clear ready
|
|
167
|
+
- job detail action: enqueue a fresh copy of any stored job
|
|
168
|
+
- pause detail action: resume the paused queue from the raw pause row
|
|
167
169
|
- failed-job actions: retry and discard from list and detail views
|
|
168
170
|
- queue drill-down pages for state-specific inspection
|
|
169
171
|
|
|
@@ -430,8 +432,9 @@ python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
|
430
432
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
431
433
|
queue database, including the exception class, message, and traceback.
|
|
432
434
|
|
|
433
|
-
You can retry and discard failed jobs through Django admin,
|
|
434
|
-
|
|
435
|
+
You can retry and discard failed jobs through Django admin, and any raw job
|
|
436
|
+
detail page can enqueue a fresh copy of that stored job. The failed-job actions
|
|
437
|
+
also stay available directly through the operations layer:
|
|
435
438
|
|
|
436
439
|
```python
|
|
437
440
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -138,6 +138,8 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
138
138
|
- queue, process, recurring-task, and semaphore overview
|
|
139
139
|
- backend-aware dashboard and raw changelists
|
|
140
140
|
- queue controls: pause, resume, clear ready
|
|
141
|
+
- job detail action: enqueue a fresh copy of any stored job
|
|
142
|
+
- pause detail action: resume the paused queue from the raw pause row
|
|
141
143
|
- failed-job actions: retry and discard from list and detail views
|
|
142
144
|
- queue drill-down pages for state-specific inspection
|
|
143
145
|
|
|
@@ -404,8 +406,9 @@ python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
|
404
406
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
405
407
|
queue database, including the exception class, message, and traceback.
|
|
406
408
|
|
|
407
|
-
You can retry and discard failed jobs through Django admin,
|
|
408
|
-
|
|
409
|
+
You can retry and discard failed jobs through Django admin, and any raw job
|
|
410
|
+
detail page can enqueue a fresh copy of that stored job. The failed-job actions
|
|
411
|
+
also stay available directly through the operations layer:
|
|
409
412
|
|
|
410
413
|
```python
|
|
411
414
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -15,7 +15,9 @@ from django.utils import timezone
|
|
|
15
15
|
|
|
16
16
|
from dj_queue.config import load_backend_config
|
|
17
17
|
from dj_queue import dashboard
|
|
18
|
+
from dj_queue.api import QueueInfo
|
|
18
19
|
from dj_queue.db import get_database_alias
|
|
20
|
+
from dj_queue.exceptions import EnqueueError
|
|
19
21
|
from dj_queue.models import (
|
|
20
22
|
BlockedExecution,
|
|
21
23
|
Dashboard,
|
|
@@ -26,6 +28,7 @@ from dj_queue.models import (
|
|
|
26
28
|
RecurringTask,
|
|
27
29
|
Semaphore,
|
|
28
30
|
)
|
|
31
|
+
from dj_queue.operations.jobs import enqueue_job_again
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
class DjQueueFirstAdminSite(admin.AdminSite):
|
|
@@ -129,48 +132,44 @@ class DashboardAdmin(admin.ModelAdmin):
|
|
|
129
132
|
return TemplateResponse(request, "admin/dj_queue/queue_jobs.html", context)
|
|
130
133
|
|
|
131
134
|
def queue_action_view(self, request, queue_name):
|
|
132
|
-
if request.method != "POST":
|
|
133
|
-
return HttpResponseNotAllowed(["POST"])
|
|
134
|
-
|
|
135
135
|
backend_alias = dashboard.resolve_backend_alias(request.POST.get("backend"))
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
return self._post_action_response(
|
|
137
|
+
request,
|
|
138
|
+
operation=lambda: dashboard.apply_queue_action(
|
|
139
139
|
backend_alias=backend_alias,
|
|
140
140
|
queue_name=queue_name,
|
|
141
|
-
action=action,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
self.message_user(request, str(exc), level=messages.ERROR)
|
|
145
|
-
else:
|
|
146
|
-
self.message_user(request, message, level=messages.SUCCESS)
|
|
147
|
-
return self._redirect(
|
|
148
|
-
request, self._queue_url(backend_alias=backend_alias, queue_name=queue_name)
|
|
141
|
+
action=request.POST.get("action"),
|
|
142
|
+
),
|
|
143
|
+
fallback_url=self._queue_url(backend_alias=backend_alias, queue_name=queue_name),
|
|
149
144
|
)
|
|
150
145
|
|
|
151
146
|
def job_action_view(self, request, queue_name):
|
|
152
|
-
if request.method != "POST":
|
|
153
|
-
return HttpResponseNotAllowed(["POST"])
|
|
154
|
-
|
|
155
147
|
backend_alias = dashboard.resolve_backend_alias(request.POST.get("backend"))
|
|
156
148
|
state = request.POST.get("state", "ready")
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
return self._post_action_response(
|
|
150
|
+
request,
|
|
151
|
+
operation=lambda: dashboard.apply_job_action(
|
|
160
152
|
backend_alias=backend_alias,
|
|
161
153
|
queue_name=queue_name,
|
|
162
154
|
state=state,
|
|
163
155
|
action=request.POST.get("action"),
|
|
164
|
-
job_ids=job_ids,
|
|
165
|
-
)
|
|
156
|
+
job_ids=[job_id for job_id in request.POST.getlist("job_ids") if job_id],
|
|
157
|
+
),
|
|
158
|
+
fallback_url=self._queue_url(
|
|
159
|
+
backend_alias=backend_alias, queue_name=queue_name, state=state
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _post_action_response(self, request, operation, fallback_url):
|
|
164
|
+
if request.method != "POST":
|
|
165
|
+
return HttpResponseNotAllowed(["POST"])
|
|
166
|
+
try:
|
|
167
|
+
message = operation()
|
|
166
168
|
except ValueError as exc:
|
|
167
169
|
self.message_user(request, str(exc), level=messages.ERROR)
|
|
168
170
|
else:
|
|
169
171
|
self.message_user(request, message, level=messages.SUCCESS)
|
|
170
|
-
return self._redirect(
|
|
171
|
-
request,
|
|
172
|
-
self._queue_url(backend_alias=backend_alias, queue_name=queue_name, state=state),
|
|
173
|
-
)
|
|
172
|
+
return self._redirect(request, fallback_url)
|
|
174
173
|
|
|
175
174
|
def _redirect(self, request, fallback_url):
|
|
176
175
|
next_url = request.POST.get("next")
|
|
@@ -214,6 +213,7 @@ class HiddenSidebarAdminMixin:
|
|
|
214
213
|
extra_context = {
|
|
215
214
|
**(extra_context or {}),
|
|
216
215
|
"dashboard_url": self._dashboard_url(request),
|
|
216
|
+
"changelist_url": self._changelist_url(backend_alias=self._backend_alias(request)),
|
|
217
217
|
"change_actions": self.get_change_actions(request, obj),
|
|
218
218
|
}
|
|
219
219
|
return super().changeform_view(
|
|
@@ -292,6 +292,12 @@ class HiddenSidebarAdminMixin:
|
|
|
292
292
|
def handle_change_action(self, request, obj, action):
|
|
293
293
|
return HttpResponseRedirect(request.get_full_path())
|
|
294
294
|
|
|
295
|
+
def _change_redirect(self, *, object_id, backend_alias):
|
|
296
|
+
return HttpResponseRedirect(self._change_url(object_id=object_id, backend_alias=backend_alias))
|
|
297
|
+
|
|
298
|
+
def _current_object_redirect(self, obj, *, backend_alias):
|
|
299
|
+
return self._change_redirect(object_id=obj.pk, backend_alias=backend_alias)
|
|
300
|
+
|
|
295
301
|
def _change_url(self, *, object_id, backend_alias):
|
|
296
302
|
url = reverse(
|
|
297
303
|
f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_change",
|
|
@@ -474,37 +480,60 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
474
480
|
return format_html('<a href="{}">{}</a>', url, obj.queue_name)
|
|
475
481
|
|
|
476
482
|
def get_change_actions(self, request, obj):
|
|
477
|
-
if obj is None
|
|
483
|
+
if obj is None:
|
|
478
484
|
return ()
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
actions = [{"name": "enqueue", "label": "Enqueue job", "css_class": "djq-object-action-retry"}]
|
|
486
|
+
if obj.status == "failed":
|
|
487
|
+
actions.extend(
|
|
488
|
+
(
|
|
489
|
+
{
|
|
490
|
+
"name": "retry",
|
|
491
|
+
"label": "Retry failed job",
|
|
492
|
+
"css_class": "djq-object-action-retry",
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"name": "discard",
|
|
496
|
+
"label": "Discard failed job",
|
|
497
|
+
"css_class": "djq-object-action-discard",
|
|
498
|
+
},
|
|
499
|
+
)
|
|
500
|
+
)
|
|
501
|
+
return tuple(actions)
|
|
487
502
|
|
|
488
503
|
def handle_change_action(self, request, obj, action):
|
|
504
|
+
if action == "enqueue":
|
|
505
|
+
try:
|
|
506
|
+
new_job = enqueue_job_again(obj.pk, backend_alias=obj.backend_name)
|
|
507
|
+
except (EnqueueError, ImportError, AttributeError) as exc:
|
|
508
|
+
self.message_user(request, f"Could not enqueue job: {exc}", level=messages.ERROR)
|
|
509
|
+
return self._current_object_redirect(obj, backend_alias=obj.backend_name)
|
|
510
|
+
|
|
511
|
+
self.message_user(
|
|
512
|
+
request,
|
|
513
|
+
format_html(
|
|
514
|
+
'Enqueued job <a href="{}">{}</a>.',
|
|
515
|
+
self._change_url(object_id=new_job.pk, backend_alias=new_job.backend_name),
|
|
516
|
+
new_job.pk,
|
|
517
|
+
),
|
|
518
|
+
level=messages.SUCCESS,
|
|
519
|
+
)
|
|
520
|
+
return self._current_object_redirect(obj, backend_alias=obj.backend_name)
|
|
521
|
+
|
|
489
522
|
if obj.status != "failed":
|
|
490
523
|
self.message_user(request, "This job is not failed", level=messages.ERROR)
|
|
491
|
-
return
|
|
492
|
-
self._change_url(object_id=obj.pk, backend_alias=obj.backend_name)
|
|
493
|
-
)
|
|
524
|
+
return self._current_object_redirect(obj, backend_alias=obj.backend_name)
|
|
494
525
|
|
|
495
526
|
if action == "retry":
|
|
496
527
|
obj.failed_execution.retry()
|
|
497
528
|
self.message_user(request, "Retried failed job", level=messages.SUCCESS)
|
|
498
|
-
return
|
|
499
|
-
self._change_url(object_id=obj.pk, backend_alias=obj.backend_name)
|
|
500
|
-
)
|
|
529
|
+
return self._current_object_redirect(obj, backend_alias=obj.backend_name)
|
|
501
530
|
|
|
502
531
|
if action == "discard":
|
|
503
532
|
obj.failed_execution.discard()
|
|
504
533
|
self.message_user(request, "Discarded failed job", level=messages.SUCCESS)
|
|
505
534
|
return HttpResponseRedirect(self._changelist_url(backend_alias=obj.backend_name))
|
|
506
535
|
|
|
507
|
-
return
|
|
536
|
+
return self._current_object_redirect(obj, backend_alias=obj.backend_name)
|
|
508
537
|
|
|
509
538
|
|
|
510
539
|
@admin.register(FailedExecution)
|
|
@@ -560,7 +589,7 @@ class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
560
589
|
self.message_user(request, "Discarded failed job", level=messages.SUCCESS)
|
|
561
590
|
return HttpResponseRedirect(self._changelist_url(backend_alias=backend_alias))
|
|
562
591
|
|
|
563
|
-
return
|
|
592
|
+
return self._current_object_redirect(obj, backend_alias=backend_alias)
|
|
564
593
|
|
|
565
594
|
|
|
566
595
|
@admin.register(Process)
|
|
@@ -633,6 +662,28 @@ class PauseAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
633
662
|
readonly_fields = ("queue_name", "created_at")
|
|
634
663
|
search_fields = ("queue_name",)
|
|
635
664
|
|
|
665
|
+
def get_change_actions(self, request, obj):
|
|
666
|
+
if obj is None:
|
|
667
|
+
return ()
|
|
668
|
+
return ({"name": "resume", "label": "Resume queue", "css_class": "djq-object-action-retry"},)
|
|
669
|
+
|
|
670
|
+
def handle_change_action(self, request, obj, action):
|
|
671
|
+
backend_alias = self._backend_alias(request)
|
|
672
|
+
if action == "resume":
|
|
673
|
+
QueueInfo(obj.queue_name, backend_alias=backend_alias).resume()
|
|
674
|
+
self.message_user(
|
|
675
|
+
request,
|
|
676
|
+
format_html(
|
|
677
|
+
'Resumed queue <a href="{}">{}</a>',
|
|
678
|
+
f"{reverse('admin:dj_queue_dashboard_queue', args=[obj.queue_name])}?{urlencode({'backend': backend_alias})}",
|
|
679
|
+
obj.queue_name,
|
|
680
|
+
),
|
|
681
|
+
level=messages.SUCCESS,
|
|
682
|
+
)
|
|
683
|
+
return HttpResponseRedirect(self._changelist_url(backend_alias=backend_alias))
|
|
684
|
+
|
|
685
|
+
return self._current_object_redirect(obj, backend_alias=backend_alias)
|
|
686
|
+
|
|
636
687
|
|
|
637
688
|
@admin.register(Semaphore)
|
|
638
689
|
class SemaphoreAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|