dj-queue 0.2.0__tar.gz → 0.2.1__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 (54) hide show
  1. {dj_queue-0.2.0 → dj_queue-0.2.1}/PKG-INFO +1 -1
  2. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/admin.py +33 -0
  3. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/apps.py +1 -1
  4. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/dashboard.py +11 -4
  5. dj_queue-0.2.1/dj_queue/templates/admin/dj_queue/change_form.html +12 -0
  6. dj_queue-0.2.1/dj_queue/templates/admin/dj_queue/change_list.html +11 -0
  7. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/templates/admin/dj_queue/dashboard.html +41 -27
  8. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/templates/admin/dj_queue/queue_jobs.html +96 -8
  9. {dj_queue-0.2.0 → dj_queue-0.2.1}/pyproject.toml +1 -1
  10. {dj_queue-0.2.0 → dj_queue-0.2.1}/LICENSE +0 -0
  11. {dj_queue-0.2.0 → dj_queue-0.2.1}/README.md +0 -0
  12. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/__init__.py +0 -0
  13. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/api.py +0 -0
  14. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/backend.py +0 -0
  15. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/config.py +0 -0
  16. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/contrib/__init__.py +0 -0
  17. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/contrib/asgi.py +0 -0
  18. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/contrib/gunicorn.py +0 -0
  19. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/db.py +0 -0
  20. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/exceptions.py +0 -0
  21. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/hooks.py +0 -0
  22. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/log.py +0 -0
  23. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/management/__init__.py +0 -0
  24. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/management/commands/__init__.py +0 -0
  25. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/management/commands/dj_queue.py +0 -0
  26. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/management/commands/dj_queue_health.py +0 -0
  27. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  28. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/migrations/0001_initial.py +0 -0
  29. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  30. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  31. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/migrations/0004_dashboard.py +0 -0
  32. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/migrations/__init__.py +0 -0
  33. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/models/__init__.py +0 -0
  34. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/models/jobs.py +0 -0
  35. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/models/recurring.py +0 -0
  36. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/models/runtime.py +0 -0
  37. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/operations/__init__.py +0 -0
  38. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/operations/cleanup.py +0 -0
  39. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/operations/concurrency.py +0 -0
  40. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/operations/jobs.py +0 -0
  41. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/operations/recurring.py +0 -0
  42. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/routers.py +0 -0
  43. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/__init__.py +0 -0
  44. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/base.py +0 -0
  45. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/dispatcher.py +0 -0
  46. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/errors.py +0 -0
  47. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/interruptible.py +0 -0
  48. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/notify.py +0 -0
  49. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/pidfile.py +0 -0
  50. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/pool.py +0 -0
  51. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/procline.py +0 -0
  52. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/scheduler.py +0 -0
  53. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/supervisor.py +0 -0
  54. {dj_queue-0.2.0 → dj_queue-0.2.1}/dj_queue/runtime/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dj-queue
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Database-backed task queue backend for Django's django.tasks framework
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -29,10 +29,25 @@ from dj_queue.models import (
29
29
 
30
30
 
31
31
  class DjQueueFirstAdminSite(admin.AdminSite):
32
+ def _dashboard_app_url(self):
33
+ return reverse("admin:dj_queue_dashboard_changelist", current_app=self.name)
34
+
32
35
  def get_app_list(self, request, app_label=None):
33
36
  app_list = super().get_app_list(request, app_label=app_label)
37
+ for app in app_list:
38
+ if app["app_label"] == "dj_queue":
39
+ app["app_url"] = self._dashboard_app_url()
34
40
  return sorted(app_list, key=lambda app: app["app_label"] != "dj_queue")
35
41
 
42
+ def app_index(self, request, app_label, extra_context=None):
43
+ if app_label == "dj_queue":
44
+ url = self._dashboard_app_url()
45
+ query = request.GET.urlencode()
46
+ if query:
47
+ url = f"{url}?{query}"
48
+ return HttpResponseRedirect(url)
49
+ return super().app_index(request, app_label, extra_context=extra_context)
50
+
36
51
 
37
52
  admin.site.__class__ = DjQueueFirstAdminSite
38
53
 
@@ -175,10 +190,25 @@ class HiddenSidebarAdminMixin:
175
190
  backend_query_param = "backend"
176
191
  backend_filter_field = None
177
192
  ignored_filter_params = ()
193
+ change_list_template = "admin/dj_queue/change_list.html"
194
+ change_form_template = "admin/dj_queue/change_form.html"
178
195
 
179
196
  def get_list_filter(self, request):
180
197
  return (BackendListFilter, *tuple(super().get_list_filter(request)))
181
198
 
199
+ def changelist_view(self, request, extra_context=None):
200
+ extra_context = {**(extra_context or {}), "dashboard_url": self._dashboard_url(request)}
201
+ return super().changelist_view(request, extra_context=extra_context)
202
+
203
+ 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)}
205
+ return super().changeform_view(
206
+ request,
207
+ object_id=object_id,
208
+ form_url=form_url,
209
+ extra_context=extra_context,
210
+ )
211
+
182
212
  def has_add_permission(self, request):
