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.
Files changed (61) hide show
  1. {dj_queue-0.2.3 → dj_queue-0.3.0}/PKG-INFO +6 -3
  2. {dj_queue-0.2.3 → dj_queue-0.3.0}/README.md +5 -2
  3. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/admin.py +94 -43
  4. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/dashboard.py +248 -169
  5. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/jobs.py +17 -0
  6. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/base.py +39 -13
  7. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/supervisor.py +30 -5
  8. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +14 -0
  9. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +11 -0
  10. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +23 -0
  11. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +9 -0
  12. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_paginator.html +19 -0
  13. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_queue_controls.html +18 -0
  14. dj_queue-0.3.0/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +13 -0
  15. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/change_form.html +1 -1
  16. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/dashboard.html +9 -279
  17. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +5 -46
  18. {dj_queue-0.2.3 → dj_queue-0.3.0}/pyproject.toml +1 -1
  19. {dj_queue-0.2.3 → dj_queue-0.3.0}/LICENSE +0 -0
  20. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/__init__.py +0 -0
  21. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/api.py +0 -0
  22. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/apps.py +0 -0
  23. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/backend.py +0 -0
  24. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/config.py +0 -0
  25. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/contrib/__init__.py +0 -0
  26. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/contrib/asgi.py +0 -0
  27. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/contrib/gunicorn.py +0 -0
  28. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/db.py +0 -0
  29. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/exceptions.py +0 -0
  30. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/hooks.py +0 -0
  31. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/log.py +0 -0
  32. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/__init__.py +0 -0
  33. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/__init__.py +0 -0
  34. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/dj_queue.py +0 -0
  35. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
  36. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  37. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0001_initial.py +0 -0
  38. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  39. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  40. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/0004_dashboard.py +0 -0
  41. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/migrations/__init__.py +0 -0
  42. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/__init__.py +0 -0
  43. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/jobs.py +0 -0
  44. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/recurring.py +0 -0
  45. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/models/runtime.py +0 -0
  46. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/__init__.py +0 -0
  47. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/cleanup.py +0 -0
  48. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/concurrency.py +0 -0
  49. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/operations/recurring.py +0 -0
  50. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/routers.py +0 -0
  51. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/__init__.py +0 -0
  52. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/dispatcher.py +0 -0
  53. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/errors.py +0 -0
  54. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/interruptible.py +0 -0
  55. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/notify.py +0 -0
  56. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/pidfile.py +0 -0
  57. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/pool.py +0 -0
  58. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/procline.py +0 -0
  59. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/scheduler.py +0 -0
  60. {dj_queue-0.2.3 → dj_queue-0.3.0}/dj_queue/runtime/worker.py +0 -0
  61. {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.2.3
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, or call the same
434
- operations directly through the operations layer:
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, or call the same
408
- operations directly through the operations layer:
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
- action = request.POST.get("action")
137
- try:
138
- message = dashboard.apply_queue_action(
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
- except ValueError as exc:
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
- job_ids = [job_id for job_id in request.POST.getlist("job_ids") if job_id]
158
- try:
159
- message = dashboard.apply_job_action(
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 or obj.status != "failed":
483
+ if obj is None:
478
484
  return ()
479
- return (
480
- {"name": "retry", "label": "Retry failed job", "css_class": "djq-object-action-retry"},
481
- {
482
- "name": "discard",
483
- "label": "Discard failed job",
484
- "css_class": "djq-object-action-discard",
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 HttpResponseRedirect(
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 HttpResponseRedirect(
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 HttpResponseRedirect(self._change_url(object_id=obj.pk, backend_alias=obj.backend_name))
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 HttpResponseRedirect(self._change_url(object_id=obj.pk, backend_alias=backend_alias))
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):