django-bootstrap6-admin 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/CHANGELOG.md +17 -0
  2. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/PKG-INFO +8 -2
  3. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/README.md +7 -1
  4. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/__init__.py +1 -1
  5. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/css/admin.css +45 -1
  6. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/js/admin.js +98 -6
  7. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin.egg-info/PKG-INFO +8 -2
  8. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/test_admin_ui.py +11 -1
  9. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/test_package.py +16 -0
  10. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/CONTRIBUTING.md +0 -0
  11. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/LICENSE +0 -0
  12. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/MANIFEST.in +0 -0
  13. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/NOTICE +0 -0
  14. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/pyproject.toml +0 -0
  15. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/setup.cfg +0 -0
  16. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/apps.py +0 -0
  17. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/css/widgets-overrides.css +0 -0
  18. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/js/theme.js +0 -0
  19. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/vendor/bootstrap/LICENSE +0 -0
  20. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/vendor/bootstrap/css/bootstrap.min.css +0 -0
  21. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/vendor/bootstrap/js/bootstrap.bundle.min.js +0 -0
  22. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/404.html +0 -0
  23. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/500.html +0 -0
  24. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/actions.html +0 -0
  25. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/app_index.html +0 -0
  26. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/app_list.html +0 -0
  27. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/auth/user/add_form.html +0 -0
  28. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/auth/user/change_password.html +0 -0
  29. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/base.html +0 -0
  30. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/base_site.html +0 -0
  31. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/change_form.html +0 -0
  32. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/change_form_object_tools.html +0 -0
  33. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/change_list.html +0 -0
  34. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/change_list_object_tools.html +0 -0
  35. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/change_list_results.html +0 -0
  36. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/color_theme_toggle.html +0 -0
  37. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/date_hierarchy.html +0 -0
  38. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/delete_confirmation.html +0 -0
  39. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/delete_selected_confirmation.html +0 -0
  40. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/edit_inline/stacked.html +0 -0
  41. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/edit_inline/tabular.html +0 -0
  42. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/filter.html +0 -0
  43. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/includes/fieldset.html +0 -0
  44. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/includes/object_delete_summary.html +0 -0
  45. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/index.html +0 -0
  46. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/login.html +0 -0
  47. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/nav_sidebar.html +0 -0
  48. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/object_history.html +0 -0
  49. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/pagination.html +0 -0
  50. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/search_form.html +0 -0
  51. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/submit_line.html +0 -0
  52. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/widgets/foreign_key_raw_id.html +0 -0
  53. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/admin/widgets/related_widget_wrapper.html +0 -0
  54. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/registration/logged_out.html +0 -0
  55. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/registration/password_change_done.html +0 -0
  56. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templates/registration/password_change_form.html +0 -0
  57. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templatetags/__init__.py +0 -0
  58. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin/templatetags/bootstrap6_admin.py +0 -0
  59. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin.egg-info/SOURCES.txt +0 -0
  60. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin.egg-info/dependency_links.txt +0 -0
  61. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin.egg-info/requires.txt +0 -0
  62. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/src/django_bootstrap6_admin.egg-info/top_level.txt +0 -0
  63. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/__init__.py +0 -0
  64. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/js/theme_toggle.test.js +0 -0
  65. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/settings.py +0 -0
  66. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/smoke_wheel.py +0 -0
  67. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/test_templatetags.py +0 -0
  68. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/testapp/__init__.py +0 -0
  69. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/testapp/admin.py +0 -0
  70. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/testapp/apps.py +0 -0
  71. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/testapp/models.py +0 -0
  72. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.0}/tests/urls.py +0 -0
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-11-15
11
+
12
+ ### Added
13
+
14
+ - The desktop filter panel can now be collapsed and expanded, and the
15
+ collapsed state is remembered between changelist pages.
16
+ - The "Show counts" / "Hide counts" choice is remembered across changelist
17
+ pages instead of resetting on navigation.
18
+ - Subtle vertical rules separate columns in changelists, inline tables and the
19
+ object history table.
20
+
21
+ ### Fixed
22
+
23
+ - Removed the duplicate native dropdown arrow on the bulk-action select.
24
+ - Improved dark-theme text contrast for secondary labels, table headers,
25
+ filter links, help text and links.
26
+
10
27
  ## [0.1.2] - 2026-11-15