183
213
  return False
184
214
 
@@ -239,6 +269,9 @@ class HiddenSidebarAdminMixin:
239
269
  def _backend_database_alias(self, request):
240
270
  return get_database_alias(self._backend_alias(request))
241
271
 
272
+ def _dashboard_url(self, request):
273
+ return f"{reverse('admin:dj_queue_dashboard_changelist')}?{urlencode({'backend': self._backend_alias(request)})}"
274
+
242
275
 
243
276
  class JobStatusListFilter(admin.SimpleListFilter):
244
277
  title = "status"
@@ -3,4 +3,4 @@ from django.apps import AppConfig
3
3
 
4
4
  class DjQueueConfig(AppConfig):
5
5
  name = "dj_queue"
6
- verbose_name = "dj queue"
6
+ verbose_name = "dj_queue"
@@ -962,6 +962,7 @@ def _overview_headers(
962
962
  explicit_sort=explicit_sort,
963
963
  page_param=page_param,
964
964
  anchor=anchor,
965
+ preserve_anchor=True,
965
966
  )
966
967
 
967
968
 
@@ -974,11 +975,12 @@ def _queue_page_headers(*, state, query_params, sort, explicit_sort, page_param,
974
975
  explicit_sort=explicit_sort,
975
976
  page_param=page_param,
976
977
  anchor=anchor,
978
+ preserve_anchor=False,
977
979
  )
978
980
 
979
981
 
980
982
  def _sortable_headers(
981
- *, fields, query_params, sort_param, sort, explicit_sort, page_param, anchor
983
+ *, fields, query_params, sort_param, sort, explicit_sort, page_param, anchor, preserve_anchor
982
984
  ):
983
985
  sort_fields = _parse_sort_fields(sort) if explicit_sort else ()
984
986
 
@@ -1021,6 +1023,7 @@ def _sortable_headers(
1021
1023
  sort_value=primary_sort,
1022
1024
  page_param=page_param,
1023
1025
  anchor=anchor,
1026
+ preserve_anchor=preserve_anchor,
1024
1027
  )
