dj-queue 0.2.1__tar.gz → 0.2.2__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 (55) hide show
  1. {dj_queue-0.2.1 → dj_queue-0.2.2}/PKG-INFO +4 -3
  2. {dj_queue-0.2.1 → dj_queue-0.2.2}/README.md +3 -2
  3. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/admin.py +101 -8
  4. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/dashboard.py +1 -1
  5. dj_queue-0.2.2/dj_queue/templates/admin/dj_queue/change_form.html +69 -0
  6. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/templates/admin/dj_queue/dashboard.html +6 -6
  7. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/templates/admin/dj_queue/queue_jobs.html +11 -2
  8. {dj_queue-0.2.1 → dj_queue-0.2.2}/pyproject.toml +1 -1
  9. dj_queue-0.2.1/dj_queue/templates/admin/dj_queue/change_form.html +0 -12
  10. {dj_queue-0.2.1 → dj_queue-0.2.2}/LICENSE +0 -0
  11. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/__init__.py +0 -0
  12. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/api.py +0 -0
  13. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/apps.py +0 -0
  14. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/backend.py +0 -0
  15. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/config.py +0 -0
  16. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/contrib/__init__.py +0 -0
  17. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/contrib/asgi.py +0 -0
  18. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/contrib/gunicorn.py +0 -0
  19. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/db.py +0 -0
  20. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/exceptions.py +0 -0
  21. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/hooks.py +0 -0
  22. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/log.py +0 -0
  23. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/management/__init__.py +0 -0
  24. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/management/commands/__init__.py +0 -0
  25. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/management/commands/dj_queue.py +0 -0
  26. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/management/commands/dj_queue_health.py +0 -0
  27. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  28. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/migrations/0001_initial.py +0 -0
  29. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  30. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  31. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/migrations/0004_dashboard.py +0 -0
  32. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/migrations/__init__.py +0 -0
  33. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/models/__init__.py +0 -0
  34. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/models/jobs.py +0 -0
  35. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/models/recurring.py +0 -0
  36. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/models/runtime.py +0 -0
  37. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/operations/__init__.py +0 -0
  38. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/operations/cleanup.py +0 -0
  39. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/operations/concurrency.py +0 -0
  40. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/operations/jobs.py +0 -0
  41. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/operations/recurring.py +0 -0
  42. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/routers.py +0 -0
  43. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/__init__.py +0 -0
  44. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/base.py +0 -0
  45. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/dispatcher.py +0 -0
  46. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/errors.py +0 -0
  47. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/interruptible.py +0 -0
  48. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/notify.py +0 -0
  49. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/pidfile.py +0 -0
  50. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/pool.py +0 -0
  51. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/procline.py +0 -0
  52. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/scheduler.py +0 -0
  53. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/supervisor.py +0 -0
  54. {dj_queue-0.2.1 → dj_queue-0.2.2}/dj_queue/runtime/worker.py +0 -0
  55. {dj_queue-0.2.1 → dj_queue-0.2.2}/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.1
3
+ Version: 0.2.2
4
4
  Summary: Database-backed task queue backend for Django's django.tasks framework
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -28,6 +28,7 @@ Description-Content-Type: text/markdown
28
28
 
29
29
  [![CI](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml/badge.svg)](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml)
