dj-queue 0.2.1__tar.gz → 0.2.3__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.1 → dj_queue-0.2.3}/PKG-INFO +26 -10
- {dj_queue-0.2.1 → dj_queue-0.2.3}/README.md +25 -9
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/admin.py +101 -8
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/dashboard.py +99 -14
- dj_queue-0.2.3/dj_queue/templates/admin/dj_queue/change_form.html +69 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/templates/admin/dj_queue/dashboard.html +140 -8
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/templates/admin/dj_queue/queue_jobs.html +19 -4
- {dj_queue-0.2.1 → dj_queue-0.2.3}/pyproject.toml +1 -1
- dj_queue-0.2.1/dj_queue/templates/admin/dj_queue/change_form.html +0 -12
- {dj_queue-0.2.1 → dj_queue-0.2.3}/LICENSE +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/api.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/apps.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/backend.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/config.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/db.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/hooks.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/log.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/operations/concurrency.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/operations/jobs.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/routers.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/base.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.2.1 → dj_queue-0.2.3}/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.3
|
|
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,10 +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
|
+
[](https://djangopackages.org/packages/p/dj-queue/)
|
|
31
32
|

|
|
32
33
|

|
|
33
|
-

|
|
34
|
+
[](https://github.com/coriocactus/dj_queue/blob/main/LICENSE)
|
|
34
35
|
|
|
35
36
|
`dj_queue` is a database-backed task queue backend for the `django.tasks` framework.
|
|
36
37
|
|
|
@@ -58,7 +59,7 @@ It has a narrow, explicit shape:
|
|
|
58
59
|
- polling remains the correctness path on every supported database
|
|
59
60
|
|
|
60
61
|
For detailed comparisons with Celery, RQ, Procrastinate, and other alternatives,
|
|
61
|
-
see [COMPARISONS.md](COMPARISONS.md).
|
|
62
|
+
see [COMPARISONS.md](docs/COMPARISONS.md).
|
|
62
63
|
|
|
63
64
|
## Installation
|
|
64
65
|
|
|
@@ -155,6 +156,25 @@ print(fresh_result.return_value)
|
|
|
155
156
|
|
|
156
157
|
When the worker has executed the job, `fresh_result.return_value` will be `10`.
|
|
157
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
|
+
- failed-job actions: retry and discard from list and detail views
|
|
168
|
+
- queue drill-down pages for state-specific inspection
|
|
169
|
+
|
|
170
|
+
**Dashboard overview**
|
|
171
|
+
|
|
172
|
+

|
|
173
|
+
|
|
174
|
+
**Queue drill-down**
|
|
175
|
+
|
|
176
|
+

|
|
177
|
+
|
|
158
178
|
## Common Patterns
|
|
159
179
|
|
|
160
180
|
### Scheduled jobs
|
|
@@ -405,17 +425,13 @@ python manage.py dj_queue_prune --older-than 86400
|
|
|
405
425
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
406
426
|
```
|
|
407
427
|
|
|
408
|
-
If Django admin is installed, `dj_queue` also registers the main operational
|
|
409
|
-
models there, including jobs, failed executions, processes, recurring tasks,
|
|
410
|
-
pauses, and semaphores.
|
|
411
|
-
|
|
412
428
|
## Failed Jobs
|
|
413
429
|
|
|
414
430
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
415
431
|
queue database, including the exception class, message, and traceback.
|
|
416
432
|
|
|
417
|
-
You can retry failed jobs through Django admin, or
|
|
418
|
-
through the operations layer:
|
|
433
|
+
You can retry and discard failed jobs through Django admin, or call the same
|
|
434
|
+
operations directly through the operations layer:
|
|
419
435
|
|
|
420
436
|
```python
|
|
421
437
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -1,10 +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
|
+
[](https://djangopackages.org/packages/p/dj-queue/)
|
|
5
6
|

|
|
6
7
|

|
|
7
|
-

|
|
8
|
+
[](https://github.com/coriocactus/dj_queue/blob/main/LICENSE)
|
|
8
9
|
|
|
9
10
|
`dj_queue` is a database-backed task queue backend for the `django.tasks` framework.
|
|
10
11
|
|
|
@@ -32,7 +33,7 @@ It has a narrow, explicit shape:
|
|
|
32
33
|
- polling remains the correctness path on every supported database
|
|
33
34
|
|
|
34
35
|
For detailed comparisons with Celery, RQ, Procrastinate, and other alternatives,
|
|
35
|
-
see [COMPARISONS.md](COMPARISONS.md).
|
|
36
|
+
see [COMPARISONS.md](docs/COMPARISONS.md).
|
|
36
37
|
|
|
37
38
|
## Installation
|
|
38
39
|
|
|
@@ -129,6 +130,25 @@ print(fresh_result.return_value)
|
|
|
129
130
|
|
|
130
131
|
When the worker has executed the job, `fresh_result.return_value` will be `10`.
|
|
131
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
|
+
- failed-job actions: retry and discard from list and detail views
|
|
142
|
+
- queue drill-down pages for state-specific inspection
|
|
143
|
+
|
|
144
|
+
**Dashboard overview**
|
|
145
|
+
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
**Queue drill-down**
|
|
149
|
+
|
|
150
|
+

|
|
151
|
+
|
|
132
152
|
## Common Patterns
|
|
133
153
|
|
|
134
154
|
### Scheduled jobs
|
|
@@ -379,17 +399,13 @@ python manage.py dj_queue_prune --older-than 86400
|
|
|
379
399
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
380
400
|
```
|
|
381
401
|
|
|
382
|
-
If Django admin is installed, `dj_queue` also registers the main operational
|
|
383
|
-
models there, including jobs, failed executions, processes, recurring tasks,
|
|
384
|
-
pauses, and semaphores.
|
|
385
|
-
|
|
386
402
|
## Failed Jobs
|
|
387
403
|
|
|
388
404
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
389
405
|
queue database, including the exception class, message, and traceback.
|
|
390
406
|
|
|
391
|
-
You can retry failed jobs through Django admin, or
|
|
392
|
-
through the operations layer:
|
|
407
|
+
You can retry and discard failed jobs through Django admin, or call the same
|
|
408
|
+
operations directly through the operations layer:
|
|
393
409
|
|
|
394
410
|
```python
|
|
395
411
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -79,6 +79,7 @@ class DashboardAdmin(admin.ModelAdmin):
|
|
|
79
79
|
context = {
|
|
80
80
|
**self.admin_site.each_context(request),
|
|
81
81
|
**dashboard.dashboard_context(backend_alias=backend_alias, query_params=request.GET),
|
|
82
|
+
"title": "dj_queue",
|
|
82
83
|
}
|
|
83
84
|
if extra_context:
|
|
84
85
|
context.update(extra_context)
|
|
@@ -111,16 +112,19 @@ class DashboardAdmin(admin.ModelAdmin):
|
|
|
111
112
|
backend_alias = dashboard.resolve_backend_alias(request.GET.get("backend"))
|
|
112
113
|
state = request.GET.get("state", "ready")
|
|
113
114
|
page_number = request.GET.get("page", 1)
|
|
115
|
+
queue_context = dashboard.queue_page_context(
|
|
116
|
+
backend_alias=backend_alias,
|
|
117
|
+
queue_name=queue_name,
|
|
118
|
+
state=state,
|
|
119
|
+
page_number=page_number,
|
|
120
|
+
query_params=request.GET,
|
|
121
|
+
)
|
|
114
122
|
context = {
|
|
115
123
|
**self.admin_site.each_context(request),
|
|
116
|
-
**
|
|
117
|
-
backend_alias=backend_alias,
|
|
118
|
-
queue_name=queue_name,
|
|
119
|
-
state=state,
|
|
120
|
-
page_number=page_number,
|
|
121
|
-
query_params=request.GET,
|
|
122
|
-
),
|
|
124
|
+
**queue_context,
|
|
123
125
|
"job_actions": dashboard.job_actions_for_state(state),
|
|
126
|
+
"title": "dj_queue",
|
|
127
|
+
"subtitle": queue_name,
|
|
124
128
|
}
|
|
125
129
|
return TemplateResponse(request, "admin/dj_queue/queue_jobs.html", context)
|
|
126
130
|
|
|
@@ -201,7 +205,17 @@ class HiddenSidebarAdminMixin:
|
|
|
201
205
|
return super().changelist_view(request, extra_context=extra_context)
|
|
202
206
|
|
|
203
207
|
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
|
|
204
|
-
|
|
208
|
+
obj = self.get_object(request, object_id) if object_id is not None else None
|
|
209
|
+
if request.method == "POST":
|
|
210
|
+
action = request.POST.get("_djq_object_action")
|
|
211
|
+
if action and obj is not None:
|
|
212
|
+
return self.handle_change_action(request, obj, action)
|
|
213
|
+
|
|
214
|
+
extra_context = {
|
|
215
|
+
**(extra_context or {}),
|
|
216
|
+
"dashboard_url": self._dashboard_url(request),
|
|
217
|
+
"change_actions": self.get_change_actions(request, obj),
|
|
218
|
+
}
|
|
205
219
|
return super().changeform_view(
|
|
206
220
|
request,
|
|
207
221
|
object_id=object_id,
|
|
@@ -272,6 +286,23 @@ class HiddenSidebarAdminMixin:
|
|
|
272
286
|
def _dashboard_url(self, request):
|
|
273
287
|
return f"{reverse('admin:dj_queue_dashboard_changelist')}?{urlencode({'backend': self._backend_alias(request)})}"
|
|
274
288
|
|
|
289
|
+
def get_change_actions(self, request, obj):
|
|
290
|
+
return ()
|
|
291
|
+
|
|
292
|
+
def handle_change_action(self, request, obj, action):
|
|
293
|
+
return HttpResponseRedirect(request.get_full_path())
|
|
294
|
+
|
|
295
|
+
def _change_url(self, *, object_id, backend_alias):
|
|
296
|
+
url = reverse(
|
|
297
|
+
f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_change",
|
|
298
|
+
args=[object_id],
|
|
299
|
+
)
|
|
300
|
+
return f"{url}?{urlencode({'backend': backend_alias})}"
|
|
301
|
+
|
|
302
|
+
def _changelist_url(self, *, backend_alias):
|
|
303
|
+
url = reverse(f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_changelist")
|
|
304
|
+
return f"{url}?{urlencode({'backend': backend_alias})}"
|
|
305
|
+
|
|
275
306
|
|
|
276
307
|
class JobStatusListFilter(admin.SimpleListFilter):
|
|
277
308
|
title = "status"
|
|
@@ -442,6 +473,39 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
442
473
|
url = f"{reverse('admin:dj_queue_dashboard_queue', args=[obj.queue_name])}?{urlencode(query)}"
|
|
443
474
|
return format_html('<a href="{}">{}</a>', url, obj.queue_name)
|
|
444
475
|
|
|
476
|
+
def get_change_actions(self, request, obj):
|
|
477
|
+
if obj is None or obj.status != "failed":
|
|
478
|
+
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
|
+
)
|
|
487
|
+
|
|
488
|
+
def handle_change_action(self, request, obj, action):
|
|
489
|
+
if obj.status != "failed":
|
|
490
|
+
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
|
+
)
|
|
494
|
+
|
|
495
|
+
if action == "retry":
|
|
496
|
+
obj.failed_execution.retry()
|
|
497
|
+
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
|
+
)
|
|
501
|
+
|
|
502
|
+
if action == "discard":
|
|
503
|
+
obj.failed_execution.discard()
|
|
504
|
+
self.message_user(request, "Discarded failed job", level=messages.SUCCESS)
|
|
505
|
+
return HttpResponseRedirect(self._changelist_url(backend_alias=obj.backend_name))
|
|
506
|
+
|
|
507
|
+
return HttpResponseRedirect(self._change_url(object_id=obj.pk, backend_alias=obj.backend_name))
|
|
508
|
+
|
|
445
509
|
|
|
446
510
|
@admin.register(FailedExecution)
|
|
447
511
|
class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
@@ -469,6 +533,35 @@ class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
469
533
|
discarded += execution.discard()
|
|
470
534
|
self.message_user(request, f"Discarded {discarded} failed jobs", level=messages.SUCCESS)
|
|
471
535
|
|
|
536
|
+
def get_change_actions(self, request, obj):
|
|
537
|
+
if obj is None:
|
|
538
|
+
return ()
|
|
539
|
+
return (
|
|
540
|
+
{"name": "retry", "label": "Retry failed job", "css_class": "djq-object-action-retry"},
|
|
541
|
+
{
|
|
542
|
+
"name": "discard",
|
|
543
|
+
"label": "Discard failed job",
|
|
544
|
+
"css_class": "djq-object-action-discard",
|
|
545
|
+
},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
def handle_change_action(self, request, obj, action):
|
|
549
|
+
backend_alias = obj.job.backend_name
|
|
550
|
+
|
|
551
|
+
if action == "retry":
|
|
552
|
+
job_id = obj.job_id
|
|
553
|
+
obj.retry()
|
|
554
|
+
self.message_user(request, "Retried failed job", level=messages.SUCCESS)
|
|
555
|
+
url = reverse("admin:dj_queue_job_change", args=[job_id])
|
|
556
|
+
return HttpResponseRedirect(f"{url}?{urlencode({'backend': backend_alias})}")
|
|
557
|
+
|
|
558
|
+
if action == "discard":
|
|
559
|
+
obj.discard()
|
|
560
|
+
self.message_user(request, "Discarded failed job", level=messages.SUCCESS)
|
|
561
|
+
return HttpResponseRedirect(self._changelist_url(backend_alias=backend_alias))
|
|
562
|
+
|
|
563
|
+
return HttpResponseRedirect(self._change_url(object_id=obj.pk, backend_alias=backend_alias))
|
|
564
|
+
|
|
472
565
|
|
|
473
566
|
@admin.register(Process)
|
|
474
567
|
class ProcessAdmin(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,
|
|
@@ -374,7 +399,7 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
374
399
|
if sum(state_counts.values()):
|
|
375
400
|
raw_links.append(
|
|
376
401
|
{
|
|
377
|
-
"label": "
|
|
402
|
+
"label": "Raw jobs",
|
|
378
403
|
"url": _job_changelist_url(
|
|
379
404
|
backend_alias,
|
|
380
405
|
queue_name=queue_name,
|
|
@@ -385,7 +410,7 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
385
410
|
if state_counts["failed"]:
|
|
386
411
|
raw_links.append(
|
|
387
412
|
{
|
|
388
|
-
"label": "
|
|
413
|
+
"label": "Failed executions",
|
|
389
414
|
"url": _failed_execution_changelist_url(
|
|
390
415
|
backend_alias,
|
|
391
416
|
job__queue_name=queue_name,
|
|
@@ -448,6 +473,9 @@ def apply_queue_action(*, backend_alias, queue_name, action):
|
|
|
448
473
|
|
|
449
474
|
|
|
450
475
|
def apply_job_action(*, backend_alias, queue_name, state, action, job_ids):
|
|
476
|
+
if not action:
|
|
477
|
+
raise ValueError("No action selected.")
|
|
478
|
+
|
|
451
479
|
if not job_ids:
|
|
452
480
|
raise ValueError("select at least one job")
|
|
453
481
|
|
|
@@ -550,24 +578,49 @@ def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_rows, s
|
|
|
550
578
|
"detail": f"{live_processes} live, {stale_processes} stale",
|
|
551
579
|
},
|
|
552
580
|
{
|
|
553
|
-
"label": "control
|
|
581
|
+
"label": "control-plane",
|
|
554
582
|
"value": len(recurring_rows) + len(semaphore_rows),
|
|
555
583
|
"detail": f"{len(recurring_rows)} recurring and {len(semaphore_rows)} semaphores",
|
|
556
584
|
},
|
|
557
585
|
)
|
|
558
586
|
|
|
559
587
|
|
|
588
|
+
def _split_queue_rows(queue_rows):
|
|
589
|
+
backend_queue_rows = []
|
|
590
|
+
shared_queue_rows = []
|
|
591
|
+
for row in queue_rows:
|
|
592
|
+
if row["has_backend_jobs"]:
|
|
593
|
+
backend_queue_rows.append(row)
|
|
594
|
+
continue
|
|
595
|
+
shared_queue_rows.append(row)
|
|
596
|
+
return backend_queue_rows, shared_queue_rows
|
|
597
|
+
|
|
598
|
+
|
|
560
599
|
def _backend_facts(*, config, queue_database_alias, recurring_count, semaphore_count):
|
|
561
600
|
retention = "disabled"
|
|
562
601
|
if config.clear_finished_jobs_after is not None:
|
|
563
602
|
retention = f"{config.clear_finished_jobs_after}s"
|
|
564
603
|
|
|
604
|
+
capabilities = database_capabilities(queue_database_alias)
|
|
605
|
+
|
|
565
606
|
return (
|
|
566
607
|
{"label": "mode", "value": config.mode},
|
|
567
608
|
{"label": "queue db", "value": queue_database_alias},
|
|
568
609
|
{"label": "scheduler", "value": "enabled" if config.has_scheduler_work else "disabled"},
|
|
569
|
-
{
|
|
570
|
-
|
|
610
|
+
{
|
|
611
|
+
"label": "notify",
|
|
612
|
+
"value": _capability_fact_value(
|
|
613
|
+
enabled=config.listen_notify,
|
|
614
|
+
supported=capabilities.supports_listen_notify,
|
|
615
|
+
),
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"label": "skip locked",
|
|
619
|
+
"value": _capability_fact_value(
|
|
620
|
+
enabled=config.use_skip_locked,
|
|
621
|
+
supported=capabilities.supports_skip_locked,
|
|
622
|
+
),
|
|
623
|
+
},
|
|
571
624
|
{"label": "heartbeat", "value": f"{config.process_alive_threshold}s"},
|
|
572
625
|
{"label": "retention", "value": retention},
|
|
573
626
|
{"label": "recurring", "value": str(recurring_count)},
|
|
@@ -575,6 +628,14 @@ def _backend_facts(*, config, queue_database_alias, recurring_count, semaphore_c
|
|
|
575
628
|
)
|
|
576
629
|
|
|
577
630
|
|
|
631
|
+
def _capability_fact_value(*, enabled, supported):
|
|
632
|
+
if not supported:
|
|
633
|
+
return "unsupported"
|
|
634
|
+
if enabled:
|
|
635
|
+
return "on"
|
|
636
|
+
return "off"
|
|
637
|
+
|
|
638
|
+
|
|
578
639
|
def _overview_section(*, section, rows, page_param, page_size, sort_param, query_params, anchor):
|
|
579
640
|
raw_sort = query_params.get(sort_param)
|
|
580
641
|
sort, explicit_sort = _resolve_overview_sort(section=section, raw_sort=raw_sort)
|
|
@@ -1168,19 +1229,43 @@ def _queue_rows(*, backend_alias, now, process_cutoff):
|
|
|
1168
1229
|
if oldest_ready_at is not None:
|
|
1169
1230
|
latency_seconds = max((now - oldest_ready_at).total_seconds(), 0.0)
|
|
1170
1231
|
|
|
1232
|
+
ready_count = ready_counts.get(queue_name, 0)
|
|
1233
|
+
claimed_count = claimed_counts.get(queue_name, 0)
|
|
1234
|
+
scheduled_count = scheduled_counts.get(queue_name, 0)
|
|
1235
|
+
blocked_count = blocked_counts.get(queue_name, 0)
|
|
1236
|
+
failed_count = failed_counts.get(queue_name, 0)
|
|
1237
|
+
finished_count = finished_counts.get(queue_name, 0)
|
|
1238
|
+
shared_sources = []
|
|
1239
|
+
if queue_name in paused_queues:
|
|
1240
|
+
shared_sources.append("pause")
|
|
1241
|
+
if queue_name in recurring_queues:
|
|
1242
|
+
shared_sources.append("recurring task")
|
|
1243
|
+
|
|
1171
1244
|
rows.append(
|
|
1172
1245
|
{
|
|
1173
1246
|
"name": queue_name,
|
|
1174
|
-
"ready_count":
|
|
1175
|
-
"claimed_count":
|
|
1176
|
-
"scheduled_count":
|
|
1177
|
-
"blocked_count":
|
|
1178
|
-
"failed_count":
|
|
1179
|
-
"finished_count":
|
|
1247
|
+
"ready_count": ready_count,
|
|
1248
|
+
"claimed_count": claimed_count,
|
|
1249
|
+
"scheduled_count": scheduled_count,
|
|
1250
|
+
"blocked_count": blocked_count,
|
|
1251
|
+
"failed_count": failed_count,
|
|
1252
|
+
"finished_count": finished_count,
|
|
1180
1253
|
"paused": queue_name in paused_queues,
|
|
1181
1254
|
"latency_seconds": latency_seconds,
|
|
1182
1255
|
"oldest_scheduled_at": oldest_scheduled.get(queue_name),
|
|
1183
1256
|
"oldest_blocked_at": oldest_blocked.get(queue_name),
|
|
1257
|
+
"has_backend_jobs": any(
|
|
1258
|
+
(
|
|
1259
|
+
ready_count,
|
|
1260
|
+
claimed_count,
|
|
1261
|
+
scheduled_count,
|
|
1262
|
+
blocked_count,
|
|
1263
|
+
failed_count,
|
|
1264
|
+
finished_count,
|
|
1265
|
+
)
|
|
1266
|
+
),
|
|
1267
|
+
"shared_sources": tuple(shared_sources),
|
|
1268
|
+
"shared_source_labels": ", ".join(shared_sources),
|
|
1184
1269
|
"live_worker_count": sum(
|
|
1185
1270
|
1
|
|
1186
1271
|
for worker in live_workers
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{% extends "admin/change_form.html" %}
|
|
2
|
+
|
|
3
|
+
{% load i18n admin_urls %}
|
|
4
|
+
|
|
5
|
+
{% block extrastyle %}
|
|
6
|
+
{{ block.super }}
|
|
7
|
+
<style>
|
|
8
|
+
.submit-row form {
|
|
9
|
+
display: inline;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.submit-row .djq-object-action {
|
|
13
|
+
border: none;
|
|
14
|
+
border-radius: 4px;
|
|
15
|
+
min-height: 2.1875rem;
|
|
16
|
+
padding: 0.625rem 0.9375rem;
|
|
17
|
+
color: var(--button-fg);
|
|
18
|
+
font-size: 0.75rem;
|
|
19
|
+
line-height: 0.9375rem;
|
|
20
|
+
text-transform: uppercase;
|
|
21
|
+
letter-spacing: 0.5px;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.submit-row .djq-object-action-retry {
|
|
26
|
+
background: var(--button-bg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.submit-row .djq-object-action-retry:hover,
|
|
30
|
+
.submit-row .djq-object-action-retry:focus {
|
|
31
|
+
background: var(--button-hover-bg);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.submit-row .djq-object-action-discard {
|
|
35
|
+
background: var(--delete-button-bg);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.submit-row .djq-object-action-discard:hover,
|
|
39
|
+
.submit-row .djq-object-action-discard:focus {
|
|
40
|
+
background: var(--delete-button-hover-bg);
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
{% endblock extrastyle %}
|
|
44
|
+
|
|
45
|
+
{% block breadcrumbs %}
|
|
46
|
+
<div class="breadcrumbs">
|
|
47
|
+
<a href='{% url "admin:index" %}'>{% translate "Home" %}</a>
|
|
48
|
+
› <a href="{{ dashboard_url }}">dj_queue</a>
|
|
49
|
+
› {% if has_view_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
|
|
50
|
+
› {% if add %}{% blocktranslate with name=opts.verbose_name %}Add {{ name }}{% endblocktranslate %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
|
51
|
+
</div>
|
|
52
|
+
{% endblock breadcrumbs %}
|
|
53
|
+
|
|
54
|
+
{% block object-tools %}{% endblock object-tools %}
|
|
55
|
+
|
|
56
|
+
{% block submit_buttons_top %}{% endblock submit_buttons_top %}
|
|
57
|
+
|
|
58
|
+
{% block submit_buttons_bottom %}
|
|
59
|
+
{% if change_actions %}
|
|
60
|
+
<div class="submit-row">
|
|
61
|
+
{% for action in change_actions %}
|
|
62
|
+
<form method="post" action="{{ request.get_full_path }}">
|
|
63
|
+
{% csrf_token %}
|
|
64
|
+
<button type="submit" name="_djq_object_action" value="{{ action.name }}" class="djq-object-action {{ action.css_class }}">{{ action.label }}</button>
|
|
65
|
+
</form>
|
|
66
|
+
{% endfor %}
|
|
67
|
+
</div>
|
|
68
|
+
{% endif %}
|
|
69
|
+
{% endblock submit_buttons_bottom %}
|
|
@@ -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>
|
|
@@ -810,9 +942,9 @@
|
|
|
810
942
|
<p>Job-centric rows scoped to the selected backend where possible.</p>
|
|
811
943
|
</div>
|
|
812
944
|
<div class="djq-raw-links">
|
|
813
|
-
<a href="{% url 'admin:dj_queue_job_changelist' %}?backend={{ backend_alias }}">
|
|
814
|
-
<a href="{% url 'admin:dj_queue_failedexecution_changelist' %}?backend={{ backend_alias }}">
|
|
815
|
-
<a href="{% url 'admin:dj_queue_pause_changelist' %}?backend={{ backend_alias }}">
|
|
945
|
+
<a href="{% url 'admin:dj_queue_job_changelist' %}?backend={{ backend_alias }}">Jobs</a>
|
|
946
|
+
<a href="{% url 'admin:dj_queue_failedexecution_changelist' %}?backend={{ backend_alias }}">Failed executions</a>
|
|
947
|
+
<a href="{% url 'admin:dj_queue_pause_changelist' %}?backend={{ backend_alias }}">Pauses</a>
|
|
816
948
|
</div>
|
|
817
949
|
</div>
|
|
818
950
|
|
|
@@ -822,9 +954,9 @@
|
|
|
822
954
|
<p>Control-plane rows scoped by the selected backend's queue database alias.</p>
|
|
823
955
|
</div>
|
|
824
956
|
<div class="djq-raw-links">
|
|
825
|
-
<a href="{% url 'admin:dj_queue_process_changelist' %}?backend={{ backend_alias }}">
|
|
826
|
-
<a href="{% url 'admin:dj_queue_recurringtask_changelist' %}?backend={{ backend_alias }}">
|
|
827
|
-
<a href="{% url 'admin:dj_queue_semaphore_changelist' %}?backend={{ backend_alias }}">
|
|
957
|
+
<a href="{% url 'admin:dj_queue_process_changelist' %}?backend={{ backend_alias }}">Processes</a>
|
|
958
|
+
<a href="{% url 'admin:dj_queue_recurringtask_changelist' %}?backend={{ backend_alias }}">Recurring tasks</a>
|
|
959
|
+
<a href="{% url 'admin:dj_queue_semaphore_changelist' %}?backend={{ backend_alias }}">Semaphores</a>
|
|
828
960
|
</div>
|
|
829
961
|
</div>
|
|
830
962
|
</div>
|
|
@@ -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;
|
|
@@ -187,14 +191,21 @@
|
|
|
187
191
|
<div class="breadcrumbs">
|
|
188
192
|
<a href="{% url 'admin:index' %}">Home</a>
|
|
189
193
|
› <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}">dj_queue</a>
|
|
194
|
+
› <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}#queue-summary">queues</a>
|
|
190
195
|
› {{ queue_name }}
|
|
191
196
|
</div>
|
|
192
197
|
{% endblock breadcrumbs %}
|
|
193
198
|
|
|
194
199
|
{% block content_title %}
|
|
195
|
-
<h1>
|
|
200
|
+
<h1>
|
|
201
|
+
<a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}">dj_queue</a>
|
|
202
|
+
› <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}#queue-summary">queues</a>
|
|
203
|
+
› {{ queue_name }}
|
|
204
|
+
</h1>
|
|
196
205
|
{% endblock content_title %}
|
|
197
206
|
|
|
207
|
+
{% block content_subtitle %}{% endblock content_subtitle %}
|
|
208
|
+
|
|
198
209
|
{% block content %}
|
|
199
210
|
<div id="content-main">
|
|
200
211
|
<div class="module" id="changelist">
|
|
@@ -203,8 +214,9 @@
|
|
|
203
214
|
<div id="toolbar">
|
|
204
215
|
<div class="queue-toolbar-row">
|
|
205
216
|
<div class="queue-toolbar-meta">
|
|
206
|
-
<span><strong>
|
|
207
|
-
<span><strong>
|
|
217
|
+
<span><strong>Backend:</strong> {{ backend_alias }}</span>
|
|
218
|
+
<span><strong>Database:</strong> {{ queue_database_alias }}</span>
|
|
219
|
+
<span><strong>Paused:</strong> {{ queue_paused|yesno:"yes,no" }}</span>
|
|
208
220
|
</div>
|
|
209
221
|
<div class="queue-toolbar-actions">
|
|
210
222
|
<form method="post" action="{% url 'admin:dj_queue_dashboard_queue_action' queue_name %}">
|
|
@@ -258,6 +270,7 @@
|
|
|
258
270
|
<div class="actions">
|
|
259
271
|
<label for="action">Action:
|
|
260
272
|
<select name="action" id="action">
|
|
273
|
+
<option value="" selected>---------</option>
|
|
261
274
|
{% for action in job_actions %}
|
|
262
275
|
<option value="{{ action.name }}">{{ action.label }}</option>
|
|
263
276
|
{% endfor %}
|
|
@@ -300,7 +313,9 @@
|
|
|
300
313
|
{% if job_actions %}
|
|
301
314
|
<td class="action-checkbox"><input type="checkbox" class="action-select" name="job_ids" value="{{ job.id }}"></td>
|
|
302
315
|
{% endif %}
|
|
303
|
-
<th scope="row"
|
|
316
|
+
<th scope="row">
|
|
317
|
+
<a href="{% url 'admin:dj_queue_job_change' job.id %}?backend={{ backend_alias }}"><code>{{ job.id }}</code></a>
|
|
318
|
+
</th>
|
|
304
319
|
<td>{{ job.task_path }}</td>
|
|
305
320
|
<td>{{ job.priority }}</td>
|
|
306
321
|
<td>{{ job.created_at|date:"Y-m-d H:i:s" }}</td>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{% extends "admin/change_form.html" %}
|
|
2
|
-
|
|
3
|
-
{% load i18n admin_urls %}
|
|
4
|
-
|
|
5
|
-
{% block breadcrumbs %}
|
|
6
|
-
<div class="breadcrumbs">
|
|
7
|
-
<a href='{% url "admin:index" %}'>{% translate "Home" %}</a>
|
|
8
|
-
› <a href="{{ dashboard_url }}">dj_queue</a>
|
|
9
|
-
› {% if has_view_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
|
|
10
|
-
› {% if add %}{% blocktranslate with name=opts.verbose_name %}Add {{ name }}{% endblocktranslate %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
|
11
|
-
</div>
|
|
12
|
-
{% endblock breadcrumbs %}
|
|
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.1 → dj_queue-0.2.3}/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
|
|
File without changes
|