1025
1028
  toggle_url = (
1026
1029
  _overview_sort_url(
@@ -1029,6 +1032,7 @@ def _sortable_headers(
1029
1032
  sort_value=toggle_sort,
1030
1033
  page_param=page_param,
1031
1034
  anchor=anchor,
1035
+ preserve_anchor=preserve_anchor,
1032
1036
  )
1033
1037
  if is_sorted
1034
1038
  else primary_url
@@ -1040,6 +1044,7 @@ def _sortable_headers(
1040
1044
  sort_value=remove_sort,
1041
1045
  page_param=page_param,
1042
1046
  anchor=anchor,
1047
+ preserve_anchor=preserve_anchor,
1043
1048
  )
1044
1049
  if is_sorted
1045
1050
  else None
@@ -1073,7 +1078,9 @@ def _queue_result_count_text(*, page_obj, total_count):
1073
1078
  return f"{page_obj.start_index()}-{page_obj.end_index()} of {total_count} jobs"
1074
1079
 
1075
1080
 
1076
- def _overview_sort_url(*, query_params, sort_param, sort_value, page_param, anchor):
1081
+ def _overview_sort_url(
1082
+ *, query_params, sort_param, sort_value, page_param, anchor, preserve_anchor
1083
+ ):
1077
1084
  params = query_params.copy()
1078
1085
  if sort_value:
1079
1086
  params[sort_param] = sort_value
@@ -1082,8 +1089,8 @@ def _overview_sort_url(*, query_params, sort_param, sort_value, page_param, anch
1082
1089
  params.pop(page_param, None)
1083
1090
  url = params.urlencode() if hasattr(params, "urlencode") else urlencode(params, doseq=True)
1084
1091
  if not url:
1085
- return f"?#{anchor}"
1086
- return f"?{url}#{anchor}"
1092
+ return f"?#{anchor}" if preserve_anchor else "?"
1093
+ return f"?{url}#{anchor}" if preserve_anchor else f"?{url}"
1087
1094
 
1088
1095
 
1089
1096
  def _queue_rows(*, backend_alias, now, process_cutoff):
@@ -0,0 +1,12 @@
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 %}
@@ -0,0 +1,11 @@
1
+ {% extends "admin/change_list.html" %}
2
+
3
+ {% load i18n %}
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
+ › {{ cl.opts.verbose_name_plural|capfirst }}
10
+ </div>
11
+ {% endblock breadcrumbs %}
@@ -7,6 +7,8 @@
7
7
  <link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.css' %}">
8
8
  <style>
9
9
  #content-main.djq-dashboard {
10
+ --djq-status-live-fg: #0b6b2d;
11
+ --djq-status-stale-fg: #8a4b00;
10
12
  width: 100%;
11
13
  max-width: 100%;
12
14
  min-width: 0;
@@ -16,6 +18,18 @@
16
18
  box-sizing: border-box;
17
19
  }
18
20
 
21
+ @media (prefers-color-scheme: dark) {
22
+ html:not([data-theme="light"]) #content-main.djq-dashboard {
23
+ --djq-status-live-fg: #7ad7a2;
24
+ --djq-status-stale-fg: #ffbf69;
25
+ }
26
+ }
27
+
28
+ html[data-theme="dark"] #content-main.djq-dashboard {
29
+ --djq-status-live-fg: #7ad7a2;
30
+ --djq-status-stale-fg: #ffbf69;
31
+ }
32
+
19
33
  .djq-hero,
20
34
  .djq-summary-grid,
21
35
  .djq-section {
@@ -30,8 +44,8 @@
30
44
  display: grid;
31
45
  gap: 1rem;
32
46
  padding: 1.25rem;
33
- border: 1px solid rgba(38, 75, 94, 0.14);
34
- background: linear-gradient(180deg, rgba(64, 110, 135, 0.06), rgba(64, 110, 135, 0.02));
47
+ border: 1px solid var(--hairline-color);
48
+ background: var(--darkened-bg);
35
49
  }
36
50
 
37
51
  .djq-hero-main {
@@ -43,7 +57,7 @@
43
57
  p.djq-eyebrow {
44
58
  padding: 0;
45
59
  margin: 0;
46
- color: #406e87;
60
+ color: var(--link-fg);
47
61
  font-size: 0.78rem;
48
62
  font-weight: 700;
49
63
  letter-spacing: 0.08em;
@@ -60,7 +74,7 @@
60
74
  max-width: 44rem;
61
75
  padding: 0;
62
76
  margin: 0;
63
- color: #4c5b66;
77
+ color: var(--body-quiet-color);
64
78
  font-size: 0.95rem;
65
79
  line-height: 1.5;
66
80
  }
@@ -69,8 +83,8 @@
69
83
  display: grid;
70
84
  gap: 0.9rem;
71
85
  padding: 1rem;
72
- border: 1px solid rgba(38, 75, 94, 0.12);
73
- background: rgba(255, 255, 255, 0.84);
86
+ border: 1px solid var(--hairline-color);
87
+ background: var(--body-bg);
74
88
  min-width: 0;
75
89
  }