11
28
 
12
29
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-bootstrap6-admin
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: A modern Bootstrap 6 skin for the Django admin.
5
5
  Author-email: Aliberk Sandıkçı <git@asandikci.com>
6
6
  Maintainer-email: Aliberk Sandıkçı <git@asandikci.com>
@@ -57,12 +57,18 @@ permissions and JavaScript remain in charge.
57
57
  with a backdrop (mobile).
58
58
  - Dashboard with application cards, model shortcuts and recent activity.
59
59
  - Changelist with search, date hierarchy, collapsible filters, sortable
60
- headers, bulk actions and Bootstrap pagination.
60
+ headers, bulk actions and Bootstrap pagination. The desktop filter panel can
61
+ be collapsed, and both its state and the "Show counts" choice are remembered
62
+ between pages.
63
+ - Subtle vertical rules separate columns in changelists, inline tables and the
64
+ object history.
61
65
  - Change form with card-based fieldsets, collapsible sections, a clear save
62
66
  bar, tabular/stacked inlines and related-object popups.
63
67
  - Modern login, logout and password change pages.
64
68
  - Light, dark and system themes. The existing Django `theme` localStorage
65
69
  preference is synchronised with Bootstrap's `data-bs-theme` attribute.
70
+ - Dark-theme secondary text, table headers, filter links and help text use
71
+ higher-contrast tokens for better readability.
66
72
  - Soft fade-in/fade-out transition when switching themes, skipped
67
73
  automatically for users who prefer reduced motion.
68
74
  - Every click changes the visible theme: if `auto` would resolve to the theme
@@ -20,12 +20,18 @@ permissions and JavaScript remain in charge.
20
20
  with a backdrop (mobile).
21
21
  - Dashboard with application cards, model shortcuts and recent activity.
22
22
  - Changelist with search, date hierarchy, collapsible filters, sortable
23
- headers, bulk actions and Bootstrap pagination.
23
+ headers, bulk actions and Bootstrap pagination. The desktop filter panel can
24
+ be collapsed, and both its state and the "Show counts" choice are remembered
25
+ between pages.
26
+ - Subtle vertical rules separate columns in changelists, inline tables and the
27
+ object history.
24
28
  - Change form with card-based fieldsets, collapsible sections, a clear save
25
29
  bar, tabular/stacked inlines and related-object popups.
26
30
  - Modern login, logout and password change pages.
27
31
  - Light, dark and system themes. The existing Django `theme` localStorage
28
32
  preference is synchronised with Bootstrap's `data-bs-theme` attribute.
33
+ - Dark-theme secondary text, table headers, filter links and help text use
34
+ higher-contrast tokens for better readability.
29
35
  - Soft fade-in/fade-out transition when switching themes, skipped
30
36
  automatically for users who prefer reduced motion.
31
37
  - Every click changes the visible theme: if `auto` would resolve to the theme
