django-bootstrap6-admin 0.1.2__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 (72) hide show
  1. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/CHANGELOG.md +33 -0
  2. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/CONTRIBUTING.md +2 -2
  3. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/PKG-INFO +26 -8
  4. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/README.md +25 -7
  5. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/__init__.py +1 -1
  6. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/css/admin.css +135 -54
  7. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/js/admin.js +98 -6
  8. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/base.html +52 -50
  9. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/login.html +0 -1
  10. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin.egg-info/PKG-INFO +26 -8
  11. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/test_admin_ui.py +13 -1
  12. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/test_package.py +18 -0
  13. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/LICENSE +0 -0
  14. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/MANIFEST.in +0 -0
  15. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/NOTICE +0 -0
  16. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/pyproject.toml +0 -0
  17. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/setup.cfg +0 -0
  18. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/apps.py +0 -0
  19. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/css/widgets-overrides.css +0 -0
  20. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/js/theme.js +0 -0
  21. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/vendor/bootstrap/LICENSE +0 -0
  22. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/vendor/bootstrap/css/bootstrap.min.css +0 -0
  23. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/static/django_bootstrap6_admin/vendor/bootstrap/js/bootstrap.bundle.min.js +0 -0
  24. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/404.html +0 -0
  25. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/500.html +0 -0
  26. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/actions.html +0 -0
  27. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/app_index.html +0 -0
  28. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/app_list.html +0 -0
  29. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/auth/user/add_form.html +0 -0
  30. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/auth/user/change_password.html +0 -0
  31. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/base_site.html +0 -0
  32. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/change_form.html +0 -0
  33. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/change_form_object_tools.html +0 -0
  34. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/change_list.html +0 -0
  35. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/change_list_object_tools.html +0 -0
  36. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/change_list_results.html +0 -0
  37. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/color_theme_toggle.html +0 -0
  38. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/date_hierarchy.html +0 -0
  39. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/delete_confirmation.html +0 -0
  40. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/delete_selected_confirmation.html +0 -0
  41. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/edit_inline/stacked.html +0 -0
  42. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/edit_inline/tabular.html +0 -0
  43. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/filter.html +0 -0
  44. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/includes/fieldset.html +0 -0
  45. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/includes/object_delete_summary.html +0 -0
  46. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/index.html +0 -0
  47. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/nav_sidebar.html +0 -0
  48. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/object_history.html +0 -0
  49. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/pagination.html +0 -0
  50. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/search_form.html +0 -0
  51. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/admin/submit_line.html +0 -0
  52. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/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.1}/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.1}/src/django_bootstrap6_admin/templates/registration/logged_out.html +0 -0
  55. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/registration/password_change_done.html +0 -0
  56. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templates/registration/password_change_form.html +0 -0
  57. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templatetags/__init__.py +0 -0
  58. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin/templatetags/bootstrap6_admin.py +0 -0
  59. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin.egg-info/SOURCES.txt +0 -0
  60. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin.egg-info/dependency_links.txt +0 -0
  61. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin.egg-info/requires.txt +0 -0
  62. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/src/django_bootstrap6_admin.egg-info/top_level.txt +0 -0
  63. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/__init__.py +0 -0
  64. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/js/theme_toggle.test.js +0 -0
  65. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/settings.py +0 -0
  66. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/smoke_wheel.py +0 -0
  67. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/test_templatetags.py +0 -0
  68. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/testapp/__init__.py +0 -0
  69. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/testapp/admin.py +0 -0
  70. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/testapp/apps.py +0 -0
  71. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/testapp/models.py +0 -0
  72. {django_bootstrap6_admin-0.1.2 → django_bootstrap6_admin-0.2.1}/tests/urls.py +0 -0
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-11-15
11
+
12
+ ### Added
13
+
14
+ - The "View site" link is now always visible in the admin top bar instead of
15
+ being hidden in the user menu.
16
+
17
+ ### Changed
18
+
19
+ - Refreshed the palette and surface treatment to match the warm near-black and
20
+ yellow visual language of the Pardus Teknofest project.
21
+ - Updated the login screen brand mark and browser theme colour to the new
22
+ palette.
23
+ - Documented that the admin follows Django's active language instead of being
24
+ hard-coded to English.
25
+
26
+ ## [0.2.0] - 2026-11-15
27
+
28
+ ### Added
29
+
30
+ - The desktop filter panel can now be collapsed and expanded, and the
31
+ collapsed state is remembered between changelist pages.
32
+ - The "Show counts" / "Hide counts" choice is remembered across changelist
33
+ pages instead of resetting on navigation.
34
+ - Subtle vertical rules separate columns in changelists, inline tables and the
35
+ object history table.
36
+
37
+ ### Fixed
38
+
39
+ - Removed the duplicate native dropdown arrow on the bulk-action select.
40
+ - Improved dark-theme text contrast for secondary labels, table headers,
41
+ filter links, help text and links.
42
+
10
43
  ## [0.1.2] - 2026-11-15
11
44
 
12
45
  ### Fixed
@@ -71,8 +71,8 @@ environment and compiles every bundled template as a smoke test.
71
71
  5. Create and push an annotated tag:
72
72
 
73
73
  ```bash
74
- git tag -a v0.1.0 -m "django-bootstrap6-admin 0.1.0"
75
- git push origin v0.1.0
74
+ git tag -a v0.2.1 -m "django-bootstrap6-admin 0.2.1"
75
+ git push origin v0.2.1
76
76
  ```
77
77
 
78
78
  6. The `Release` workflow builds the distributions, verifies that the tag
@@ -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.1
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>
@@ -53,16 +53,23 @@ permissions and JavaScript remain in charge.
53
53
  ## Features