76
90
 
@@ -88,7 +102,7 @@
88
102
  }
89
103
 
90
104
  .djq-backend-switch label {
91
- color: #264b5e;
105
+ color: var(--link-selected-fg);
92
106
  font-size: 0.78rem;
93
107
  font-weight: 700;
94
108
  letter-spacing: 0.06em;
@@ -107,7 +121,7 @@
107
121
  p.djq-backend-caption {
108
122
  padding: 0;
109
123
  margin: 0;
110
- color: #4c5b66;
124
+ color: var(--body-quiet-color);
111
125
  font-size: 0.86rem;
112
126
  line-height: 1.45;
113
127
  }
@@ -126,7 +140,7 @@
126
140
 
127
141
  .djq-backend-facts dt {
128
142
  margin: 0;
129
- color: #4c5b66;
143
+ color: var(--body-quiet-color);
130
144
  font-size: 0.76rem;
131
145
  font-weight: 700;
132
146
  letter-spacing: 0.06em;
@@ -135,7 +149,7 @@
135
149
 
136
150
  .djq-backend-facts dd {
137
151
  margin: 0.2rem 0 0;
138
- color: #1f1f1f;
152
+ color: var(--body-fg);
139
153
  font-size: 0.95rem;
140
154
  line-height: 1.3;
141
155
  word-break: break-word;
@@ -143,8 +157,8 @@
143
157
 
144
158
  .djq-raw-panel {
145
159
  padding: 0.95rem 1rem;
146
- border: 1px solid rgba(38, 75, 94, 0.12);
147
- background: rgba(255, 255, 255, 0.82);
160
+ border: 1px solid var(--hairline-color);
161
+ background: var(--body-bg);
148
162
  min-width: 0;
149
163
  }
150
164
 
@@ -182,7 +196,7 @@
182
196
  .djq-raw-group h3 {
183
197
  padding: 0;
184
198
  margin: 0;
185
- color: #4c5b66;
199
+ color: var(--body-quiet-color);
186
200
  font-size: 0.78rem;
187
201
  font-weight: 700;
188
202
  letter-spacing: 0.08em;
@@ -232,8 +246,8 @@
232
246
  }
233
247
 
234
248
  .djq-summary-card {
235
- border: 1px solid rgba(38, 75, 94, 0.12);
236
- background: rgba(255, 255, 255, 0.9);
249
+ border: 1px solid var(--hairline-color);
250
+ background: var(--body-bg);
237
251
  min-width: 0;
238
252
  }
239
253
 
@@ -250,12 +264,12 @@
250
264
  font-size: 1.7rem;
251
265
  font-weight: 700;
252
266
  line-height: 1.1;
253
- color: #1f1f1f;
267
+ color: var(--body-fg);
254
268
  }
255
269
 
256
270
  .djq-summary-detail {
257
271
  margin-top: 0.35rem;
258
- color: #4c5b66;
272
+ color: var(--body-quiet-color);
259
273
  font-size: 0.88rem;
260
274
  }
261
275
 
@@ -276,18 +290,18 @@
276
290
  display: block;
277
291
  width: 100%;
278
292
  padding: 0.5rem;
279
- background: #4f82a0;
280
- color: #fff;
293
+ background: var(--primary);
294
+ color: var(--primary-fg);
281
295
  box-sizing: border-box;
282
296
  }
283
297
 
284
298
  .djq-section-copy {
285
299
  margin: 0;
286
300
  padding: 0.5rem;
287
- color: #4c5b66;
301
+ color: var(--body-quiet-color);
288
302
  font-size: 0.9rem;
289
303
  line-height: 1.45;
290
- border-bottom: 1px solid rgba(38, 75, 94, 0.08);
304
+ border-bottom: 1px solid var(--hairline-color);
291
305
  }
292
306
 
293
307
  .djq-table-wrap {
@@ -354,7 +368,7 @@
354
368
  }
355
369
 
356
370
  .djq-process-row-group {
357
- background: rgba(38, 75, 94, 0.04);
371
+ background: var(--selected-bg);
358
372
  }
359
373
 
360
374
  .djq-process-row-child th:first-child {
@@ -369,25 +383,25 @@
369
383
  top: 0.7rem;
370
384
  bottom: 0.7rem;
371
385
  width: 2px;
372
- background: rgba(38, 75, 94, 0.16);
386
+ background: var(--border-color);
373
387
  }
374
388
 
375
389
  .djq-process-meta {
376
390
  display: block;
377
391
  margin-top: 0.15rem;
378
- color: #4c5b66;
392
+ color: var(--body-quiet-color);
379
393
  font-size: 0.78rem;
380
394
  font-weight: 400;
381
395
  white-space: normal;
382
396
  }
383
397
 
384
398
  .djq-status-live {
385
- color: #0b6b2d;
399
+ color: var(--djq-status-live-fg);
386
400
  font-weight: 700;
387
401
  }
388
402
 
389
403
  .djq-status-stale {
390
- color: #8a4b00;
404
+ color: var(--djq-status-stale-fg);
391
405
  font-weight: 700;
392
406
  }
393
407
 
@@ -416,7 +430,7 @@
416
430
  .djq-empty {
417
431
  margin: 0;
418
432
  padding: 1rem;
419
- color: #4c5b66;
433
+ color: var(--body-quiet-color);
420
434
  font-size: 0.86rem;
421
435
  line-height: 1.45;
422
436
  }
@@ -6,6 +6,29 @@
6
6
  {{ block.super }}
7
7
  <link rel="stylesheet" type="text/css" href="{% static 'admin/css/changelists.css' %}">
8
8
  <style>
9
+ #content-main {
10
+ --djq-tab-selected-bg: var(--primary);
11
+ --djq-tab-selected-border: var(--primary);
12
+ --djq-tab-selected-fg: var(--primary-fg);
13
+ --djq-tab-selected-count-fg: var(--breadcrumbs-link-fg);
14
+ }
15
+
16
+ @media (prefers-color-scheme: dark) {
17
+ html:not([data-theme="light"]) #content-main {
18
+ --djq-tab-selected-bg: var(--selected-row);
19
+ --djq-tab-selected-border: var(--link-selected-fg);
20
+ --djq-tab-selected-fg: var(--body-fg);
21
+ --djq-tab-selected-count-fg: var(--body-quiet-color);
22
+ }
23
+ }
24
+
25
+ html[data-theme="dark"] #content-main {
26
+ --djq-tab-selected-bg: var(--selected-row);
27
+ --djq-tab-selected-border: var(--link-selected-fg);
28
+ --djq-tab-selected-fg: var(--body-fg);
29
+ --djq-tab-selected-count-fg: var(--body-quiet-color);
30
+ }
31
+
9
32
  #toolbar .queue-toolbar-row,