30
30
  ![PyPI](https://img.shields.io/pypi/v/dj-queue.svg)
31
+ [![Latest on Django Packages](https://img.shields.io/badge/pypi/dj-queue-tags.svg)](https://djangopackages.org/packages/p/dj-queue/)
31
32
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dj-queue.svg)
32
33
  ![PyPI - Status](https://img.shields.io/pypi/status/dj-queue.svg)
33
34
  ![PyPI - License](https://img.shields.io/pypi/l/dj-queue.svg)
@@ -414,8 +415,8 @@ pauses, and semaphores.
414
415
  When a task raises, `dj_queue` keeps the job and its failed execution row in the
415
416
  queue database, including the exception class, message, and traceback.
416
417
 
417
- You can retry failed jobs through Django admin, or retry and discard them
418
- through the operations layer:
418
+ You can retry and discard failed jobs through Django admin, or call the same
419
+ operations directly through the operations layer:
419
420
 
420
421
  ```python
421
422
  from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml/badge.svg)](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml)
4
4
  ![PyPI](https://img.shields.io/pypi/v/dj-queue.svg)
5
+ [![Latest on Django Packages](https://img.shields.io/badge/pypi/dj-queue-tags.svg)](https://djangopackages.org/packages/p/dj-queue/)
5
6
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dj-queue.svg)
6
7
  ![PyPI - Status](https://img.shields.io/pypi/status/dj-queue.svg)
7
8
  ![PyPI - License](https://img.shields.io/pypi/l/dj-queue.svg)
@@ -388,8 +389,8 @@ pauses, and semaphores.
388
389
  When a task raises, `dj_queue` keeps the job and its failed execution row in the
389
390
  queue database, including the exception class, message, and traceback.
390
391
 
391
- You can retry failed jobs through Django admin, or retry and discard them
392
- through the operations layer:
392
+ You can retry and discard failed jobs through Django admin, or call the same
393
+ operations directly through the operations layer:
393
394
 
394
395
  ```python
395
396
  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
- **dashboard.queue_page_context(
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
- extra_context = {**(extra_context or {}), "dashboard_url": self._dashboard_url(request)}
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):
@@ -550,7 +550,7 @@ def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_rows, s
550
550
  "detail": f"{live_processes} live, {stale_processes} stale",
551
551
  },
552
552
  {
553
- "label": "control plane",
553
+ "label": "control-plane",
554
554
  "value": len(recurring_rows) + len(semaphore_rows),
555
555
  "detail": f"{len(recurring_rows)} recurring and {len(semaphore_rows)} semaphores",
556
556
  },
@@ -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 %}
@@ -810,9 +810,9 @@
810
810
  <p>Job-centric rows scoped to the selected backend where possible.</p>
811
811
  </div>
812
812
  <div class="djq-raw-links">
813
- <a href="{% url 'admin:dj_queue_job_changelist' %}?backend={{ backend_alias }}">jobs</a>
814
- <a href="{% url 'admin:dj_queue_failedexecution_changelist' %}?backend={{ backend_alias }}">failed executions</a>
815
- <a href="{% url 'admin:dj_queue_pause_changelist' %}?backend={{ backend_alias }}">pause rows</a>
813
+ <a href="{% url 'admin:dj_queue_job_changelist' %}?backend={{ backend_alias }}">Jobs</a>
814
+ <a href="{% url 'admin:dj_queue_failedexecution_changelist' %}?backend={{ backend_alias }}">Failed executions</a>
815
+ <a href="{% url 'admin:dj_queue_pause_changelist' %}?backend={{ backend_alias }}">Pauses</a>
816
816
  </div>
817
817
  </div>
818
818
 
@@ -822,9 +822,9 @@
822
822
  <p>Control-plane rows scoped by the selected backend's queue database alias.</p>
823
823
  </div>
824
824
  <div class="djq-raw-links">
825
- <a href="{% url 'admin:dj_queue_process_changelist' %}?backend={{ backend_alias }}">processes</a>
826
- <a href="{% url 'admin:dj_queue_recurringtask_changelist' %}?backend={{ backend_alias }}">recurring tasks</a>
827
- <a href="{% url 'admin:dj_queue_semaphore_changelist' %}?backend={{ backend_alias }}">semaphores</a>
825
+ <a href="{% url 'admin:dj_queue_process_changelist' %}?backend={{ backend_alias }}">Processes</a>
826
+ <a href="{% url 'admin:dj_queue_recurringtask_changelist' %}?backend={{ backend_alias }}">Recurring tasks</a>
827
+ <a href="{% url 'admin:dj_queue_semaphore_changelist' %}?backend={{ backend_alias }}">Semaphores</a>
828
828
  </div>
829
829
  </div>
830
830
  </div>
@@ -187,14 +187,21 @@
187
187
  <div class="breadcrumbs">
188
188
  <a href="{% url 'admin:index' %}">Home</a>
189
189
  › <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}">dj_queue</a>
190
+ › <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}#queue-summary">queues</a>
190
191
  › {{ queue_name }}
191
192
  </div>
192
193
  {% endblock breadcrumbs %}
193
194
 
194
195
  {% block content_title %}
195
- <h1>{{ queue_name }} {{ state_label }} jobs</h1>
196
+ <h1>
197
+ <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}">dj_queue</a>
198
+ › <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}#queue-summary">queues</a>
199
+ › {{ queue_name }}
200
+ </h1>
196
201
  {% endblock content_title %}
197
202
 
203
+ {% block content_subtitle %}{% endblock content_subtitle %}
204
+
198
205
  {% block content %}
199
206
  <div id="content-main">
200
207
  <div class="module" id="changelist">
@@ -300,7 +307,9 @@
300
307
  {% if job_actions %}
301
308
  <td class="action-checkbox"><input type="checkbox" class="action-select" name="job_ids" value="{{ job.id }}"></td>
302
309
  {% endif %}
303
- <th scope="row"><code>{{ job.id }}</code></th>
310
+ <th scope="row">
311
+ <a href="{% url 'admin:dj_queue_job_change' job.id %}?backend={{ backend_alias }}"><code>{{ job.id }}</code></a>
312
+ </th>
304
313
  <td>{{ job.task_path }}</td>
305
314
  <td>{{ job.priority }}</td>
306
315
  <td>{{ job.created_at|date:"Y-m-d H:i:s" }}</td>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "dj-queue"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Database-backed task queue backend for Django's django.tasks framework"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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