dj-queue 0.2.2__tar.gz → 0.2.4__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.2 → dj_queue-0.2.4}/PKG-INFO +28 -10
- {dj_queue-0.2.2 → dj_queue-0.2.4}/README.md +27 -9
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/admin.py +65 -9
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/dashboard.py +115 -16
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/operations/jobs.py +17 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/change_form.html +1 -1
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/dashboard.html +134 -2
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/queue_jobs.html +10 -2
- {dj_queue-0.2.2 → dj_queue-0.2.4}/pyproject.toml +1 -1
- {dj_queue-0.2.2 → dj_queue-0.2.4}/LICENSE +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/api.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/apps.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/backend.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/config.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/db.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/hooks.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/log.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/operations/concurrency.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/routers.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/base.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.2.2 → dj_queue-0.2.4}/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
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Database-backed task queue backend for Django's django.tasks framework
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -27,11 +27,11 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
# dj_queue
|
|
28
28
|
|
|
29
29
|
[](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml)
|
|
30
|
-

|
|
30
|
+
[](https://pypi.org/project/dj-queue/)
|
|
31
31
|
[](https://djangopackages.org/packages/p/dj-queue/)
|
|
32
32
|

|
|
33
33
|

|
|
34
|
-

|
|
34
|
+
[](https://github.com/coriocactus/dj_queue/blob/main/LICENSE)
|
|
35
35
|
|
|
36
36
|
`dj_queue` is a database-backed task queue backend for the `django.tasks` framework.
|
|
37
37
|
|
|
@@ -59,7 +59,7 @@ It has a narrow, explicit shape:
|
|
|
59
59
|
- polling remains the correctness path on every supported database
|
|
60
60
|
|
|
61
61
|
For detailed comparisons with Celery, RQ, Procrastinate, and other alternatives,
|
|
62
|
-
see [COMPARISONS.md](COMPARISONS.md).
|
|
62
|
+
see [COMPARISONS.md](docs/COMPARISONS.md).
|
|
63
63
|
|
|
64
64
|
## Installation
|
|
65
65
|
|
|
@@ -156,6 +156,27 @@ print(fresh_result.return_value)
|
|
|
156
156
|
|
|
157
157
|
When the worker has executed the job, `fresh_result.return_value` will be `10`.
|
|
158
158
|
|
|
159
|
+
## Admin Integration
|
|
160
|
+
|
|
161
|
+
If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
162
|
+
`/admin/dj_queue/dashboard/`.
|
|
163
|
+
|
|
164
|
+
- queue, process, recurring-task, and semaphore overview
|
|
165
|
+
- backend-aware dashboard and raw changelists
|
|
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
|
|
169
|
+
- failed-job actions: retry and discard from list and detail views
|
|
170
|
+
- queue drill-down pages for state-specific inspection
|
|
171
|
+
|
|
172
|
+
**Dashboard overview**
|
|
173
|
+
|
|
174
|
+

|
|
175
|
+
|
|
176
|
+
**Queue drill-down**
|
|
177
|
+
|
|
178
|
+

|
|
179
|
+
|
|
159
180
|
## Common Patterns
|
|
160
181
|
|
|
161
182
|
### Scheduled jobs
|
|
@@ -406,17 +427,14 @@ python manage.py dj_queue_prune --older-than 86400
|
|
|
406
427
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
407
428
|
```
|
|
408
429
|
|
|
409
|
-
If Django admin is installed, `dj_queue` also registers the main operational
|
|
410
|
-
models there, including jobs, failed executions, processes, recurring tasks,
|
|
411
|
-
pauses, and semaphores.
|
|
412
|
-
|
|
413
430
|
## Failed Jobs
|
|
414
431
|
|
|
415
432
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
416
433
|
queue database, including the exception class, message, and traceback.
|
|
417
434
|
|
|
418
|
-
You can retry and discard failed jobs through Django admin,
|
|
419
|
-
|
|
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:
|
|
420
438
|
|
|
421
439
|
```python
|
|
422
440
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# dj_queue
|
|
2
2
|
|
|
3
3
|
[](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml)
|
|
4
|
-

|
|
4
|
+
[](https://pypi.org/project/dj-queue/)
|
|
5
5
|
[](https://djangopackages.org/packages/p/dj-queue/)
|
|
6
6
|

|
|
7
7
|

|
|
8
|
-

|
|
8
|
+
[](https://github.com/coriocactus/dj_queue/blob/main/LICENSE)
|
|
9
9
|
|
|
10
10
|
`dj_queue` is a database-backed task queue backend for the `django.tasks` framework.
|
|
11
11
|
|
|
@@ -33,7 +33,7 @@ It has a narrow, explicit shape:
|
|
|
33
33
|
- polling remains the correctness path on every supported database
|
|
34
34
|
|
|
35
35
|
For detailed comparisons with Celery, RQ, Procrastinate, and other alternatives,
|
|
36
|
-
see [COMPARISONS.md](COMPARISONS.md).
|
|
36
|
+
see [COMPARISONS.md](docs/COMPARISONS.md).
|
|
37
37
|
|
|
38
38
|
## Installation
|
|
39
39
|
|
|
@@ -130,6 +130,27 @@ print(fresh_result.return_value)
|
|
|
130
130
|
|
|
131
131
|
When the worker has executed the job, `fresh_result.return_value` will be `10`.
|
|
132
132
|
|
|
133
|
+
## Admin Integration
|
|
134
|
+
|
|
135
|
+
If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
136
|
+
`/admin/dj_queue/dashboard/`.
|
|
137
|
+
|
|
138
|
+
- queue, process, recurring-task, and semaphore overview
|
|
139
|
+
- backend-aware dashboard and raw changelists
|
|
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
|
|
143
|
+
- failed-job actions: retry and discard from list and detail views
|
|
144
|
+
- queue drill-down pages for state-specific inspection
|
|
145
|
+
|
|
146
|
+
**Dashboard overview**
|
|
147
|
+
|
|
148
|
+

|
|
149
|
+
|
|
150
|
+
**Queue drill-down**
|
|
151
|
+
|
|
152
|
+

|
|
153
|
+
|
|
133
154
|
## Common Patterns
|
|
134
155
|
|
|
135
156
|
### Scheduled jobs
|
|
@@ -380,17 +401,14 @@ python manage.py dj_queue_prune --older-than 86400
|
|
|
380
401
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
381
402
|
```
|
|
382
403
|
|
|
383
|
-
If Django admin is installed, `dj_queue` also registers the main operational
|
|
384
|
-
models there, including jobs, failed executions, processes, recurring tasks,
|
|
385
|
-
pauses, and semaphores.
|
|
386
|
-
|
|
387
404
|
## Failed Jobs
|
|
388
405
|
|
|
389
406
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
390
407
|
queue database, including the exception class, message, and traceback.
|
|
391
408
|
|
|
392
|
-
You can retry and discard failed jobs through Django admin,
|
|
393
|
-
|
|
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:
|
|
394
412
|
|
|
395
413
|
```python
|
|
396
414
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -15,6 +15,7 @@ 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
|
|
19
20
|
from dj_queue.models import (
|
|
20
21
|
BlockedExecution,
|
|
@@ -26,6 +27,7 @@ from dj_queue.models import (
|
|
|
26
27
|
RecurringTask,
|
|
27
28
|
Semaphore,
|
|
28
29
|
)
|
|
30
|
+
from dj_queue.operations.jobs import enqueue_job_again
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class DjQueueFirstAdminSite(admin.AdminSite):
|
|
@@ -214,6 +216,7 @@ class HiddenSidebarAdminMixin:
|
|
|
214
216
|
extra_context = {
|
|
215
217
|
**(extra_context or {}),
|
|
216
218
|
"dashboard_url": self._dashboard_url(request),
|
|
219
|
+
"changelist_url": self._changelist_url(backend_alias=self._backend_alias(request)),
|
|
217
220
|
"change_actions": self.get_change_actions(request, obj),
|
|
218
221
|
}
|
|
219
222
|
return super().changeform_view(
|
|
@@ -474,18 +477,49 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
474
477
|
return format_html('<a href="{}">{}</a>', url, obj.queue_name)
|
|
475
478
|
|
|
476
479
|
def get_change_actions(self, request, obj):
|
|
477
|
-
if obj is None
|
|
480
|
+
if obj is None:
|
|
478
481
|
return ()
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
482
|
+
actions = [{"name": "enqueue", "label": "Enqueue job", "css_class": "djq-object-action-retry"}]
|
|
483
|
+
if obj.status == "failed":
|
|
484
|
+
actions.extend(
|
|
485
|
+
(
|
|
486
|
+
{
|
|
487
|
+
"name": "retry",
|
|
488
|
+
"label": "Retry failed job",
|
|
489
|
+
"css_class": "djq-object-action-retry",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"name": "discard",
|
|
493
|
+
"label": "Discard failed job",
|
|
494
|
+
"css_class": "djq-object-action-discard",
|
|
495
|
+
},
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
return tuple(actions)
|
|
487
499
|
|
|
488
500
|
def handle_change_action(self, request, obj, action):
|
|
501
|
+
if action == "enqueue":
|
|
502
|
+
try:
|
|
503
|
+
new_job = enqueue_job_again(obj.pk, backend_alias=obj.backend_name)
|
|
504
|
+
except Exception as exc:
|
|
505
|
+
self.message_user(request, f"Could not enqueue job: {exc}", level=messages.ERROR)
|
|
506
|
+
return HttpResponseRedirect(
|
|
507
|
+
self._change_url(object_id=obj.pk, backend_alias=obj.backend_name)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
self.message_user(
|
|
511
|
+
request,
|
|
512
|
+
format_html(
|
|
513
|
+
'Enqueued job <a href="{}">{}</a>.',
|
|
514
|
+
self._change_url(object_id=new_job.pk, backend_alias=new_job.backend_name),
|
|
515
|
+
new_job.pk,
|
|
516
|
+
),
|
|
517
|
+
level=messages.SUCCESS,
|
|
518
|
+
)
|
|
519
|
+
return HttpResponseRedirect(
|
|
520
|
+
self._change_url(object_id=obj.pk, backend_alias=obj.backend_name)
|
|
521
|
+
)
|
|
522
|
+
|
|
489
523
|
if obj.status != "failed":
|
|
490
524
|
self.message_user(request, "This job is not failed", level=messages.ERROR)
|
|
491
525
|
return HttpResponseRedirect(
|
|
@@ -633,6 +667,28 @@ class PauseAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
633
667
|
readonly_fields = ("queue_name", "created_at")
|
|
634
668
|
search_fields = ("queue_name",)
|
|
635
669
|
|
|
670
|
+
def get_change_actions(self, request, obj):
|
|
671
|
+
if obj is None:
|
|
672
|
+
return ()
|
|
673
|
+
return ({"name": "resume", "label": "Resume queue", "css_class": "djq-object-action-retry"},)
|
|
674
|
+
|
|
675
|
+
def handle_change_action(self, request, obj, action):
|
|
676
|
+
backend_alias = self._backend_alias(request)
|
|
677
|
+
if action == "resume":
|
|
678
|
+
QueueInfo(obj.queue_name, backend_alias=backend_alias).resume()
|
|
679
|
+
self.message_user(
|
|
680
|
+
request,
|
|
681
|
+
format_html(
|
|
682
|
+
'Resumed queue <a href="{}">{}</a>',
|
|
683
|
+
f"{reverse('admin:dj_queue_dashboard_queue', args=[obj.queue_name])}?{urlencode({'backend': backend_alias})}",
|
|
684
|
+
obj.queue_name,
|
|
685
|
+
),
|
|
686
|
+
level=messages.SUCCESS,
|
|
687
|
+
)
|
|
688
|
+
return HttpResponseRedirect(self._changelist_url(backend_alias=backend_alias))
|
|
689
|
+
|
|
690
|
+
return HttpResponseRedirect(self._change_url(object_id=obj.pk, backend_alias=backend_alias))
|
|
691
|
+
|
|
636
692
|
|
|
637
693
|
@admin.register(Semaphore)
|
|
638
694
|
class SemaphoreAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
@@ -15,7 +15,7 @@ from django.utils import timezone
|
|
|
15
15
|
|
|
16
16
|
from dj_queue.api import QueueInfo
|
|
17
17
|
from dj_queue.config import load_backend_config
|
|
18
|
-
from dj_queue.db import get_database_alias
|
|
18
|
+
from dj_queue.db import database_capabilities, get_database_alias
|
|
19
19
|
from dj_queue.models import (
|
|
20
20
|
BlockedExecution,
|
|
21
21
|
ClaimedExecution,
|
|
@@ -44,12 +44,14 @@ QUEUE_STATE_LABELS = dict(QUEUE_STATES)
|
|
|
44
44
|
PAGE_SIZE = 100
|
|
45
45
|
OVERVIEW_PAGE_SIZES = {
|
|
46
46
|
"queues": 18,
|
|
47
|
+
"shared_queues": 5,
|
|
47
48
|
"processes": 10,
|
|
48
49
|
"recurring": 12,
|
|
49
50
|
"semaphores": 12,
|
|
50
51
|
}
|
|
51
52
|
OVERVIEW_COUNT_LABELS = {
|
|
52
53
|
"queues": ("queue", "queues"),
|
|
54
|
+
"shared_queues": ("shared queue", "shared queues"),
|
|
53
55
|
"processes": ("process", "processes"),
|
|
54
56
|
"recurring": ("recurring task", "recurring tasks"),
|
|
55
57
|
"semaphores": ("semaphore", "semaphores"),
|
|
@@ -80,6 +82,19 @@ OVERVIEW_SORTS = {
|
|
|
80
82
|
},
|
|
81
83
|
},
|
|
82
84
|
},
|
|
85
|
+
"shared_queues": {
|
|
86
|
+
"default": "name",
|
|
87
|
+
"fields": {
|
|
88
|
+
"name": {"label": "name", "key": "name", "default_desc": False, "css_class": "djq-col-name"},
|
|
89
|
+
"shared_via": {
|
|
90
|
+
"label": "shared via",
|
|
91
|
+
"key": "shared_source_labels",
|
|
92
|
+
"default_desc": False,
|
|
93
|
+
"css_class": "djq-col-shared-via",
|
|
94
|
+
},
|
|
95
|
+
"paused": {"label": "paused", "key": "paused", "default_desc": True},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
83
98
|
"processes": {
|
|
84
99
|
"default": "status",
|
|
85
100
|
"fields": {
|
|
@@ -272,6 +287,7 @@ def dashboard_context(*, backend_alias, query_params=None):
|
|
|
272
287
|
query_params = {}
|
|
273
288
|
|
|
274
289
|
queue_rows = _queue_rows(backend_alias=backend_alias, now=now, process_cutoff=process_cutoff)
|
|
290
|
+
backend_queue_rows, shared_queue_rows = _split_queue_rows(queue_rows)
|
|
275
291
|
process_rows = _process_rows(
|
|
276
292
|
backend_alias=backend_alias,
|
|
277
293
|
now=now,
|
|
@@ -287,7 +303,7 @@ def dashboard_context(*, backend_alias, query_params=None):
|
|
|
287
303
|
"queue_database_alias": queue_database_alias,
|
|
288
304
|
"summary_cards": _summary_cards(
|
|
289
305
|
backend_alias=backend_alias,
|
|
290
|
-
queue_rows=
|
|
306
|
+
queue_rows=backend_queue_rows,
|
|
291
307
|
process_rows=process_rows,
|
|
292
308
|
recurring_rows=recurring_rows,
|
|
293
309
|
semaphore_rows=semaphore_rows,
|
|
@@ -300,13 +316,22 @@ def dashboard_context(*, backend_alias, query_params=None):
|
|
|
300
316
|
),
|
|
301
317
|
"queue_section": _overview_section(
|
|
302
318
|
section="queues",
|
|
303
|
-
rows=
|
|
319
|
+
rows=backend_queue_rows,
|
|
304
320
|
page_param="queues_page",
|
|
305
321
|
page_size=OVERVIEW_PAGE_SIZES["queues"],
|
|
306
322
|
sort_param="queues_sort",
|
|
307
323
|
query_params=query_params,
|
|
308
324
|
anchor="queue-summary",
|
|
309
325
|
),
|
|
326
|
+
"shared_queue_section": _overview_section(
|
|
327
|
+
section="shared_queues",
|
|
328
|
+
rows=shared_queue_rows,
|
|
329
|
+
page_param="shared_queues_page",
|
|
330
|
+
page_size=OVERVIEW_PAGE_SIZES["shared_queues"],
|
|
331
|
+
sort_param="shared_queues_sort",
|
|
332
|
+
query_params=query_params,
|
|
333
|
+
anchor="shared-queue-summary",
|
|
334
|
+
),
|
|
310
335
|
"process_section": _overview_section(
|
|
311
336
|
section="processes",
|
|
312
337
|
rows=process_rows,
|
|
@@ -360,7 +385,14 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
360
385
|
paginator = Paginator(jobs, PAGE_SIZE)
|
|
361
386
|
page_obj = paginator.get_page(query_params.get("page", page_number))
|
|
362
387
|
queue_info = QueueInfo(queue_name, backend_alias=backend_alias)
|
|
388
|
+
queue_paused = queue_info.paused
|
|
389
|
+
queue_latency_seconds = None if queue_paused else queue_info.latency
|
|
363
390
|
state_counts = _queue_state_counts(backend_alias=backend_alias, queue_name=queue_name)
|
|
391
|
+
live_workers = [
|
|
392
|
+
process
|
|
393
|
+
for process in Process.objects.using(alias).filter(kind="Worker")
|
|
394
|
+
if process.last_heartbeat_at >= process_cutoff
|
|
395
|
+
]
|
|
364
396
|
state_tabs = [
|
|
365
397
|
{
|
|
366
398
|
"name": state_name,
|
|
@@ -374,7 +406,7 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
374
406
|
if sum(state_counts.values()):
|
|
375
407
|
raw_links.append(
|
|
376
408
|
{
|
|
377
|
-
"label": "
|
|
409
|
+
"label": "Raw jobs",
|
|
378
410
|
"url": _job_changelist_url(
|
|
379
411
|
backend_alias,
|
|
380
412
|
queue_name=queue_name,
|
|
@@ -385,7 +417,7 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
385
417
|
if state_counts["failed"]:
|
|
386
418
|
raw_links.append(
|
|
387
419
|
{
|
|
388
|
-
"label": "
|
|
420
|
+
"label": "Failed executions",
|
|
389
421
|
"url": _failed_execution_changelist_url(
|
|
390
422
|
backend_alias,
|
|
391
423
|
job__queue_name=queue_name,
|
|
@@ -400,7 +432,13 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
400
432
|
"queue_database_alias": alias,
|
|
401
433
|
"queue_name": queue_name,
|
|
402
434
|
"queue_info": queue_info,
|
|
403
|
-
"queue_paused":
|
|
435
|
+
"queue_paused": queue_paused,
|
|
436
|
+
"queue_latency_seconds": queue_latency_seconds,
|
|
437
|
+
"queue_worker_count": sum(
|
|
438
|
+
1
|
|
439
|
+
for worker in live_workers
|
|
440
|
+
if _queue_matches_selectors(queue_name, worker.metadata.get("queues", []))
|
|
441
|
+
),
|
|
404
442
|
"state": state,
|
|
405
443
|
"state_label": QUEUE_STATE_LABELS[state],
|
|
406
444
|
"state_tabs": state_tabs,
|
|
@@ -448,6 +486,9 @@ def apply_queue_action(*, backend_alias, queue_name, action):
|
|
|
448
486
|
|
|
449
487
|
|
|
450
488
|
def apply_job_action(*, backend_alias, queue_name, state, action, job_ids):
|
|
489
|
+
if not action:
|
|
490
|
+
raise ValueError("No action selected.")
|
|
491
|
+
|
|
451
492
|
if not job_ids:
|
|
452
493
|
raise ValueError("select at least one job")
|
|
453
494
|
|
|
@@ -557,17 +598,42 @@ def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_rows, s
|
|
|
557
598
|
)
|
|
558
599
|
|
|
559
600
|
|
|
601
|
+
def _split_queue_rows(queue_rows):
|
|
602
|
+
backend_queue_rows = []
|
|
603
|
+
shared_queue_rows = []
|
|
604
|
+
for row in queue_rows:
|
|
605
|
+
if row["has_backend_jobs"]:
|
|
606
|
+
backend_queue_rows.append(row)
|
|
607
|
+
continue
|
|
608
|
+
shared_queue_rows.append(row)
|
|
609
|
+
return backend_queue_rows, shared_queue_rows
|
|
610
|
+
|
|
611
|
+
|
|
560
612
|
def _backend_facts(*, config, queue_database_alias, recurring_count, semaphore_count):
|
|
561
613
|
retention = "disabled"
|
|
562
614
|
if config.clear_finished_jobs_after is not None:
|
|
563
615
|
retention = f"{config.clear_finished_jobs_after}s"
|
|
564
616
|
|
|
617
|
+
capabilities = database_capabilities(queue_database_alias)
|
|
618
|
+
|
|
565
619
|
return (
|
|
566
620
|
{"label": "mode", "value": config.mode},
|
|
567
621
|
{"label": "queue db", "value": queue_database_alias},
|
|
568
622
|
{"label": "scheduler", "value": "enabled" if config.has_scheduler_work else "disabled"},
|
|
569
|
-
{
|
|
570
|
-
|
|
623
|
+
{
|
|
624
|
+
"label": "notify",
|
|
625
|
+
"value": _capability_fact_value(
|
|
626
|
+
enabled=config.listen_notify,
|
|
627
|
+
supported=capabilities.supports_listen_notify,
|
|
628
|
+
),
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
"label": "skip locked",
|
|
632
|
+
"value": _capability_fact_value(
|
|
633
|
+
enabled=config.use_skip_locked,
|
|
634
|
+
supported=capabilities.supports_skip_locked,
|
|
635
|
+
),
|
|
636
|
+
},
|
|
571
637
|
{"label": "heartbeat", "value": f"{config.process_alive_threshold}s"},
|
|
572
638
|
{"label": "retention", "value": retention},
|
|
573
639
|
{"label": "recurring", "value": str(recurring_count)},
|
|
@@ -575,6 +641,14 @@ def _backend_facts(*, config, queue_database_alias, recurring_count, semaphore_c
|
|
|
575
641
|
)
|
|
576
642
|
|
|
577
643
|
|
|
644
|
+
def _capability_fact_value(*, enabled, supported):
|
|
645
|
+
if not supported:
|
|
646
|
+
return "unsupported"
|
|
647
|
+
if enabled:
|
|
648
|
+
return "on"
|
|
649
|
+
return "off"
|
|
650
|
+
|
|
651
|
+
|
|
578
652
|
def _overview_section(*, section, rows, page_param, page_size, sort_param, query_params, anchor):
|
|
579
653
|
raw_sort = query_params.get(sort_param)
|
|
580
654
|
sort, explicit_sort = _resolve_overview_sort(section=section, raw_sort=raw_sort)
|
|
@@ -1163,24 +1237,49 @@ def _queue_rows(*, backend_alias, now, process_cutoff):
|
|
|
1163
1237
|
|
|
1164
1238
|
rows = []
|
|
1165
1239
|
for queue_name in sorted(queue_names):
|
|
1240
|
+
paused = queue_name in paused_queues
|
|
1166
1241
|
oldest_ready_at = oldest_ready.get(queue_name)
|
|
1167
1242
|
latency_seconds = None
|
|
1168
|
-
if oldest_ready_at is not None:
|
|
1243
|
+
if oldest_ready_at is not None and paused is False:
|
|
1169
1244
|
latency_seconds = max((now - oldest_ready_at).total_seconds(), 0.0)
|
|
1170
1245
|
|
|
1246
|
+
ready_count = ready_counts.get(queue_name, 0)
|
|
1247
|
+
claimed_count = claimed_counts.get(queue_name, 0)
|
|
1248
|
+
scheduled_count = scheduled_counts.get(queue_name, 0)
|
|
1249
|
+
blocked_count = blocked_counts.get(queue_name, 0)
|
|
1250
|
+
failed_count = failed_counts.get(queue_name, 0)
|
|
1251
|
+
finished_count = finished_counts.get(queue_name, 0)
|
|
1252
|
+
shared_sources = []
|
|
1253
|
+
if paused:
|
|
1254
|
+
shared_sources.append("pause")
|
|
1255
|
+
if queue_name in recurring_queues:
|
|
1256
|
+
shared_sources.append("recurring task")
|
|
1257
|
+
|
|
1171
1258
|
rows.append(
|
|
1172
1259
|
{
|
|
1173
1260
|
"name": queue_name,
|
|
1174
|
-
"ready_count":
|
|
1175
|
-
"claimed_count":
|
|
1176
|
-
"scheduled_count":
|
|
1177
|
-
"blocked_count":
|
|
1178
|
-
"failed_count":
|
|
1179
|
-
"finished_count":
|
|
1180
|
-
"paused":
|
|
1261
|
+
"ready_count": ready_count,
|
|
1262
|
+
"claimed_count": claimed_count,
|
|
1263
|
+
"scheduled_count": scheduled_count,
|
|
1264
|
+
"blocked_count": blocked_count,
|
|
1265
|
+
"failed_count": failed_count,
|
|
1266
|
+
"finished_count": finished_count,
|
|
1267
|
+
"paused": paused,
|
|
1181
1268
|
"latency_seconds": latency_seconds,
|
|
1182
1269
|
"oldest_scheduled_at": oldest_scheduled.get(queue_name),
|
|
1183
1270
|
"oldest_blocked_at": oldest_blocked.get(queue_name),
|
|
1271
|
+
"has_backend_jobs": any(
|
|
1272
|
+
(
|
|
1273
|
+
ready_count,
|
|
1274
|
+
claimed_count,
|
|
1275
|
+
scheduled_count,
|
|
1276
|
+
blocked_count,
|
|
1277
|
+
failed_count,
|
|
1278
|
+
finished_count,
|
|
1279
|
+
)
|
|
1280
|
+
),
|
|
1281
|
+
"shared_sources": tuple(shared_sources),
|
|
1282
|
+
"shared_source_labels": ", ".join(shared_sources),
|
|
1184
1283
|
"live_worker_count": sum(
|
|
1185
1284
|
1
|
|
1186
1285
|
for worker in live_workers
|
|
@@ -354,6 +354,23 @@ def retry_failed_job(job_id, *, backend_alias="default"):
|
|
|
354
354
|
return job
|
|
355
355
|
|
|
356
356
|
|
|
357
|
+
def enqueue_job_again(job_id, *, backend_alias="default"):
|
|
358
|
+
alias = get_database_alias(backend_alias)
|
|
359
|
+
source_job = Job.objects.using(alias).get(pk=job_id)
|
|
360
|
+
task = import_string(source_job.task_path)
|
|
361
|
+
if hasattr(task, "using"):
|
|
362
|
+
task = task.using(
|
|
363
|
+
priority=source_job.priority,
|
|
364
|
+
queue_name=source_job.queue_name,
|
|
365
|
+
run_after=source_job.scheduled_at,
|
|
366
|
+
backend=source_job.backend_name,
|
|
367
|
+
)
|
|
368
|
+
args = list(source_job.payload.get("args", []))
|
|
369
|
+
kwargs = dict(source_job.payload.get("kwargs", {}))
|
|
370
|
+
job, _ = enqueue_job_with_dispatch(task, args, kwargs, backend_alias=source_job.backend_name)
|
|
371
|
+
return job
|
|
372
|
+
|
|
373
|
+
|
|
357
374
|
def discard_failed_job(job_id, *, backend_alias="default"):
|
|
358
375
|
alias = get_database_alias(backend_alias)
|
|
359
376
|
queryset = Job.objects.using(alias).filter(pk=job_id, failed_execution__isnull=False)
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<div class="breadcrumbs">
|
|
47
47
|
<a href='{% url "admin:index" %}'>{% translate "Home" %}</a>
|
|
48
48
|
› <a href="{{ dashboard_url }}">dj_queue</a>
|
|
49
|
-
› {% if has_view_permission %}<a href="{
|
|
49
|
+
› {% if has_view_permission %}<a href="{{ changelist_url }}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
|
|
50
50
|
› {% if add %}{% blocktranslate with name=opts.verbose_name %}Add {{ name }}{% endblocktranslate %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
|
51
51
|
</div>
|
|
52
52
|
{% endblock breadcrumbs %}
|
|
@@ -332,6 +332,27 @@
|
|
|
332
332
|
min-width: 48rem;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
.djq-table-shared-queues {
|
|
336
|
+
min-width: 56rem;
|
|
337
|
+
table-layout: fixed;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.djq-table-shared-queues col.djq-shared-col-name {
|
|
341
|
+
width: 20%;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.djq-table-shared-queues col.djq-shared-col-via {
|
|
345
|
+
width: 20%;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.djq-table-shared-queues col.djq-shared-col-paused {
|
|
349
|
+
width: 20%;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.djq-table-shared-queues col.djq-shared-col-controls {
|
|
353
|
+
width: 20%;
|
|
354
|
+
}
|
|
355
|
+
|
|
335
356
|
.djq-table th,
|
|
336
357
|
.djq-table td {
|
|
337
358
|
white-space: nowrap;
|
|
@@ -367,6 +388,15 @@
|
|
|
367
388
|
white-space: normal;
|
|
368
389
|
}
|
|
369
390
|
|
|
391
|
+
.djq-col-shared-via {
|
|
392
|
+
min-width: 16rem;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.djq-table-shared-queues .djq-col-controls,
|
|
396
|
+
.djq-table-shared-queues td.djq-col-controls {
|
|
397
|
+
min-width: 13rem;
|
|
398
|
+
}
|
|
399
|
+
|
|
370
400
|
.djq-process-row-group {
|
|
371
401
|
background: var(--selected-bg);
|
|
372
402
|
}
|
|
@@ -435,6 +465,26 @@
|
|
|
435
465
|
line-height: 1.45;
|
|
436
466
|
}
|
|
437
467
|
|
|
468
|
+
.djq-reason-list {
|
|
469
|
+
display: flex;
|
|
470
|
+
gap: 0.45rem;
|
|
471
|
+
flex-wrap: wrap;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.djq-reason-pill {
|
|
475
|
+
display: inline-flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
min-height: 1.6rem;
|
|
478
|
+
padding: 0 0.6rem;
|
|
479
|
+
border: 1px solid var(--hairline-color);
|
|
480
|
+
border-radius: 999px;
|
|
481
|
+
background: var(--body-bg);
|
|
482
|
+
color: var(--body-quiet-color);
|
|
483
|
+
font-size: 0.76rem;
|
|
484
|
+
font-weight: 600;
|
|
485
|
+
line-height: 1;
|
|
486
|
+
}
|
|
487
|
+
|
|
438
488
|
@media (max-width: 900px) {
|
|
439
489
|
.djq-summary-grid {
|
|
440
490
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -475,7 +525,7 @@
|
|
|
475
525
|
<form method="get" action="{% url 'admin:dj_queue_dashboard_changelist' %}">
|
|
476
526
|
<select id="id_backend" name="backend" onchange="this.form.submit()">
|
|
477
527
|
{% for choice in backend_choices %}
|
|
478
|
-
<option value="{{ choice.alias }}"{% if choice.alias == backend_alias %} selected{% endif %}>{{ choice.alias }}
|
|
528
|
+
<option value="{{ choice.alias }}"{% if choice.alias == backend_alias %} selected{% endif %}>{{ choice.alias }}</option>
|
|
479
529
|
{% endfor %}
|
|
480
530
|
</select>
|
|
481
531
|
<noscript><button type="submit" class="button">Switch</button></noscript>
|
|
@@ -521,7 +571,7 @@
|
|
|
521
571
|
|
|
522
572
|
<section class="module djq-section" id="queue-summary">
|
|
523
573
|
<div class="djq-section-banner"><h2>queues</h2></div>
|
|
524
|
-
<p class="djq-section-copy">Live queue pressure, pause state, and first-line controls for this backend
|
|
574
|
+
<p class="djq-section-copy">Live queue pressure, pause state, and first-line controls for this backend.{% if shared_queue_section.total_count %} Shared pause or recurring-only queues are available in a separate section below.{% endif %}</p>
|
|
525
575
|
{% if queue_section.rows %}
|
|
526
576
|
<div class="djq-table-wrap">
|
|
527
577
|
<table class="djq-table djq-table-queues" id="result_list">
|
|
@@ -800,6 +850,88 @@
|
|
|
800
850
|
{% endif %}
|
|
801
851
|
</section>
|
|
802
852
|
|
|
853
|
+
{% if shared_queue_section.total_count %}
|
|
854
|
+
<section class="module djq-section" id="shared-queue-summary">
|
|
855
|
+
<div class="djq-section-banner"><h2>shared queues</h2></div>
|
|
856
|
+
<p class="djq-section-copy">These queues have no jobs for backend <strong>{{ backend_alias }}</strong> and are shown separately because pause and recurring-task rows are shared on queue database <strong>{{ queue_database_alias }}</strong>.</p>
|
|
857
|
+
<div class="djq-table-wrap">
|
|
858
|
+
<table class="djq-table djq-table-shared-queues">
|
|
859
|
+
<colgroup>
|
|
860
|
+
<col class="djq-shared-col-name">
|
|
861
|
+
<col class="djq-shared-col-via">
|
|
862
|
+
<col class="djq-shared-col-paused">
|
|
863
|
+
<col class="djq-shared-col-controls">
|
|
864
|
+
</colgroup>
|
|
865
|
+
<thead>
|
|
866
|
+
<tr>
|
|
867
|
+
{% for header in shared_queue_section.headers %}
|
|
868
|
+
<th scope="col"{{ header.class_attrib|safe }}>
|
|
869
|
+
{% if header.sortable and header.sorted %}
|
|
870
|
+
<div class="sortoptions">
|
|
871
|
+
<a class="sortremove" href="{{ header.url_remove }}" title="Remove from sorting"></a>
|
|
872
|
+
{% if shared_queue_section.num_sorted_fields > 1 %}<span class="sortpriority" title="Sorting priority: {{ header.sort_priority }}">{{ header.sort_priority }}</span>{% endif %}
|
|
873
|
+
<a href="{{ header.url_toggle }}" class="toggle {{ header.ascending|yesno:'ascending,descending' }}" title="Toggle sorting"></a>
|
|
874
|
+
</div>
|
|
875
|
+
{% endif %}
|
|
876
|
+
<div class="text"><a role="button" href="{{ header.url_primary }}">{{ header.text|capfirst }}</a></div>
|
|
877
|
+
<div class="clear"></div>
|
|
878
|
+
</th>
|
|
879
|
+
{% endfor %}
|
|
880
|
+
<th scope="col" class="djq-col-controls"><div class="text">Controls</div></th>
|
|
881
|
+
</tr>
|
|
882
|
+
</thead>
|
|
883
|
+
<tbody>
|
|
884
|
+
{% for queue in shared_queue_section.rows %}
|
|
885
|
+
<tr class="{% cycle 'row1' 'row2' %}">
|
|
886
|
+
<th class="djq-col-name"><a href="{% url 'admin:dj_queue_dashboard_queue' queue.name %}?backend={{ backend_alias }}&state=ready">{{ queue.name }}</a></th>
|
|
887
|
+
<td>
|
|
888
|
+
<div class="djq-reason-list">
|
|
889
|
+
{% for source in queue.shared_sources %}
|
|
890
|
+
<span class="djq-reason-pill">{{ source }}</span>
|
|
891
|
+
{% endfor %}
|
|
892
|
+
</div>
|
|
893
|
+
</td>
|
|
894
|
+
<td>{{ queue.paused|yesno:"yes,no" }}</td>
|
|
895
|
+
<td class="djq-col-controls">
|
|
896
|
+
<div class="djq-queue-controls">
|
|
897
|
+
<form method="post" action="{% url 'admin:dj_queue_dashboard_queue_action' queue.name %}">
|
|
898
|
+
{% csrf_token %}
|
|
899
|
+
<input type="hidden" name="backend" value="{{ backend_alias }}">
|
|
900
|
+
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
|
901
|
+
<input type="hidden" name="action" value="{% if queue.paused %}resume{% else %}pause{% endif %}">
|
|
902
|
+
<button type="submit" class="button {% if queue.paused %}djq-button-resume{% else %}djq-button-pause{% endif %}">{% if queue.paused %}resume{% else %}pause{% endif %}</button>
|
|
903
|
+
</form>
|
|
904
|
+
</div>
|
|
905
|
+
</td>
|
|
906
|
+
</tr>
|
|
907
|
+
{% endfor %}
|
|
908
|
+
</tbody>
|
|
909
|
+
</table>
|
|
910
|
+
</div>
|
|
911
|
+
<div class="djq-table-footer">
|
|
912
|
+
<nav class="paginator" aria-labelledby="pagination-shared-queues">
|
|
913
|
+
<h2 id="pagination-shared-queues" class="visually-hidden">Pagination shared queues</h2>
|
|
914
|
+
{% if shared_queue_section.pagination_required %}
|
|
915
|
+
<ul>
|
|
916
|
+
{% for link in shared_queue_section.page_links %}
|
|
917
|
+
<li>
|
|
918
|
+
{% if link.is_ellipsis %}
|
|
919
|
+
{{ link.label }}
|
|
920
|
+
{% elif link.is_current %}
|
|
921
|
+
<span aria-current="page">{{ link.number }}</span>
|
|
922
|
+
{% else %}
|
|
923
|
+
<a role="button" href="{{ link.url }}">{{ link.number }}</a>
|
|
924
|
+
{% endif %}
|
|
925
|
+
</li>
|
|
926
|
+
{% endfor %}
|
|
927
|
+
</ul>
|
|
928
|
+
{% endif %}
|
|
929
|
+
{{ shared_queue_section.result_count_text }}
|
|
930
|
+
</nav>
|
|
931
|
+
</div>
|
|
932
|
+
</section>
|
|
933
|
+
{% endif %}
|
|
934
|
+
|
|
803
935
|
<section class="module djq-section" id="raw-summary">
|
|
804
936
|
<div class="djq-section-banner"><h2>inspect raw rows</h2></div>
|
|
805
937
|
<p class="djq-section-copy">Use the model changelists for exhaustive filtering, lower-level debugging, and record-by-record inspection.</p>
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
margin: 0;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
#toolbar .queue-toolbar-meta strong {
|
|
59
|
+
text-transform: uppercase;
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
#toolbar .queue-toolbar-actions button {
|
|
59
63
|
border: none;
|
|
60
64
|
border-radius: 15px;
|
|
@@ -210,8 +214,11 @@
|
|
|
210
214
|
<div id="toolbar">
|
|
211
215
|
<div class="queue-toolbar-row">
|
|
212
216
|
<div class="queue-toolbar-meta">
|
|
213
|
-
<span><strong>
|
|
214
|
-
<span><strong>
|
|
217
|
+
<span><strong>Backend:</strong> {{ backend_alias }}</span>
|
|
218
|
+
<span><strong>Database:</strong> {{ queue_database_alias }}</span>
|
|
219
|
+
<span><strong>Workers:</strong> {{ queue_worker_count }}</span>
|
|
220
|
+
<span><strong>Latency:</strong> {% if queue_latency_seconds != None %}{{ queue_latency_seconds|floatformat:1 }}s{% else %}-{% endif %}</span>
|
|
221
|
+
<span><strong>Paused:</strong> {{ queue_paused|yesno:"yes,no" }}</span>
|
|
215
222
|
</div>
|
|
216
223
|
<div class="queue-toolbar-actions">
|
|
217
224
|
<form method="post" action="{% url 'admin:dj_queue_dashboard_queue_action' queue_name %}">
|
|
@@ -265,6 +272,7 @@
|
|
|
265
272
|
<div class="actions">
|
|
266
273
|
<label for="action">Action:
|
|
267
274
|
<select name="action" id="action">
|
|
275
|
+
<option value="" selected>---------</option>
|
|
268
276
|
{% for action in job_actions %}
|
|
269
277
|
<option value="{{ action.name }}">{{ action.label }}</option>
|
|
270
278
|
{% endfor %}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.2.2 → dj_queue-0.2.4}/dj_queue/migrations/0003_recurringtask_recurringexecution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|