10
33
  #toolbar .queue-toolbar-meta,
11
34
  #toolbar .queue-toolbar-links,
@@ -78,12 +101,71 @@
78
101
  margin-top: 0.5rem;
79
102
  }
80
103
 
81
- .queue-state-tabs {
82
- padding-bottom: 5px;
104
+ .toplinks.queue-state-tabs {
105
+ gap: 1rem;
106
+ margin-top: 0.75rem;
107
+ padding: 0.45rem;
108
+ border: 1px solid var(--hairline-color);
109
+ background: var(--darkened-bg);
110
+ }
111
+
112
+ .queue-state-tab {
113
+ display: inline-flex;
114
+ gap: 0.4rem;
115
+ align-items: center;
116
+ min-height: 2.15rem;
117
+ padding: 0 0.9rem;
118
+ border: 1px solid var(--border-color);
119
+ border-radius: 999px;
120
+ background: var(--body-bg);
121
+ color: var(--link-fg);
122
+ font-weight: 600;
123
+ text-decoration: none;
124
+ transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease;
125
+ }
126
+
127
+ #changelist .queue-state-tabs a.queue-state-tab,
128
+ #changelist .queue-state-tabs a.queue-state-tab:hover,
129
+ #changelist .queue-state-tabs a.queue-state-tab:focus,
130
+ #changelist .queue-state-tabs a.queue-state-tab:visited {
131
+ text-decoration: none;
132
+ }
133
+
134
+ .queue-state-tab:not(.queue-state-tab-current):hover,
135
+ .queue-state-tab:not(.queue-state-tab-current):focus {
136
+ background: var(--selected-bg);
137
+ border-color: var(--primary);
138
+ color: var(--link-hover-color);
139
+ text-decoration: none;
140
+ }
141
+
142
+ .queue-state-tab-current {
143
+ background: var(--djq-tab-selected-bg);
144
+ border-color: var(--djq-tab-selected-border);
145
+ color: var(--djq-tab-selected-fg);
146
+ box-shadow: none;
147
+ }
148
+
149
+ .queue-state-tab-current:hover,
150
+ .queue-state-tab-current:focus {
151
+ background: var(--djq-tab-selected-bg);
152
+ border-color: var(--djq-tab-selected-border);
153
+ color: var(--djq-tab-selected-fg);
154
+ }
155
+
156
+ .queue-state-tab-count {
157
+ color: var(--body-quiet-color);
158
+ font-size: 0.82rem;
159
+ font-weight: 700;
160
+ }
161
+
162
+ .queue-state-tab-current .queue-state-tab-count {
163
+ color: var(--djq-tab-selected-count-fg);
83
164
  }