54
54
 
55
55
  - Bootstrap 6 markup and design tokens throughout the admin.
56
+ - "View site" is always available in the top bar, not buried in the user menu.
56
57
  - Sticky top bar with a collapsible sidebar (desktop) and drawer navigation
57
58
  with a backdrop (mobile).
58
59
  - Dashboard with application cards, model shortcuts and recent activity.
59
60
  - Changelist with search, date hierarchy, collapsible filters, sortable
60
- headers, bulk actions and Bootstrap pagination.
61
+ headers, bulk actions and Bootstrap pagination. The desktop filter panel can
62
+ be collapsed, and both its state and the "Show counts" choice are remembered
63
+ between pages.
64
+ - Subtle vertical rules separate columns in changelists, inline tables and the
65
+ object history.
61
66
  - Change form with card-based fieldsets, collapsible sections, a clear save
62
67
  bar, tabular/stacked inlines and related-object popups.
63
68
  - Modern login, logout and password change pages.
64
69
  - Light, dark and system themes. The existing Django `theme` localStorage
65
70
  preference is synchronised with Bootstrap's `data-bs-theme` attribute.
71
+ - Dark-theme secondary text, table headers, filter links and help text use
72
+ higher-contrast tokens for better readability.
66
73
  - Soft fade-in/fade-out transition when switching themes, skipped
67
74
  automatically for users who prefer reduced motion.
68
75
  - Every click changes the visible theme: if `auto` would resolve to the theme
@@ -75,8 +82,8 @@ permissions and JavaScript remain in charge.
75
82
  - CSP-aware: bundled scripts and styles use Django 6.1's
76
83
  `{% csp_nonce_attr %}` tag, so nonce-based policies work out of the box.
77
84
  - Generic by design: no models, no migrations, no project-specific imports and
78
- no assumptions about the host project's branding. The default navy/gold
79
- palette is only a set of overridable CSS variables.
85
+ no assumptions about the host project's branding. The default warm
86
+ near-black/yellow palette is only a set of overridable CSS variables.
80
87
 
81
88
  ## Requirements
82
89
 
@@ -188,14 +195,15 @@ custom admin CSS file:
188
195
 
189
196
  ```css
190
197
  :root {
191
- --ba-topbar-bg: #1f2937;
192
- --ba-gold: #f59e0b;
198
+ --ba-topbar-bg: #231f20;
199
+ --ba-gold: #ffcb08;
200
+ --ba-canvas: #f6f6f4;
193
201
  --ba-sidebar-width: 300px;
194
202
  }
195
203
  ```
196
204
 
197
- The default primary and danger colours are deliberately deeper than Bootstrap
198
- 6's defaults so white button text meets WCAG AA contrast.
205
+ The default primary is the warm yellow accent with dark text, and the danger
206
+ colour remains a deep red for readable white labels.
199
207
 
200
208
  The select controls use small `data:` SVG arrows. If you enable a strict CSP,
201
209
  allow `img-src 'self' data:` (or replace the arrows in your own CSS).
@@ -223,6 +231,16 @@ There is no runtime state to clean up.
223
231
  - `bootstrap.bundle.min.js`:
224
232
  `47ea447f46e288dd9d3e83147b7ba539c6df5192c047b8f91739cd56a5721148`
225
233
 
234
+ ### Language
235
+
236
+ Django's admin is translated using the active language; it is not fixed to
237
+ English. With `LANGUAGE_CODE = "tr"`, Django's own admin labels are Turkish,
238
+ and this theme follows the same project language. The package's source strings
239
+ are English and are translated when a matching locale is installed. To keep
240
+ the admin in English while the public site uses another language, activate
241
+ English for `/admin/` in your project (for example with a small middleware),
242
+ or set `LANGUAGE_CODE = "en-us"` if the whole site should be English.
243
+
226
244
  ## Development
227
245
 
228
246
  The test suite uses an isolated settings module and a small demo app. Create a
@@ -16,16 +16,23 @@ permissions and JavaScript remain in charge.
16
16
  ## Features
17
17
 
18
18
  - Bootstrap 6 markup and design tokens throughout the admin.
19
+ - "View site" is always available in the top bar, not buried in the user menu.
19
20
  - Sticky top bar with a collapsible sidebar (desktop) and drawer navigation
20
21
  with a backdrop (mobile).
21
22
  - Dashboard with application cards, model shortcuts and recent activity.
22
23
  - Changelist with search, date hierarchy, collapsible filters, sortable
23
- headers, bulk actions and Bootstrap pagination.
24
+ headers, bulk actions and Bootstrap pagination. The desktop filter panel can
25
+ be collapsed, and both its state and the "Show counts" choice are remembered
26
+ between pages.
27
+ - Subtle vertical rules separate columns in changelists, inline tables and the
28
+ object history.
24
29
  - Change form with card-based fieldsets, collapsible sections, a clear save
25
30
  bar, tabular/stacked inlines and related-object popups.
26
31
  - Modern login, logout and password change pages.
27
32
  - Light, dark and system themes. The existing Django `theme` localStorage
28
33
  preference is synchronised with Bootstrap's `data-bs-theme` attribute.
34
+ - Dark-theme secondary text, table headers, filter links and help text use
35
+ higher-contrast tokens for better readability.
29
36
  - Soft fade-in/fade-out transition when switching themes, skipped
30
37
  automatically for users who prefer reduced motion.
31
38
  - Every click changes the visible theme: if `auto` would resolve to the theme
@@ -38,8 +45,8 @@ permissions and JavaScript remain in charge.
38
45
  - CSP-aware: bundled scripts and styles use Django 6.1's