@@ -1,3 +1,3 @@
1
1
  """Bootstrap 6 skin for the Django admin."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.2.0"
@@ -111,6 +111,23 @@
111
111
  2. Base
112
112
  -------------------------------------------------------------------------- */
113
113
 
114
+ /*
115
+ * Bootstrap's dark gray-500/600 secondary text is too dim on the deep navy
116
+ * admin surfaces, especially at the small sizes used by filters, table
117
+ * headers and action help. Lift those tokens for the effective dark theme.
118
+ */
119
+ [data-bs-theme="dark"],
120
+ [data-effective-theme="dark"] {
121
+ --bs-fg-1: #f1f5f9;
122
+ --bs-fg-2: #d7e0ea;
123
+ --bs-fg-3: #b9c7d6;
124
+ --bs-fg-4: #9fb1c4;
125
+ --ba-text-muted: #c9d4e0;
126
+ --bs-link-color: #8dbcf7;
127
+ --bs-link-hover-color: #b7d4ff;
128
+ --bs-border-muted: #34465a;
129
+ }
130
+
114
131
  html {
115
132
  min-height: 100%;
116
133
  }
@@ -1365,7 +1382,7 @@ a.ba-timeline-object:focus {
1365
1382
  }
1366
1383
 
1367
1384
  .ba-filters-toggle-row {
1368
- display: none;
1385
+ display: flex;
1369
1386
  }
1370
1387
 
1371
1388
  .ba-filters-toggle {
@@ -1551,6 +1568,20 @@ a.ba-date-chip:focus {
1551
1568
  border-block-end: 0;
1552
1569
  }
1553
1570
 
1571
+ /* Very light column rules keep dense admin tables readable without making
1572
+ * every cell look boxed in. */
1573
+ #result_list thead th:not(:last-child),
1574
+ #result_list tbody th:not(:last-child),
1575
+ #result_list tbody td:not(:last-child),
1576
+ .ba-inline-table thead th:not(:last-child),
1577
+ .ba-inline-table tbody th:not(:last-child),
1578
+ .ba-inline-table tbody td:not(:last-child),
1579
+ .ba-history-table thead th:not(:last-child),
1580
+ .ba-history-table tbody th:not(:last-child),
1581
+ .ba-history-table tbody td:not(:last-child) {
1582
+ border-inline-end: 1px solid var(--bs-border-muted);
1583
+ }
1584
+
1554
1585
  #result_list tbody tr.selected > * {
1555
1586
  background: var(--bs-primary-bg-subtle) !important;
1556
1587
  }
@@ -1597,6 +1628,7 @@ a.ba-date-chip:focus {
1597
1628
 
1598
1629
  #result_list select {
1599
1630
  padding-inline-end: 1.6rem;
1631
+ appearance: none;
1600
1632
  background-image: var(--ba-select-arrow);
1601
1633
  background-position: right 0.45rem center;
1602
1634
  background-repeat: no-repeat;
@@ -2177,6 +2209,7 @@ label.required::after {
2177
2209
  .ba-form-group select,
2178
2210
  .ba-actions select {
2179
2211
  padding-inline-end: 2rem;
2212
+ appearance: none;
2180
2213
  background-image: var(--ba-select-arrow);
2181
2214
  background-position: right 0.7rem center;
2182
2215
  background-repeat: no-repeat;
@@ -2551,6 +2584,7 @@ div.help ul {
2551
2584
 
2552
2585
  .ba-inline-table select {
2553
2586
  padding-inline-end: 1.6rem;
2587
+ appearance: none;
2554
2588
  background-image: var(--ba-select-arrow);
2555
2589
  background-position: right 0.45rem center;
2556
2590
  background-repeat: no-repeat;
@@ -3202,6 +3236,16 @@ body.popup .ba-submit-row {
3202
3236
  13. Responsive
3203
3237
  -------------------------------------------------------------------------- */
3204
3238
 
3239
+ @media (min-width: 1200px) {
3240
+ #changelist.ba-filters-collapsed .ba-changelist-layout {
3241
+ grid-template-columns: minmax(0, 1fr);
3242
+ }
3243
+
3244
+ #changelist.ba-filters-collapsed .ba-filters {
3245
+ display: none;
3246
+ }
3247
+ }
3248
+
3205
3249
  @media (max-width: 1199.98px) {
3206
3250
  .ba-dashboard {
3207
3251
  grid-template-columns: minmax(0, 1fr);
@@ -10,7 +10,10 @@
10
10
  */
11
11
  (() => {
12
12
  const MOBILE = window.matchMedia("(max-width: 1023.98px)");
13
+ const DESKTOP = window.matchMedia("(min-width: 1200px)");
13
14
  const STORAGE_KEY = "django.admin.navSidebarIsOpen";
15
+ const FILTERS_KEY = "django.admin.filtersCollapsed";
16
+ const FACETS_KEY = "django.admin.showFacets";
14
17
 
15
18
  function initSidebar() {
16
19
  const main = document.getElementById("main");
@@ -228,16 +231,50 @@
228
231
  return;
229
232
  }
230
233
 
234
+ const isCollapsed = () =>
235
+ changelist.classList.contains("ba-filters-collapsed");
236
+
231
237
  const sync = () => {
232
- toggle.setAttribute(
233
- "aria-expanded",
234
- changelist.classList.contains("ba-filters-open")
235
- ? "true"
236
- : "false",
237
- );
238
+ const expanded = DESKTOP.matches
239
+ ? !isCollapsed()
240
+ : changelist.classList.contains("ba-filters-open");
241
+ toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
242
+ if (panel) {
243
+ panel.toggleAttribute(
244
+ "inert",
245
+ DESKTOP.matches && isCollapsed(),
246
+ );
247
+ }
238
248
  };
239
249
 
250
+ const setCollapsed = (collapsed) => {
251
+ changelist.classList.toggle("ba-filters-collapsed", collapsed);
252
+ if (DESKTOP.matches) {
253
+ localStorage.setItem(
254
+ FILTERS_KEY,
255
+ collapsed ? "true" : "false",
256
+ );
257
+ }
258
+ sync();
259
+ };
260
+
261
+ // Restore the desktop preference. On mobile the panel is a drawer and
262
+ // the collapsed class has no visual effect.
263
+ if (DESKTOP.matches && localStorage.getItem(FILTERS_KEY) === "true") {
264
+ changelist.classList.add("ba-filters-collapsed");
265
+ }
266
+
240
267
  toggle.addEventListener("click", () => {
268
+ if (DESKTOP.matches) {
269
+ const collapsed = !isCollapsed();
270
+ setCollapsed(collapsed);
271
+ if (!collapsed) {
272
+ const target = panel?.querySelector("summary, a, button");
273
+ window.requestAnimationFrame(() => target?.focus());
274
+ }
275
+ return;
276
+ }
277
+
241
278
  changelist.classList.toggle("ba-filters-open");
242
279
  sync();
243
280
  if (changelist.classList.contains("ba-filters-open")) {
@@ -249,15 +286,69 @@
249
286
 
250
287
  if (close) {
251
288
  close.addEventListener("click", () => {
289
+ setCollapsed(true);
252
290
  changelist.classList.remove("ba-filters-open");
253
291
  sync();
254
292
  toggle.focus();
255
293
  });
256
294
  }
257
295
 
296
+ if (DESKTOP.addEventListener) {
297
+ DESKTOP.addEventListener("change", () => {
298
+ if (DESKTOP.matches) {
299
+ changelist.classList.toggle(
300
+ "ba-filters-collapsed",
301
+ localStorage.getItem(FILTERS_KEY) === "true",
302
+ );
303
+ changelist.classList.remove("ba-filters-open");
304
+ }
305
+ sync();
306
+ });
307
+ }
308
+
258
309
  sync();
259
310
  }
260
311
 
312
+ function initFacetPreference() {
313
+ const panel = document.getElementById("changelist-filter");
314
+ if (!panel) {
315
+ return;
316
+ }
317
+
318
+ const links = document.querySelectorAll(
319
+ ".ba-filters-actions .hidelink, .ba-filters-actions .viewlink",
320
+ );
321
+ if (!links.length) {
322
+ return;
323
+ }
324
+
325
+ links.forEach((link) => {
326
+ link.addEventListener("click", () => {
327
+ const show = link.classList.contains("viewlink");
328
+ localStorage.setItem(FACETS_KEY, show ? "true" : "false");
329
+ });
330
+ });
331
+
332
+ const stored = localStorage.getItem(FACETS_KEY);
333
+ if (stored !== "true" && stored !== "false") {
334
+ return;
335
+ }
336
+
337
+ const url = new URL(window.location.href);
338
+ const hasFacets = url.searchParams.has("_facets");
339
+ const showFacets = stored === "true";
340
+ if (hasFacets === showFacets) {
341
+ return;
342
+ }
343
+
344
+ if (showFacets) {
345
+ url.searchParams.set("_facets", "1");
346
+ } else {
347
+ url.searchParams.delete("_facets");
348
+ }
349
+ window.location.replace(url.toString());
350
+ }
351
+
261
352
  function initDirtyGuard() {
262
353
  const form =
263
354
  document.querySelector(".ba-change-form form") ||
@@ -327,6 +418,7 @@
327
418
  function initAll() {
328
419
  initSidebar();
329
420
  initFilterDisclosure();
421
+ initFacetPreference();
330
422
  initDirtyGuard();
331
423
  initErrorFocus();
332
424
  initAccessibility();
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-bootstrap6-admin
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: A modern Bootstrap 6 skin for the Django admin.
5
5
  Author-email: Aliberk Sandıkçı <git@asandikci.com>
6
6
  Maintainer-email: Aliberk Sandıkçı <git@asandikci.com>
@@ -57,12 +57,18 @@ permissions and JavaScript remain in charge.
57
57
  with a backdrop (mobile).
58
58
  - Dashboard with application cards, model shortcuts and recent activity.
59
59
  - Changelist with search, date hierarchy, collapsible filters, sortable
60
- headers, bulk actions and Bootstrap pagination.
60
+ headers, bulk actions and Bootstrap pagination. The desktop filter panel can
61
+ be collapsed, and both its state and the "Show counts" choice are remembered
62
+ between pages.
63
+ - Subtle vertical rules separate columns in changelists, inline tables and the
64
+ object history.
61
65
  - Change form with card-based fieldsets, collapsible sections, a clear save
62
66
  bar, tabular/stacked inlines and related-object popups.
63
67
  - Modern login, logout and password change pages.
64
68
  - Light, dark and system themes. The existing Django `theme` localStorage
65
69
  preference is synchronised with Bootstrap's `data-bs-theme` attribute.
70
+ - Dark-theme secondary text, table headers, filter links and help text use
71
+ higher-contrast tokens for better readability.
66
72
  - Soft fade-in/fade-out transition when switching themes, skipped
67
73
  automatically for users who prefer reduced motion.
68
74
  - Every click changes the visible theme: if `auto` would resolve to the theme
@@ -76,6 +76,8 @@ class AdminUiTests(TestCase):
76
76
  'id="changelist-search"',
77
77
  'id="searchbar"',
78
78
  'id="changelist-filter"',
79
+ 'id="ba-filters-toggle"',
80
+ 'id="ba-filters-close"',
79
81
  'id="action-toggle"',
80
82
  'id="date-hierarchy-label"',
81
83
  'class="actions ba-actions"',
@@ -119,7 +121,6 @@ class AdminUiTests(TestCase):
119
121
  def test_changelist_search_and_pagination(self):
120
122
  response = self.client.get(reverse("admin:testapp_book_changelist"), {"q": "Django"})
121
123
  self.assertContains(response, "Django for Everyone")
122
-
123
124
  for index in range(105):
124
125
  Book.objects.create(
125
126
  title=f"Extra book {index:03d}",
@@ -134,6 +135,15 @@ class AdminUiTests(TestCase):
134
135
  self.assertContains(response, "?p=1")
135
136
  self.assertContains(response, 'aria-current="page">2')
136
137
 
138
+ def test_facet_count_toggle_links(self):
139
+ url = reverse("admin:testapp_book_changelist")
140
+
141
+ response = self.client.get(url)
142
+ self.assertContains(response, "Show counts")
143
+
144
+ response = self.client.get(url, {"_facets": "1"})
145
+ self.assertContains(response, "Hide counts")
146
+
137
147
  def test_change_form_keeps_widgets_and_submit_contract(self):
138
148
  response = self.client.get(reverse("admin:testapp_book_change", args=[self.book.pk]))
139
149
 
@@ -46,3 +46,19 @@ class PackageAssetsTests(SimpleTestCase):
46
46
  self.assertIn(".ba-theme-fade", admin_css)
47
47
  self.assertIn(".ba-theme-fade.is-active", admin_css)
48
48
  self.assertIn("transition: opacity 200ms ease", admin_css)
49
+
50
+ def test_changelist_ux_assets(self):
51
+ static_dir = Path(django_bootstrap6_admin.__file__).parent / "static"
52
+ admin_js = (static_dir / "django_bootstrap6_admin" / "js" / "admin.js").read_text(
53
+ encoding="utf-8"
54
+ )
55
+ admin_css = (static_dir / "django_bootstrap6_admin" / "css" / "admin.css").read_text(
56
+ encoding="utf-8"
57
+ )
58
+
59
+ self.assertIn("django.admin.showFacets", admin_js)
60
+ self.assertIn("django.admin.filtersCollapsed", admin_js)
61
+ self.assertIn("ba-filters-collapsed", admin_css)
62
+ self.assertIn("border-inline-end: 1px solid var(--bs-border-muted)", admin_css)
63
+ self.assertIn("appearance: none", admin_css)
64
+ self.assertIn("--bs-fg-4: #9fb1c4", admin_css)