84
165
 
85
- .queue-state-current {
86
- color: var(--link-selected-fg);
166
+ .queue-state-tab-current:hover .queue-state-tab-count,
167
+ .queue-state-tab-current:focus .queue-state-tab-count {
168
+ color: var(--djq-tab-selected-count-fg);
87
169
  }
88
170
 
89
171
  #result_list {
@@ -104,7 +186,7 @@
104
186
  {% block breadcrumbs %}
105
187
  <div class="breadcrumbs">
106
188
  <a href="{% url 'admin:index' %}">Home</a>
107
- › <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}">dj queue</a>
189
+ › <a href="{% url 'admin:dj_queue_dashboard_changelist' %}?backend={{ backend_alias }}">dj_queue</a>
108
190
  › {{ queue_name }}
109
191
  </div>
110
192
  {% endblock breadcrumbs %}
@@ -150,12 +232,18 @@
150
232
  {% endif %}
151
233
  </div>
152
234
 
153
- <div class="toplinks queue-state-tabs">
235
+ <div class="toplinks queue-state-tabs" aria-label="Job state navigation">
154
236
  {% for tab in state_tabs %}
155
237
  {% if tab.selected %}
156
- <span class="queue-state-current">{{ tab.label }} ({{ tab.count }})</span>
238
+ <span class="queue-state-tab queue-state-tab-current" aria-current="page">
239
+ <span>{{ tab.label }}</span>
240
+ <span class="queue-state-tab-count">{{ tab.count }}</span>
241
+ </span>
157
242
  {% else %}
158
- <a href="{% url 'admin:dj_queue_dashboard_queue' queue_name %}?backend={{ backend_alias }}&state={{ tab.name }}">{{ tab.label }} ({{ tab.count }})</a>
243
+ <a class="queue-state-tab" href="{% url 'admin:dj_queue_dashboard_queue' queue_name %}?backend={{ backend_alias }}&state={{ tab.name }}">
244
+ <span>{{ tab.label }}</span>
245
+ <span class="queue-state-tab-count">{{ tab.count }}</span>
246
+ </a>
159
247
  {% endif %}
160
248
  {% endfor %}
161
249
  </div>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "dj-queue"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Database-backed task queue backend for Django's django.tasks framework"
9
9
  readme = "README.md"
10
10
  license = "MIT"
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