39
46
  `{% csp_nonce_attr %}` tag, so nonce-based policies work out of the box.
40
47
  - Generic by design: no models, no migrations, no project-specific imports and
41
- no assumptions about the host project's branding. The default navy/gold
42
- palette is only a set of overridable CSS variables.
48
+ no assumptions about the host project's branding. The default warm
49
+ near-black/yellow palette is only a set of overridable CSS variables.
43
50
 
44
51
  ## Requirements
45
52
 
@@ -151,14 +158,15 @@ custom admin CSS file:
151
158
 
152
159
  ```css
153
160
  :root {
154
- --ba-topbar-bg: #1f2937;
155
- --ba-gold: #f59e0b;
161
+ --ba-topbar-bg: #231f20;
162
+ --ba-gold: #ffcb08;
163
+ --ba-canvas: #f6f6f4;
156
164
  --ba-sidebar-width: 300px;
157
165
  }
158
166
  ```
159
167
 
160
- The default primary and danger colours are deliberately deeper than Bootstrap
161
- 6's defaults so white button text meets WCAG AA contrast.
168
+ The default primary is the warm yellow accent with dark text, and the danger
169
+ colour remains a deep red for readable white labels.
162
170
 
163
171
  The select controls use small `data:` SVG arrows. If you enable a strict CSP,
164
172
  allow `img-src 'self' data:` (or replace the arrows in your own CSS).
@@ -186,6 +194,16 @@ There is no runtime state to clean up.
186
194
  - `bootstrap.bundle.min.js`:
187
195
  `47ea447f46e288dd9d3e83147b7ba539c6df5192c047b8f91739cd56a5721148`
188
196
 
197
+ ### Language
198
+
199
+ Django's admin is translated using the active language; it is not fixed to
200
+ English. With `LANGUAGE_CODE = "tr"`, Django's own admin labels are Turkish,
201
+ and this theme follows the same project language. The package's source strings
202
+ are English and are translated when a matching locale is installed. To keep
203
+ the admin in English while the public site uses another language, activate
204
+ English for `/admin/` in your project (for example with a small middleware),
205
+ or set `LANGUAGE_CODE = "en-us"` if the whole site should be English.
206
+
189
207
  ## Development
190
208
 
191
209
  The test suite uses an isolated settings module and a small demo app. Create a
@@ -1,3 +1,3 @@
1
1
  """Bootstrap 6 skin for the Django admin."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.2.1"
@@ -12,42 +12,45 @@
12
12
  --ba-sidebar-width: 272px;
13
13
  --ba-content-max: 1600px;
14
14
 
15
- --ba-topbar-bg: light-dark(#172b45, #0c1520);
16
- --ba-topbar-fg: light-dark(#f3f7fb, #e5edf6);
17
- --ba-topbar-muted: light-dark(#aebed0, #9fb0c2);
18
- --ba-canvas: light-dark(#f4f6f9, #0d141c);
19
- --ba-surface: light-dark(#ffffff, #151f2b);
20
- --ba-surface-muted: light-dark(#f8fafc, #111a24);
21
- --ba-border: light-dark(#dce3eb, #2d3a4b);
22
- --ba-border-strong: light-dark(#c6d0dc, #3b4b5f);
23
- --ba-gold: light-dark(#c99b54, #d6b46f);
24
- --ba-shadow-sm: 0 1px 2px rgb(23 43 69 / 0.06);
25
- --ba-shadow-md: 0 0.5rem 1.5rem rgb(23 43 69 / 0.08);
26
- --ba-shadow-lg: 0 1rem 3rem rgb(23 43 69 / 0.16);
27
- --ba-radius: var(--bs-radius-7);
28
- --ba-radius-sm: var(--bs-radius-5);
29
- --ba-text-muted: light-dark(#536274, #b3bfcd);
30
- --ba-control-border: light-dark(#7f8d9d, #6b7d91);
15
+ --ba-topbar-bg: light-dark(#231f20, #242122);
16
+ --ba-topbar-fg: light-dark(#f6f4f4, #f2efef);
17
+ --ba-topbar-muted: light-dark(#d8d3d2, #c6c0c0);
18
+ --ba-canvas: light-dark(#f6f6f4, #191718);
19
+ --ba-surface: light-dark(#ffffff, #222021);
20
+ --ba-surface-muted: light-dark(#fafaf8, #292627);
21
+ --ba-border: light-dark(#e3e3df, #3a3637);
22
+ --ba-border-strong: light-dark(#cfcfc8, #4a4546);
23
+ --ba-gold: light-dark(#ffcb08, #d8ab32);
24
+ --ba-shadow-sm: 0 1px 2px rgb(35 31 32 / 0.05);
25
+ --ba-shadow-md: 0 0.35rem 1rem rgb(35 31 32 / 0.07);
26
+ --ba-shadow-lg: 0 0.75rem 2rem rgb(35 31 32 / 0.12);
27
+ --ba-radius: 6px;
28
+ --ba-radius-sm: 4px;
29
+ --ba-text-muted: light-dark(#65605f, #c6c0c0);
30
+ --ba-control-border: light-dark(#b8b3ad, #6b6464);
31
31
 
32
32
  /*
33
- * Bootstrap 6's default primary is a very bright blue; white text on it
34
- * only reaches ~3.5:1. Use a deeper, accessible blue for light mode and a
35
- * slightly lighter one for dark mode.
33
+ * The Pardus Teknofest palette uses a warm near-black surface and a
34
+ * saturated yellow accent with dark text. Keep the primary role usable as
35
+ * an accent rather than a blue action colour.
36
36
  */
37
- --bs-primary-base: light-dark(#315e9b, #3f6ea8);
38
- --bs-primary-bg: light-dark(#315e9b, #3f6ea8);
39
- --bs-primary-contrast: #fff;
40
- --bs-primary-fg: light-dark(#244a7d, #a9c8ec);
41
- --bs-primary-fg-emphasis: light-dark(#1d3c66, #cfe1f7);
42
- --bs-primary-bg-subtle: light-dark(#eaf0f7, #23364b);
43
- --bs-primary-bg-muted: light-dark(#d9e4f1, #2f4a66);
44
- --bs-primary-border: light-dark(#6688b0, #6f95b8);
45
- --bs-secondary-border: light-dark(#8a97a6, #6b7d91);
37
+ --bs-primary-base: light-dark(#ffcb08, #d8ab32);
38
+ --bs-primary-bg: light-dark(#ffcb08, #d8ab32);
39
+ --bs-primary-contrast: #231f20;
40
+ --bs-primary-fg: light-dark(#7a5a00, #e8c66e);
41
+ --bs-primary-fg-emphasis: light-dark(#5f4600, #f3d98f);
42
+ --bs-primary-bg-subtle: light-dark(#fff6d6, #342c18);
43
+ --bs-primary-bg-muted: light-dark(#ffeaa1, #4a3d1e);
44
+ --bs-primary-border: light-dark(#d4a900, #8a6a19);
45
+ --bs-secondary-border: light-dark(#b8b3ad, #6b6464);
46
46
  --bs-primary-focus-ring: color-mix(
47
47
  in oklch,
48
- var(--bs-primary-bg) 55%,
49
- var(--bs-bg-body)
48
+ var(--bs-primary-bg) 45%,
49
+ transparent
50
50
  );
51
+ --bs-link-color: light-dark(#8a6500, #e8c66e);
52
+ --bs-link-hover-color: light-dark(#6f5000, #f3d98f);
53
+ --bs-border-muted: light-dark(#e3e3df, #3a3637);
51
54
 
52
55
  --bs-danger-base: light-dark(#b4233a, #c23a53);
53
56
  --bs-danger-bg: light-dark(#b4233a, #c23a53);
@@ -62,8 +65,8 @@
62
65
  var(--bs-danger-bg) 55%,
63
66
  var(--bs-bg-body)
64
67
  );
65
- --ba-select-arrow: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23667085' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
66
- --ba-select-arrow-dark: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23aab6c5' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
68
+ --ba-select-arrow: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%237a7472' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
69
+ --ba-select-arrow-dark: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23c6c0c0' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
67
70
 
68
71
  /* Django admin CSS variables used by widgets.css / autocomplete.css. */
69
72
  --primary: var(--bs-primary-bg);
@@ -111,6 +114,23 @@
111
114
  2. Base
112
115
  -------------------------------------------------------------------------- */
113
116
 
117
+ /*
118
+ * Bootstrap's dark gray-500/600 secondary text is too dim on the deep navy
119
+ * admin surfaces, especially at the small sizes used by filters, table
120
+ * headers and action help. Lift those tokens for the effective dark theme.
121
+ */
122
+ [data-bs-theme="dark"],
123
+ [data-effective-theme="dark"] {
124
+ --bs-fg-1: #f2efef;
125
+ --bs-fg-2: #dedada;
126
+ --bs-fg-3: #c6c0c0;
127
+ --bs-fg-4: #b0aaa9;
128
+ --ba-text-muted: #c6c0c0;
129
+ --bs-link-color: #e8c66e;
130
+ --bs-link-hover-color: #f3d98f;
131
+ --bs-border-muted: #3a3637;
132
+ }
133
+
114
134
  html {
115
135
  min-height: 100%;
116
136
  }
@@ -353,14 +373,9 @@ samp {
353
373
  inset-inline-start: 0;
354
374
  width: 100%;
355
375
  height: 3px;
356
- background: linear-gradient(
357
- 90deg,
358
- var(--ba-gold) 0,
359
- var(--ba-gold) 4.5rem,
360
- transparent 4.5rem
361
- );
376
+ background: var(--ba-gold);
362
377
  content: "";
363
- opacity: 0.9;
378
+ opacity: 1;
364
379
  pointer-events: none;
365
380
  }
366
381
 
@@ -421,13 +436,12 @@ samp {
421
436
  place-items: center;
422
437
  width: 2.35rem;
423
438
  height: 2.35rem;
424
- border-radius: 0.65rem;
425
- background: #fff;
426
- color: var(--ba-topbar-bg);
439
+ border-radius: var(--ba-radius-sm);
440
+ background: var(--ba-gold);
441
+ color: #231f20;
427
442
  font-size: 1.05rem;
428
443
  font-weight: 800;
429
444
  line-height: 1;
430
- box-shadow: 0 2px 0 rgb(0 0 0 / 0.12);
431
445
  }
432
446
 
433
447
  .ba-brand-text {
@@ -462,6 +476,32 @@ samp {
462
476
  margin-inline-start: auto;
463
477
  }
464
478
 
479
+ .ba-view-site {
480
+ --bs-btn-color: var(--ba-topbar-fg);
481
+ --bs-btn-bg: transparent;
482
+ --bs-btn-border-color: rgb(255 255 255 / 0.22);
483
+ --bs-btn-hover-bg: rgb(255 255 255 / 0.1);
484
+ --bs-btn-hover-color: #fff;
485
+ --bs-btn-hover-border-color: rgb(255 255 255 / 0.38);
486
+ --bs-btn-active-bg: rgb(255 255 255 / 0.16);
487
+ --bs-btn-active-color: #fff;
488
+ display: inline-flex;
489
+ align-items: center;
490
+ justify-content: center;
491
+ gap: 0.35rem;
492
+ min-height: 2.35rem;
493
+ padding: 0.25rem 0.7rem;
494
+ border-radius: var(--ba-radius-sm);
495
+ font-size: 0.8rem;
496
+ font-weight: 600;
497
+ white-space: nowrap;
498
+ }
499
+
500
+ .ba-view-site .ba-icon {
501
+ width: 0.9rem;
502
+ height: 0.9rem;
503
+ }
504
+
465
505
  .ba-theme-toggle {
466
506
  --bs-btn-color: var(--ba-topbar-fg);
467
507
  --bs-btn-bg: transparent;
@@ -473,7 +513,7 @@ samp {
473
513
  --bs-btn-active-color: #fff;
474
514
  width: 2.35rem;
475
515
  height: 2.35rem;
476
- border-radius: 50%;
516
+ border-radius: var(--ba-radius-sm);
477
517
  }
478
518
 
479
519
  #branding > .ba-theme-toggle {
@@ -522,7 +562,7 @@ html[data-effective-theme="dark"] .theme-icon-when-dark {
522
562
  --bs-btn-active-color: #fff;
523
563
  min-height: 2.35rem;
524
564
  padding: 0.25rem 0.55rem 0.25rem 0.3rem;
525
- border-radius: 999px;
565
+ border-radius: var(--ba-radius-sm);
526
566
  }
527
567
 
528
568
  .ba-user-avatar {
@@ -533,7 +573,7 @@ html[data-effective-theme="dark"] .theme-icon-when-dark {
533
573
  height: 1.8rem;
534
574
  border-radius: 50%;
535
575
  background: var(--ba-gold);
536
- color: #172b45;
576
+ color: #231f20;
537
577
  font-size: 0.78rem;
538
578
  font-weight: 800;
539
579
  }
@@ -1365,7 +1405,7 @@ a.ba-timeline-object:focus {
1365
1405
  }
1366
1406
 
1367
1407
  .ba-filters-toggle-row {
1368
- display: none;
1408
+ display: flex;
1369
1409
  }
1370
1410
 
1371
1411
  .ba-filters-toggle {
@@ -1551,6 +1591,20 @@ a.ba-date-chip:focus {
1551
1591
  border-block-end: 0;
1552
1592
  }
1553
1593
 
1594
+ /* Very light column rules keep dense admin tables readable without making
1595
+ * every cell look boxed in. */
1596
+ #result_list thead th:not(:last-child),
1597
+ #result_list tbody th:not(:last-child),
1598
+ #result_list tbody td:not(:last-child),
1599
+ .ba-inline-table thead th:not(:last-child),
1600
+ .ba-inline-table tbody th:not(:last-child),
1601
+ .ba-inline-table tbody td:not(:last-child),
1602
+ .ba-history-table thead th:not(:last-child),
1603
+ .ba-history-table tbody th:not(:last-child),
1604
+ .ba-history-table tbody td:not(:last-child) {
1605
+ border-inline-end: 1px solid var(--bs-border-muted);
1606
+ }
1607
+
1554
1608
  #result_list tbody tr.selected > * {
1555
1609
  background: var(--bs-primary-bg-subtle) !important;
1556
1610
  }
@@ -1597,13 +1651,15 @@ a.ba-date-chip:focus {
1597
1651
 
1598
1652
  #result_list select {
1599
1653
  padding-inline-end: 1.6rem;
1654
+ appearance: none;
1600
1655
  background-image: var(--ba-select-arrow);
1601
1656
  background-position: right 0.45rem center;
1602
1657
  background-repeat: no-repeat;
1603
1658
  background-size: 0.65rem;
1604
1659
  }
1605
1660
 
1606
- [data-bs-theme="dark"] #result_list select {
1661
+ [data-bs-theme="dark"] #result_list select,
1662
+ [data-effective-theme="dark"] #result_list select {
1607
1663
  background-image: var(--ba-select-arrow-dark);
1608
1664
  }
1609
1665
 
@@ -2177,6 +2233,7 @@ label.required::after {
2177
2233
  .ba-form-group select,
2178
2234
  .ba-actions select {
2179
2235
  padding-inline-end: 2rem;
2236
+ appearance: none;
2180
2237
  background-image: var(--ba-select-arrow);
2181
2238
  background-position: right 0.7rem center;
2182
2239
  background-repeat: no-repeat;
@@ -2185,7 +2242,10 @@ label.required::after {
2185
2242
 
2186
2243
  [data-bs-theme="dark"] .ba-field select,
2187
2244
  [data-bs-theme="dark"] .ba-form-group select,
2188
- [data-bs-theme="dark"] .ba-actions select {
2245
+ [data-bs-theme="dark"] .ba-actions select,
2246
+ [data-effective-theme="dark"] .ba-field select,
2247
+ [data-effective-theme="dark"] .ba-form-group select,
2248
+ [data-effective-theme="dark"] .ba-actions select {
2189
2249
  background-image: var(--ba-select-arrow-dark);
2190
2250
  }
2191
2251
 
@@ -2551,13 +2611,15 @@ div.help ul {
2551
2611
 
2552
2612
  .ba-inline-table select {
2553
2613
  padding-inline-end: 1.6rem;
2614
+ appearance: none;
2554
2615
  background-image: var(--ba-select-arrow);
2555
2616
  background-position: right 0.45rem center;
2556
2617
  background-repeat: no-repeat;
2557
2618
  background-size: 0.65rem;
2558
2619
  }
2559
2620
 
2560
- [data-bs-theme="dark"] .ba-inline-table select {
2621
+ [data-bs-theme="dark"] .ba-inline-table select,
2622
+ [data-effective-theme="dark"] .ba-inline-table select {
2561
2623
  background-image: var(--ba-select-arrow-dark);
2562
2624
  }
2563
2625
 
@@ -2905,7 +2967,7 @@ body.login .ba-topbar-inner {
2905
2967
  padding: 2.5rem;
2906
2968
  border-radius: calc(var(--ba-radius) + 0.25rem);
2907
2969
  background:
2908
- radial-gradient(circle at 18% 18%, rgb(201 155 84 / 0.2), transparent 42%),
2970
+ radial-gradient(circle at 18% 18%, rgb(255 203 8 / 0.18), transparent 42%),
2909
2971
  var(--ba-topbar-bg);
2910
2972
  color: var(--ba-topbar-fg);
2911
2973
  box-shadow: var(--ba-shadow-lg);
@@ -2927,9 +2989,9 @@ body.login .ba-topbar-inner {
2927
2989
  place-items: center;
2928
2990
  width: 3rem;
2929
2991
  height: 3rem;
2930
- border-radius: 0.8rem;
2931
- background: #fff;
2932
- color: var(--ba-topbar-bg);
2992
+ border-radius: var(--ba-radius);
2993
+ background: var(--ba-gold);
2994
+ color: #231f20;
2933
2995
  font-size: 1.3rem;
2934
2996
  font-weight: 800;
2935
2997
  }
@@ -3202,6 +3264,16 @@ body.popup .ba-submit-row {
3202
3264
  13. Responsive
3203
3265
  -------------------------------------------------------------------------- */
3204
3266
 
3267
+ @media (min-width: 1200px) {
3268
+ #changelist.ba-filters-collapsed .ba-changelist-layout {
3269
+ grid-template-columns: minmax(0, 1fr);
3270
+ }
3271
+
3272
+ #changelist.ba-filters-collapsed .ba-filters {
3273
+ display: none;
3274
+ }
3275
+ }
3276
+
3205
3277
  @media (max-width: 1199.98px) {
3206
3278
  .ba-dashboard {
3207
3279
  grid-template-columns: minmax(0, 1fr);
@@ -3346,6 +3418,15 @@ body.popup .ba-submit-row {
3346
3418
  padding-inline: 0.25rem;
3347
3419
  }
3348
3420
 
3421
+ .ba-view-site {
3422
+ width: 2.35rem;
3423
+ padding: 0;
3424
+ }
3425
+
3426
+ .ba-view-site-label {
3427
+ display: none;
3428
+ }
3429
+
3349
3430
  .ba-brand-title {
3350
3431
  max-width: 11rem;
3351
3432
  font-size: 0.9rem;
@@ -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();
@@ -6,8 +6,8 @@
6
6
  <title>{% block title %}{% endblock %}</title>
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
8
  <meta name="color-scheme" content="light dark">
9
- <meta name="theme-color" content="#172b45" media="(prefers-color-scheme: light)">
10
- <meta name="theme-color" content="#0c1520" media="(prefers-color-scheme: dark)">
9
+ <meta name="theme-color" content="#231f20" media="(prefers-color-scheme: light)">
10
+ <meta name="theme-color" content="#242122" media="(prefers-color-scheme: dark)">
11
11
  <script src="{% static 'django_bootstrap6_admin/js/theme.js' %}" {% csp_nonce_attr %}></script>
12
12
  <link rel="stylesheet" href="{% static 'django_bootstrap6_admin/vendor/bootstrap/css/bootstrap.min.css' %}" {% csp_nonce_attr %}>
13
13
  <link rel="stylesheet" href="{% static 'admin/css/widgets.css' %}" {% csp_nonce_attr %}>
@@ -48,58 +48,60 @@
48
48
  </div>
49
49
 
50
50
  {% block usertools %}
51
- {% if has_permission %}
51
+ {% if site_url or has_permission %}
52
52
  <div id="user-tools" class="ba-user-tools">
53
- {% include "admin/color_theme_toggle.html" %}
54
- {% firstof user.get_short_name user.get_username as user_name %}
55
- <div class="ba-user-menu-wrap">
56
- <button type="button" class="btn btn-outline theme-secondary ba-user-menu-button"
57
- data-bs-toggle="menu" data-bs-target="#ba-user-menu"
58
- aria-expanded="false" aria-haspopup="true">
59
- <span class="visually-hidden">{% translate 'User menu' %}</span>
60
- <span class="ba-user-avatar" aria-hidden="true">{{ user_name|first|upper }}</span>
61
- <span class="ba-user-name">{% firstof user.get_short_name user.get_username %}</span>
62
- <svg class="ba-icon ba-chevron" aria-hidden="true"><use href="#ba-chevron-down"></use></svg>
63
- </button>
64
- <div class="menu ba-user-menu" id="ba-user-menu">
65
- {% block welcome-msg %}
66
- <div class="menu-header">
67
- {% translate 'Welcome,' %} <strong>{% firstof user.get_short_name user.get_username %}</strong>.
68
- </div>
69
- {% endblock %}
70
- {% block userlinks %}
71
- {% if site_url %}
72
- <a class="menu-item" href="{{ site_url }}">
73
- <svg class="ba-icon" aria-hidden="true"><use href="#ba-external"></use></svg>
74
- {% translate 'View site' %}
75
- </a>
76
- {% endif %}
77
- {% if user.is_active and user.is_staff %}
78
- {% url 'django-admindocs-docroot' as docsroot %}
79
- {% if docsroot %}
80
- <a class="menu-item" href="{{ docsroot }}">
81
- <svg class="ba-icon" aria-hidden="true"><use href="#ba-document"></use></svg>
82
- {% translate 'Documentation' %}
53
+ {% if site_url %}
54
+ <a class="btn btn-outline ba-view-site" href="{{ site_url }}">
55
+ <svg class="ba-icon" aria-hidden="true"><use href="#ba-external"></use></svg>
56
+ <span class="ba-view-site-label">{% translate 'View site' %}</span>
57
+ </a>
58
+ {% endif %}
59
+ {% if has_permission %}
60
+ {% include "admin/color_theme_toggle.html" %}
61
+ {% firstof user.get_short_name user.get_username as user_name %}
62
+ <div class="ba-user-menu-wrap">
63
+ <button type="button" class="btn btn-outline theme-secondary ba-user-menu-button"
64
+ data-bs-toggle="menu" data-bs-target="#ba-user-menu"
65
+ aria-expanded="false" aria-haspopup="true">
66
+ <span class="visually-hidden">{% translate 'User menu' %}</span>
67
+ <span class="ba-user-avatar" aria-hidden="true">{{ user_name|first|upper }}</span>
68
+ <span class="ba-user-name">{% firstof user.get_short_name user.get_username %}</span>
69
+ <svg class="ba-icon ba-chevron" aria-hidden="true"><use href="#ba-chevron-down"></use></svg>
70
+ </button>
71
+ <div class="menu ba-user-menu" id="ba-user-menu">
72
+ {% block welcome-msg %}
73
+ <div class="menu-header">
74
+ {% translate 'Welcome,' %} <strong>{% firstof user.get_short_name user.get_username %}</strong>.
75
+ </div>
76
+ {% endblock %}
77
+ {% block userlinks %}
78
+ {% if user.is_active and user.is_staff %}
79
+ {% url 'django-admindocs-docroot' as docsroot %}
80
+ {% if docsroot %}
81
+ <a class="menu-item" href="{{ docsroot }}">
82
+ <svg class="ba-icon" aria-hidden="true"><use href="#ba-document"></use></svg>
83
+ {% translate 'Documentation' %}
84
+ </a>
85
+ {% endif %}
86
+ {% endif %}
87
+ {% if user.has_usable_password %}
88
+ <a class="menu-item" href="{% url 'admin:password_change' %}">
89
+ <svg class="ba-icon" aria-hidden="true"><use href="#ba-key"></use></svg>
90
+ {% translate 'Change password' %}
83
91
  </a>
84
92
  {% endif %}
85
- {% endif %}
86
- {% if user.has_usable_password %}
87
- <a class="menu-item" href="{% url 'admin:password_change' %}">
88
- <svg class="ba-icon" aria-hidden="true"><use href="#ba-key"></use></svg>
89
- {% translate 'Change password' %}
90
- </a>
91
- {% endif %}
92
- <div class="menu-divider"></div>
93
- <form id="logout-form" method="post" action="{% url 'admin:logout' %}">
94
- {% csrf_token %}
95
- <button type="submit" class="menu-item">
96
- <svg class="ba-icon" aria-hidden="true"><use href="#ba-logout"></use></svg>
97
- {% translate 'Log out' %}
98
- </button>
99
- </form>
100
- {% endblock %}
93
+ <div class="menu-divider"></div>
94
+ <form id="logout-form" method="post" action="{% url 'admin:logout' %}">
95
+ {% csrf_token %}
96
+ <button type="submit" class="menu-item">
97
+ <svg class="ba-icon" aria-hidden="true"><use href="#ba-logout"></use></svg>
98
+ {% translate 'Log out' %}
99
+ </button>
100
+ </form>
101
+ {% endblock %}
102
+ </div>
101
103
  </div>
102
- </div>
104
+ {% endif %}
103
105
  </div>
104
106
  {% endif %}
105
107
  {% endblock %}
@@ -5,7 +5,6 @@
5
5
  {% block extrastyle %}{{ block.super }}{% csp_nonce_attr form.media %}{% endblock %}
6
6
  {% block bodyclass %}{{ block.super }} login ba-login-page ba-no-sidebar{% endblock %}
7
7
 
8
- {% block usertools %}{% endblock %}
9
8
  {% block nav-global %}{% endblock %}
10
9
  {% block nav-sidebar %}{% endblock %}
11
10
  {% block nav-breadcrumbs %}{% endblock %}
@@ -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.1
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>
@@ -53,16 +53,23 @@ permissions and JavaScript remain in charge.
53
53
  ## Features
54
54
 
55
55
  - Bootstrap 6 markup and design tokens throughout the admin.
56
+ - "View site" is always available in the top bar, not buried in the user menu.
56
57
  - Sticky top bar with a collapsible sidebar (desktop) and drawer navigation
57
58
  with a backdrop (mobile).
58
59
  - Dashboard with application cards, model shortcuts and recent activity.
59
60
  - Changelist with search, date hierarchy, collapsible filters, sortable
60
- headers, bulk actions and Bootstrap pagination.
61
+ headers, bulk actions and Bootstrap pagination. The desktop filter panel can
62
+ be collapsed, and both its state and the "Show counts" choice are remembered
63
+ between pages.
64
+ - Subtle vertical rules separate columns in changelists, inline tables and the
65
+ object history.
61
66
  - Change form with card-based fieldsets, collapsible sections, a clear save
62
67
  bar, tabular/stacked inlines and related-object popups.
63
68
  - Modern login, logout and password change pages.
64
69
  - Light, dark and system themes. The existing Django `theme` localStorage
65
70
  preference is synchronised with Bootstrap's `data-bs-theme` attribute.
71
+ - Dark-theme secondary text, table headers, filter links and help text use
72
+ higher-contrast tokens for better readability.
66
73
  - Soft fade-in/fade-out transition when switching themes, skipped
67
74
  automatically for users who prefer reduced motion.
68
75
  - Every click changes the visible theme: if `auto` would resolve to the theme
@@ -75,8 +82,8 @@ permissions and JavaScript remain in charge.
75
82
  - CSP-aware: bundled scripts and styles use Django 6.1's
76
83
  `{% csp_nonce_attr %}` tag, so nonce-based policies work out of the box.
77
84
  - Generic by design: no models, no migrations, no project-specific imports and
78
- no assumptions about the host project's branding. The default navy/gold
79
- palette is only a set of overridable CSS variables.
85
+ no assumptions about the host project's branding. The default warm
86
+ near-black/yellow palette is only a set of overridable CSS variables.
80
87
 
81
88
  ## Requirements
82
89
 
@@ -188,14 +195,15 @@ custom admin CSS file:
188
195
 
189
196
  ```css
190
197
  :root {
191
- --ba-topbar-bg: #1f2937;
192
- --ba-gold: #f59e0b;
198
+ --ba-topbar-bg: #231f20;
199
+ --ba-gold: #ffcb08;
200
+ --ba-canvas: #f6f6f4;
193
201
  --ba-sidebar-width: 300px;
194
202
  }
195
203
  ```
196
204
 
197
- The default primary and danger colours are deliberately deeper than Bootstrap
198
- 6's defaults so white button text meets WCAG AA contrast.
205
+ The default primary is the warm yellow accent with dark text, and the danger
206
+ colour remains a deep red for readable white labels.
199
207
 
200
208
  The select controls use small `data:` SVG arrows. If you enable a strict CSP,
201
209
  allow `img-src 'self' data:` (or replace the arrows in your own CSS).
@@ -223,6 +231,16 @@ There is no runtime state to clean up.
223
231
  - `bootstrap.bundle.min.js`:
224
232
  `47ea447f46e288dd9d3e83147b7ba539c6df5192c047b8f91739cd56a5721148`
225
233
 
234
+ ### Language
235
+
236
+ Django's admin is translated using the active language; it is not fixed to
237
+ English. With `LANGUAGE_CODE = "tr"`, Django's own admin labels are Turkish,
238
+ and this theme follows the same project language. The package's source strings
239
+ are English and are translated when a matching locale is installed. To keep
240
+ the admin in English while the public site uses another language, activate
241
+ English for `/admin/` in your project (for example with a small middleware),
242
+ or set `LANGUAGE_CODE = "en-us"` if the whole site should be English.
243
+
226
244
  ## Development
227
245
 
228
246
  The test suite uses an isolated settings module and a small demo app. Create a
@@ -49,6 +49,7 @@ class AdminUiTests(TestCase):
49
49
  self.assertContains(response, "ba-app-grid")
50
50
  self.assertContains(response, "ba-recent-card")
51
51
  self.assertContains(response, "theme-toggle")
52
+ self.assertContains(response, "ba-view-site")
52
53
 
53
54
  def test_default_django_admin_css_is_not_loaded(self):
54
55
  response = self.client.get(reverse("admin:index"))
@@ -76,6 +77,8 @@ class AdminUiTests(TestCase):
76
77
  'id="changelist-search"',
77
78
  'id="searchbar"',
78
79
  'id="changelist-filter"',
80
+ 'id="ba-filters-toggle"',
81
+ 'id="ba-filters-close"',
79
82
  'id="action-toggle"',
80
83
  'id="date-hierarchy-label"',
81
84
  'class="actions ba-actions"',
@@ -119,7 +122,6 @@ class AdminUiTests(TestCase):
119
122
  def test_changelist_search_and_pagination(self):
120
123
  response = self.client.get(reverse("admin:testapp_book_changelist"), {"q": "Django"})
121
124
  self.assertContains(response, "Django for Everyone")
122
-
123
125
  for index in range(105):
124
126
  Book.objects.create(
125
127
  title=f"Extra book {index:03d}",
@@ -134,6 +136,15 @@ class AdminUiTests(TestCase):
134
136
  self.assertContains(response, "?p=1")
135
137
  self.assertContains(response, 'aria-current="page">2')
136
138
 
139
+ def test_facet_count_toggle_links(self):
140
+ url = reverse("admin:testapp_book_changelist")
141
+
142
+ response = self.client.get(url)
143
+ self.assertContains(response, "Show counts")
144
+
145
+ response = self.client.get(url, {"_facets": "1"})
146
+ self.assertContains(response, "Hide counts")
147
+
137
148
  def test_change_form_keeps_widgets_and_submit_contract(self):
138
149
  response = self.client.get(reverse("admin:testapp_book_change", args=[self.book.pk]))
139
150
 
@@ -212,6 +223,7 @@ class AdminUiTests(TestCase):
212
223
  self.assertContains(response, 'type="password"')
213
224
  self.assertContains(response, "theme-toggle")
214
225
  self.assertContains(response, "ba-login")
226
+ self.assertContains(response, "ba-view-site")
215
227
 
216
228
  def test_login_post_works(self):
217
229
  self.client.logout()
@@ -46,3 +46,21 @@ 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: #b0aaa9", admin_css)
65
+ self.assertIn("--ba-topbar-bg: light-dark(#231f20, #242122)", admin_css)
66
+ self.assertIn("--bs-primary-bg: light-dark(#ffcb08, #d8ab32)", admin_css)