ophix-admin-interface 2026.10.4.1__py3-none-any.whl

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 (172) hide show
  1. admin_interface/OPHIX_RELEASE_NOTES.md +679 -0
  2. admin_interface/__init__.py +35 -0
  3. admin_interface/_version.py +2 -0
  4. admin_interface/admin.py +420 -0
  5. admin_interface/apps.py +45 -0
  6. admin_interface/cache.py +2 -0
  7. admin_interface/docs/error_pages.md +188 -0
  8. admin_interface/docs/sections.yaml +3 -0
  9. admin_interface/docs/theme-tools.md +186 -0
  10. admin_interface/fixtures/admin_interface_theme_bootstrap.json +35 -0
  11. admin_interface/fixtures/admin_interface_theme_django.json +35 -0
  12. admin_interface/fixtures/admin_interface_theme_foundation.json +35 -0
  13. admin_interface/fixtures/admin_interface_theme_uswds.json +35 -0
  14. admin_interface/fixtures/initial_data.json +35 -0
  15. admin_interface/locale/ar/LC_MESSAGES/django.mo +0 -0
  16. admin_interface/locale/ar/LC_MESSAGES/django.po +332 -0
  17. admin_interface/locale/de/LC_MESSAGES/django.mo +0 -0
  18. admin_interface/locale/de/LC_MESSAGES/django.po +332 -0
  19. admin_interface/locale/es/LC_MESSAGES/django.mo +0 -0
  20. admin_interface/locale/es/LC_MESSAGES/django.po +334 -0
  21. admin_interface/locale/fa/LC_MESSAGES/django.mo +0 -0
  22. admin_interface/locale/fa/LC_MESSAGES/django.po +329 -0
  23. admin_interface/locale/fr/LC_MESSAGES/django.mo +0 -0
  24. admin_interface/locale/fr/LC_MESSAGES/django.po +332 -0
  25. admin_interface/locale/it/LC_MESSAGES/django.mo +0 -0
  26. admin_interface/locale/it/LC_MESSAGES/django.po +332 -0
  27. admin_interface/locale/pl/LC_MESSAGES/django.mo +0 -0
  28. admin_interface/locale/pl/LC_MESSAGES/django.po +332 -0
  29. admin_interface/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  30. admin_interface/locale/pt_BR/LC_MESSAGES/django.po +331 -0
  31. admin_interface/locale/ru/LC_MESSAGES/django.mo +0 -0
  32. admin_interface/locale/ru/LC_MESSAGES/django.po +333 -0
  33. admin_interface/locale/tr/LC_MESSAGES/django.mo +0 -0
  34. admin_interface/locale/tr/LC_MESSAGES/django.po +335 -0
  35. admin_interface/management/__init__.py +0 -0
  36. admin_interface/management/commands/__init__.py +0 -0
  37. admin_interface/management/commands/delete_theme.py +66 -0
  38. admin_interface/management/commands/export_theme.py +161 -0
  39. admin_interface/management/commands/generate_error_pages.py +160 -0
  40. admin_interface/management/commands/import_theme.py +160 -0
  41. admin_interface/management/commands/list_themes.py +99 -0
  42. admin_interface/management/commands/set_theme.py +87 -0
  43. admin_interface/metadata.py +10 -0
  44. admin_interface/migrations/0001_initial.py +152 -0
  45. admin_interface/migrations/0002_remove_theme_placeholder_fields.py +58 -0
  46. admin_interface/migrations/0003_theme_message_banner_colors.py +128 -0
  47. admin_interface/migrations/0004_theme_message_banner_text_colors.py +128 -0
  48. admin_interface/migrations/0005_theme_update_ophix_defaults.py +344 -0
  49. admin_interface/migrations/0006_remove_theme_env_color.py +24 -0
  50. admin_interface/migrations/0007_theme_css_body_background_color.py +33 -0
  51. admin_interface/migrations/0008_phase_c_theme_fields.py +110 -0
  52. admin_interface/migrations/0009_phase_e_notification_rename.py +117 -0
  53. admin_interface/migrations/0010_theme_css_disabled_color.py +33 -0
  54. admin_interface/migrations/0011_theme_css_body_font_size_default.py +37 -0
  55. admin_interface/migrations/0012_theme_related_modal_opacity_dark.py +37 -0
  56. admin_interface/migrations/0013_alter_theme_css_body_font_size.py +25 -0
  57. admin_interface/migrations/__init__.py +0 -0
  58. admin_interface/models.py +723 -0
  59. admin_interface/settings.py +29 -0
  60. admin_interface/static/admin/js/cancel.js +20 -0
  61. admin_interface/static/admin/js/collapse.js +49 -0
  62. admin_interface/static/admin/js/popup_response.js +48 -0
  63. admin_interface/static/admin_interface/collapsible-inlines/collapsible-inlines.js +48 -0
  64. admin_interface/static/admin_interface/colorfield/colorfield-swatch.js +30 -0
  65. admin_interface/static/admin_interface/css/admin-interface.css +984 -0
  66. admin_interface/static/admin_interface/css/change-form.css +68 -0
  67. admin_interface/static/admin_interface/css/changelist.css +80 -0
  68. admin_interface/static/admin_interface/css/fieldsets.css +14 -0
  69. admin_interface/static/admin_interface/css/file-upload.css +52 -0
  70. admin_interface/static/admin_interface/css/header.css +64 -0
  71. admin_interface/static/admin_interface/css/inlines.css +108 -0
  72. admin_interface/static/admin_interface/css/jquery.ui.tabs.css +243 -0
  73. admin_interface/static/admin_interface/css/language-chooser.css +84 -0
  74. admin_interface/static/admin_interface/css/list-filter-dropdown.css +30 -0
  75. admin_interface/static/admin_interface/css/list-filter.css +43 -0
  76. admin_interface/static/admin_interface/css/login.css +88 -0
  77. admin_interface/static/admin_interface/css/modules.css +17 -0
  78. admin_interface/static/admin_interface/css/nav-sidebar.css +99 -0
  79. admin_interface/static/admin_interface/css/object-tools.css +51 -0
  80. admin_interface/static/admin_interface/css/paginator.css +24 -0
  81. admin_interface/static/admin_interface/css/recent-actions.css +10 -0
  82. admin_interface/static/admin_interface/css/rtl.css +42 -0
  83. admin_interface/static/admin_interface/css/sticky-form-controls.css +114 -0
  84. admin_interface/static/admin_interface/css/tabbed-changeform.css +71 -0
  85. admin_interface/static/admin_interface/css/theme-editor.css +490 -0
  86. admin_interface/static/admin_interface/css/third-party/ckeditor.css +126 -0
  87. admin_interface/static/admin_interface/css/third-party/import-export.css +7 -0
  88. admin_interface/static/admin_interface/css/third-party/json-widget.css +27 -0
  89. admin_interface/static/admin_interface/css/third-party/modeltranslation.css +17 -0
  90. admin_interface/static/admin_interface/css/third-party/rangefilter.css +25 -0
  91. admin_interface/static/admin_interface/css/third-party/sorl-thumbnail.css +67 -0
  92. admin_interface/static/admin_interface/css/third-party/streamfield.css +226 -0
  93. admin_interface/static/admin_interface/css/third-party/tabbed-admin.css +37 -0
  94. admin_interface/static/admin_interface/css/third-party/tinymce.css +3 -0
  95. admin_interface/static/admin_interface/css/widgets.css +327 -0
  96. admin_interface/static/admin_interface/dropdown-filter/dropdown-filter.js +30 -0
  97. admin_interface/static/admin_interface/favico/favico-0.3.10-patched.js +913 -0
  98. admin_interface/static/admin_interface/favico/favico-0.3.10-patched.min.js +1 -0
  99. admin_interface/static/admin_interface/favico/favico-env.js +17 -0
  100. admin_interface/static/admin_interface/foldable-apps/foldable-apps.css +70 -0
  101. admin_interface/static/admin_interface/foldable-apps/foldable-apps.js +36 -0
  102. admin_interface/static/admin_interface/img/icon-calendar.svg +3 -0
  103. admin_interface/static/admin_interface/img/icon-clock.svg +4 -0
  104. admin_interface/static/admin_interface/js/dark-toggle.js +29 -0
  105. admin_interface/static/admin_interface/js/datetime-shortcuts.js +33 -0
  106. admin_interface/static/admin_interface/js/dismiss.js +42 -0
  107. admin_interface/static/admin_interface/js/logo-preview.js +158 -0
  108. admin_interface/static/admin_interface/js/select2-init.js +63 -0
  109. admin_interface/static/admin_interface/js/theme-sections.js +112 -0
  110. admin_interface/static/admin_interface/language-chooser/language-chooser.js +9 -0
  111. admin_interface/static/admin_interface/magnific-popup/jquery.magnific-popup.js +1867 -0
  112. admin_interface/static/admin_interface/magnific-popup/magnific-popup.css +351 -0
  113. admin_interface/static/admin_interface/related-modal/related-modal.css +93 -0
  114. admin_interface/static/admin_interface/related-modal/related-modal.js +182 -0
  115. admin_interface/static/admin_interface/tabbed-changeform/tabbed-changeform.js +87 -0
  116. admin_interface/static/ckeditor/ckeditor/skins/light/LICENSE +21 -0
  117. admin_interface/static/ckeditor/ckeditor/skins/light/README.md +2 -0
  118. admin_interface/static/ckeditor/ckeditor/skins/light/bower.json +13 -0
  119. admin_interface/static/ckeditor/ckeditor/skins/light/dialog.css +5 -0
  120. admin_interface/static/ckeditor/ckeditor/skins/light/editor.css +1348 -0
  121. admin_interface/static/ckeditor/ckeditor/skins/light/editor_gecko.css +6 -0
  122. admin_interface/static/ckeditor/ckeditor/skins/light/editor_ie.css +5 -0
  123. admin_interface/static/ckeditor/ckeditor/skins/light/editor_ie7.css +5 -0
  124. admin_interface/static/ckeditor/ckeditor/skins/light/editor_ie8.css +5 -0
  125. admin_interface/static/ckeditor/ckeditor/skins/light/icons.png +0 -0
  126. admin_interface/static/ckeditor/ckeditor/skins/light/icons_hidpi.png +0 -0
  127. admin_interface/static/ckeditor/ckeditor/skins/light/images/arrow.png +0 -0
  128. admin_interface/static/ckeditor/ckeditor/skins/light/images/close.png +0 -0
  129. admin_interface/static/ckeditor/ckeditor/skins/light/images/hidpi/close.png +0 -0
  130. admin_interface/static/ckeditor/ckeditor/skins/light/images/hidpi/lock-open.png +0 -0
  131. admin_interface/static/ckeditor/ckeditor/skins/light/images/hidpi/lock.png +0 -0
  132. admin_interface/static/ckeditor/ckeditor/skins/light/images/hidpi/refresh.png +0 -0
  133. admin_interface/static/ckeditor/ckeditor/skins/light/images/lock-open.png +0 -0
  134. admin_interface/static/ckeditor/ckeditor/skins/light/images/lock.png +0 -0
  135. admin_interface/static/ckeditor/ckeditor/skins/light/images/refresh.png +0 -0
  136. admin_interface/static/ckeditor/ckeditor/skins/light/skin.js +322 -0
  137. admin_interface/static/streamfield/admin_popup_response.js +24 -0
  138. admin_interface/static/streamfield/js/admin_popup_response.js +24 -0
  139. admin_interface/templates/admin/admin_interface/theme/change_form.html +857 -0
  140. admin_interface/templates/admin/base_site.html +373 -0
  141. admin_interface/templates/admin/change_form.html +107 -0
  142. admin_interface/templates/admin/change_list.html +51 -0
  143. admin_interface/templates/admin/edit_inline/headerless_stacked.html +24 -0
  144. admin_interface/templates/admin/edit_inline/headerless_tabular.html +61 -0
  145. admin_interface/templates/admin/filter.html +25 -0
  146. admin_interface/templates/admin/includes/headerless_fieldset.html +30 -0
  147. admin_interface/templates/admin/object_history.html +14 -0
  148. admin_interface/templates/admin/popup_response.html +14 -0
  149. admin_interface/templates/admin/submit_line.html +12 -0
  150. admin_interface/templates/admin/widgets/split_datetime.html +4 -0
  151. admin_interface/templates/admin_interface/collapsible-inlines.html +5 -0
  152. admin_interface/templates/admin_interface/date_hierarchy_removal_link.html +3 -0
  153. admin_interface/templates/admin_interface/dropdown_filter.html +14 -0
  154. admin_interface/templates/admin_interface/error_pages/error_page.html +219 -0
  155. admin_interface/templates/admin_interface/favicon.html +6 -0
  156. admin_interface/templates/admin_interface/foldable-apps.html +6 -0
  157. admin_interface/templates/admin_interface/language_chooser.html +19 -0
  158. admin_interface/templates/admin_interface/list_filter_removal_link.html +5 -0
  159. admin_interface/templates/admin_interface/related-modal.html +8 -0
  160. admin_interface/templatetags/__init__.py +0 -0
  161. admin_interface/templatetags/admin_interface_tags.py +188 -0
  162. admin_interface/themes/Ophix/media/favicon/ophix-icon-transp-600.png +0 -0
  163. admin_interface/themes/Ophix/media/logo/ophix-logo-transp-darkbg-300.png +0 -0
  164. admin_interface/themes/Ophix/theme.json +150 -0
  165. admin_interface/utils.py +153 -0
  166. admin_interface/validators.py +275 -0
  167. admin_interface/widgets.py +14 -0
  168. ophix_admin_interface-2026.10.4.1.dist-info/METADATA +64 -0
  169. ophix_admin_interface-2026.10.4.1.dist-info/RECORD +172 -0
  170. ophix_admin_interface-2026.10.4.1.dist-info/WHEEL +5 -0
  171. ophix_admin_interface-2026.10.4.1.dist-info/entry_points.txt +2 -0
  172. ophix_admin_interface-2026.10.4.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,679 @@
1
+ # Ophix Admin Interface Release Notes
2
+
3
+ ## 2026.10.04.01
4
+
5
+ - Fixed a `migrate` warning on every fresh install since 2026-08-31: the hand-written
6
+ migration `0011_theme_css_body_font_size_default` recorded an older, shorter
7
+ `help_text` for `Theme.css_body_font_size` than the wording `models.py` was changed
8
+ to in the same commit. Django tracks `help_text` as part of a field's migration
9
+ state, so the mismatch surfaced as "Your models in app(s): 'admin_interface' have
10
+ changes that are not yet reflected in a migration" on every `migrate` run since —
11
+ harmless to the actual schema (no column-level change), but noisy. Added migration
12
+ `0013_alter_theme_css_body_font_size` to bring the recorded state back in sync with
13
+ the model. Swept every other installed app for the same drift (`admin_settings`,
14
+ `ophix_core`, `ophix_tasks`, `ophix_docs`, `ophix_client_management`) and confirmed
15
+ nothing else was affected.
16
+
17
+ ## 2026.09.26.02
18
+
19
+ - Verified real compatibility under Python 3.14 (not just added the classifier) as part of the taskserver-release-wave compatibility sweep, and added `Programming Language :: Python :: 3.14` to the package classifiers.
20
+
21
+ ## 2026.09.26.01
22
+
23
+ - Docs: removed `theme-tools.md`'s entire `set_title` section — that command
24
+ was retired when title moved to `ophix-admin-settings`'s Server Settings
25
+ page (see this file's own earlier "Retired `set_title`" entry), but the doc
26
+ was never updated to match. Title is now set via the Settings admin page
27
+ directly, or restored in bulk via `ophix-admin-settings`'s `import_settings`.
28
+
29
+ ## 2026.09.08.03
30
+
31
+ - Fixed the related-modal opacity dropdowns still overshooting the colour
32
+ picker's right edge after `2026.09.08.01`'s `width: 100%` fix — measured via
33
+ DevTools that Coloris renders its colour input at a fixed 161px border-box
34
+ width (147px content + 6px+6px padding + 1px+1px border), well short of the
35
+ 190px column both share. Set the select's width to the same 161px explicitly
36
+ instead of 100%, so its right edge now lines up with the colour swatch's
37
+ exactly.
38
+
39
+ ## 2026.09.08.02
40
+
41
+ - Redesigned the Related Modal section's three checkboxes ("Active", "Rounded
42
+ corners", "Close button visible") — changed of mind from `2026.09.08.01`'s
43
+ flush-left checkbox+caption layout. Now uses the same `col-label`/`col-light`
44
+ shape as "Background color"/"Background opacity" in the same section: the
45
+ label takes the left column, and the bare checkbox sits left-aligned in the
46
+ value column, lining up with the colour swatch/opacity dropdown above/below
47
+ it. The now-unused `.boolean-row` CSS from the previous two releases (no
48
+ longer referenced by any template) has been removed.
49
+
50
+ ## 2026.09.08.01
51
+
52
+ - Fixed two follow-on issues from `2026.09.07.01`'s theme editor boolean-row fix:
53
+ - The checkbox (e.g. "Active", "Rounded corners") was sitting ~200px in from
54
+ the left instead of flush left like Django's own checkbox fields (e.g.
55
+ Enabled/Paused on a Scheduled Task) — caused by an empty `.col-label`
56
+ spacer reserved for alignment with sibling rows that *do* have a label
57
+ (e.g. "Background opacity"). Boolean rows have no such label, so the
58
+ spacer is now collapsed to zero width for them specifically.
59
+ - The related-modal opacity dropdowns' light and dark values rendered at
60
+ different widths. Colour-picker inputs in these columns get a fixed width
61
+ from their own widget library (Coloris); a plain `<select>` has no such
62
+ enforced width and just auto-sizes, and `.col-dark`'s extra 8px
63
+ `padding-left` was leaving less room for its select than `.col-light`'s.
64
+ Both now get an explicit `width: 100%` so they fill their column
65
+ identically regardless of that padding difference.
66
+
67
+ ## 2026.09.07.01
68
+
69
+ - Theme: added a dark-mode pair for the related-modal background opacity
70
+ (`related_modal_background_opacity_dark_use` + `related_modal_background_opacity_dark`,
71
+ migration `0012`) — previously only the modal background *colour* had a dark
72
+ variant, opacity did not. `_dark_use` defaults to `False`; `_dark` defaults to
73
+ `"0.3"`, matching `related_modal_background_opacity`'s own model default.
74
+ Reverses the earlier "not needed" call on this (marketing-manager item #24)
75
+ now that a wider pool of themes has shown it's actually wanted. Wired through
76
+ the theme editor (folded into the same colour-table row as the background
77
+ colour, rather than the old standalone row) and `base_site.html`'s dark-mode
78
+ CSS variable block; no JS changes needed since `dark-toggle.js` matches
79
+ `*_dark_use`/`*_dark` pairs by field name generically.
80
+ - Fixed theme editor boolean-row checkboxes (e.g. "Active", "Rounded corners",
81
+ "Close button visible") rendering with their caption dropped onto its own
82
+ line below the checkbox, in heavy bold. Root cause: the session-52
83
+ `.aligned label { display: block; min-width: 160px; }` restoration (for
84
+ ordinary Django form fields) leaks into these custom captions too, since the
85
+ editor's fieldsets carry `class="aligned"` for unrelated reasons. Django's
86
+ own `.flex-container.checkbox-row` pattern is immune to this because its
87
+ container is itself a flex row — flex items ignore their own block/inline
88
+ display for layout purposes — but the theme editor's `.col-field` wasn't.
89
+ Made `.boolean-row .col-field` a flex row (same fix, reused) and reset the
90
+ caption's block-sizing and font-weight back to normal, matching
91
+ `.col-label`'s own convention rather than a required-field-style bold label.
92
+
93
+ ## 2026.08.31.01
94
+
95
+ - Theme: `css_body_font_size` now defaults to `14px` instead of blank (migration
96
+ `0011`, which also backfills any existing theme row currently left blank).
97
+ The CSS variable this field drives is only ever emitted when the field is
98
+ non-blank; left blank, `body.admin-interface`'s `font-size` fell back to
99
+ plain CSS `inherit`, which resolves differently depending on what happens to
100
+ be above it in the DOM on a given page — the source of the inconsistent
101
+ 13px/16px mix across pages. A real default means the variable is always
102
+ defined, so every page gets the same size unless a theme explicitly
103
+ overrides it.
104
+ - Help text and validation errors under a field (`.flex-container > .help` /
105
+ `> ul.errorlist`) now indent to start under the field itself, matching the
106
+ input's left edge, instead of starting flush with the container's left edge
107
+ (i.e. under the label). Indented 170px — the label column's own width plus
108
+ right padding (`160px + 10px`, the same value used by `.aligned label` and
109
+ `.datetime-sublabel` elsewhere in this file) — with `flex-basis` reduced by
110
+ the same amount so the added margin doesn't push the row wider than its
111
+ container. Checkbox rows (`.checkbox-row`) are excluded from the indent —
112
+ there's no separate label column there (the checkbox and its label sit
113
+ together at the row's own left edge), so their help text stays flush left.
114
+ - Fixed breadcrumb links always showing the generic link colour instead of
115
+ the theme's module link colour (hover already worked correctly). Root cause
116
+ was a specificity tie, not a Django-version regression: `.admin-interface
117
+ .breadcrumbs a` and the generic `.admin-interface a:link` rule computed to
118
+ identical specificity (`:link` counts in the same tier as a class), so the
119
+ generic rule — declared later in the file — won by cascade order alone.
120
+ Changed the breadcrumbs rule to target `a:link`/`a:visited` explicitly,
121
+ matching the generic rule's own pseudo-classes while keeping the extra
122
+ `.breadcrumbs` class, so it now wins outright rather than depending on
123
+ declaration order.
124
+
125
+ ## 2026.08.30.11
126
+
127
+ - Theme: added a dedicated "Disabled" colour to the Status Colors section
128
+ (`css_disabled_color` + `css_disabled_color_dark_use`/`_dark` pair, migration
129
+ `0010`) — for styling disabled rows/records, distinct from `css_muted_color`
130
+ (ordinary secondary/muted text). Emitted as `--admin-interface-disabled-color`.
131
+ Ophix's own bundled default theme set to `#666666` (darker than the existing
132
+ `#999999` muted colour), adjustable per-theme like any other status colour.
133
+
134
+ ## 2026.08.30.10
135
+
136
+ - `.object-tools` position tuning (structural fix landed in `2026.08.30.08`; this
137
+ is purely the `top`/`right` offset values against `#content`, now that they're
138
+ simple, low-risk numbers rather than a fragile per-page float calibration):
139
+ `2026.08.30.09`'s `top: -15px; right: 10px` tested too high vertically and not
140
+ far enough left horizontally. Tuned live in DevTools against the real page to
141
+ `top: 20px; right: 40px`.
142
+
143
+ ## 2026.08.30.08
144
+
145
+ - Replaced `.object-tools`'s `float`/negative-`margin-top` positioning
146
+ (Django's own long-standing approach, still carried through several rounds of
147
+ `2026.08.30.0[4-7]`) with `position: absolute; top: 0; right: 0;` anchored
148
+ against `#content`. This fixes two problems that a fixed float/margin
149
+ calibration can never fully solve:
150
+ - The history-page "Back" button was rendering with no HTML output at all on
151
+ pre-6.1 Django (confirmed against a real 6.0.6 install) — Django's own
152
+ `object_history.html` has never defined `{% block object-tools %}` inside
153
+ its own `content` override, on any version, so a block-override approach
154
+ only ever had somewhere to render on 6.1 (where `object-tools` was promoted
155
+ to an independent top-level block). `#content` exists, in the same
156
+ structural role, on every version, so anchoring to it sidesteps the whole
157
+ question of which block nests where.
158
+ - "Duplicate"/"History" rendering visibly low on some change-form pages but
159
+ not others, on the same Django version — root cause confirmed via DevTools:
160
+ pages with a subtitle (e.g. Plugin Versions' "View Plugin" / h2
161
+ "ophix-server-base") have a taller title block than a plain single-`<h1>`
162
+ page, and a fixed `-45px` margin was calibrated for the shorter case.
163
+ `top: 0; right: 0` against `#content` has no such dependency.
164
+
165
+ As a side effect, this also closes out the original click-stacking bug this
166
+ whole chain started from: positioned elements always paint above
167
+ normal-flow/floated content regardless of DOM order, so there's no longer any
168
+ way for a title element to win the hit-test over the button sitting "beside"
169
+ it. The object-history template is back to a plain `{% block content %}`
170
+ override with no JS involved — nesting position no longer matters once
171
+ everything is anchored the same way.
172
+
173
+ ## 2026.08.30.07
174
+
175
+ - Fixed `2026.08.30.05`'s removal of the `float`/`margin-top`/`width` hack from
176
+ `.object-tools` breaking positioning on pre-6.1 Django (tested against a real
177
+ 6.0.6 install): "History"/"Duplicate" rendered far below the title instead of
178
+ beside it, and "Back" was pushed low enough to go unnoticed. Checked the real
179
+ Django 6.0.6 source directly — its own `.object-tools` CSS is
180
+ `text-align:right; margin:0 0 15px` with no float or negative margin at all, so
181
+ `ophix-admin-interface`'s hack was the *only* thing making buttons sit next to
182
+ the title on that version; `2026.08.30.05` removed it unconditionally, correct
183
+ for 6.1 but wrong for everything older. Restored the pre-6.1 hack as the default
184
+ (unconditional, since older Django is still in the fleet), and instead scoped
185
+ its cancellation to `.titles-and-tools .object-tools` specifically — a selector
186
+ that can only ever match on Django 6.1+, since that wrapper doesn't exist at all
187
+ on older Django (confirmed against real 5.2/6.0.6/6.1 source). Both the
188
+ `2026.08.30.06` `align-items: flex-start` fix and the `float:none;
189
+ margin-top:0; width:auto` cancellation now live under that same scoped selector.
190
+
191
+ ## 2026.08.30.06
192
+
193
+ - Fixed `2026.08.30.05`'s `object-tools` positioning still drifting noticeably
194
+ page-to-page (compared "History"/"Duplicate" on several change forms, "Add
195
+ Schedule" on a changelist, and "Back" on a history page — all visibly different
196
+ heights). Root cause: `.titles-and-tools` has no `align-items` of its own, so it
197
+ defaults to `stretch` — `.object-tools` stretches to match `.titles`'s height
198
+ (which varies with title text length/wrapping across pages), then centers its
199
+ buttons *within* that stretched box, so the button's vertical position tracked
200
+ however tall the title happened to be on each specific page. Added
201
+ `align-items: flex-start` to `.titles-and-tools` so both it and `object-tools`
202
+ anchor to the top of the row regardless of title height — the button's position
203
+ no longer depends on page-specific title content.
204
+
205
+ ## 2026.08.30.05
206
+
207
+ - Fixed `2026.08.30.04`'s history-page "Back" button (and by extension, every
208
+ `object-tools` button, e.g. "History"/"Duplicate") rendering slightly too high
209
+ once correctly nested inside Django 6.1's `.titles-and-tools` flex row.
210
+ `object-tools.css` still carried a pre-6.1 `float: right; margin-top: -45px;
211
+ width: 55–70%` hack meant to manually pull `object-tools` up beside the title
212
+ when it sat outside any flex context (older Django). Now that it's a genuine flex
213
+ item, `float` is simply ignored per spec, the negative `margin-top` still applies
214
+ and pulls it above its natural row position, and the fixed percentage `width`
215
+ fights `.titles`'s `flex-grow: 1` — together producing content-length-sensitive
216
+ drift, which is why "Back" and "History" (same CSS, different pages) landed at
217
+ visibly different vertical positions. Removed the `float`/`margin-top`/`width`
218
+ entirely; Django 6.1's own `.titles-and-tools` flex row already places
219
+ `object-tools` correctly at the trailing edge of the title row with no extra
220
+ positioning needed. Kept the internal `display:flex; justify-content:flex-end;
221
+ align-items:center; flex-wrap:wrap` used to lay out multiple buttons within
222
+ `object-tools` itself.
223
+
224
+ ## 2026.08.30.04
225
+
226
+ - Fixed the `2026.08.30.03` history-page "Back" button being unclickable (no hover
227
+ feedback, no click, no navigation) — it was rendered by overriding
228
+ `{% block content %}` instead of `{% block object-tools %}`, which placed it
229
+ *outside* Django 6.1's new `.titles-and-tools` flex wrapper — a div pairing the
230
+ page title with `{% block object-tools %}`, replacing the old
231
+ `float`/negative-`margin-top` positioning entirely.
232
+ Sitting outside that wrapper, the button still inherited `ophix-admin-interface`'s
233
+ own legacy `object-tools.css` (`float: right; margin-top: -45px; width: 55–70%`) —
234
+ a hack designed for the pre-6.1 layout where `object-tools` sat *inside* `content`.
235
+ Applied to an element now sitting *outside* `.titles-and-tools` instead, the negative
236
+ margin visually pulled it up beside the `<h1>` (looking correctly placed) while the
237
+ `<h1>` itself won the actual stacking/hit-test — clicking it always hit the title,
238
+ never the link. Confirmed by diffing a real Django 6.1 install's `admin/base.html`
239
+ and `base.css` against the vendored 5.2 copy previously checked, per the session-52
240
+ diffing technique. Fixed by overriding `object-tools` (the same block name
241
+ `change_form.html` already uses correctly) instead of `content`, so the Back button
242
+ now renders in the exact same DOM position/CSS context as "History"/"Duplicate" on
243
+ every other page.
244
+
245
+ ## 2026.08.30.03
246
+
247
+ - Added a "Back" button (`object-tools`, styled like "History") to the object history
248
+ page, linking back to the change view for the object being viewed. Django's own
249
+ `object_history.html` has no `object-tools` block at all, so the only ways back
250
+ from a history page were breadcrumbs or the left nav — no direct equivalent to the
251
+ "History" button's round trip.
252
+
253
+ ## 2026.08.30.02
254
+
255
+ - Fixed "LOG OUT" (`#logout-form button`) not picking up the `2026.08.30.01`
256
+ `#user-tools` font-weight fix. Django's own `base.css` sets `font-weight: 300`
257
+ directly on `#logout-form button`, which bypasses inheritance from the
258
+ `#user-tools` container entirely — the div-level `font-weight: 400` added for
259
+ "CHANGE PASSWORD" (an `<a>`) never reached the logout button because it's a direct
260
+ rule on the button itself, not an inherited value. Added an explicit
261
+ `font-weight: 400` to the button rule.
262
+
263
+ ## 2026.08.30.01
264
+
265
+ - Filter sidebar: tightened vertical spacing between a filter's label and its
266
+ dropdown, and between consecutive filter groups. `.module.filtered
267
+ #changelist-filter h3` margin-bottom 12px → 4px; `#changelist-filter
268
+ .list-filter-dropdown` margin-bottom 15px → 8px.
269
+ - `.admin-settings-cross-link` (the "Theme: ... →" pre-label on Admin Settings)
270
+ was coloured with `--admin-interface-module-text-color` — the variable meant for
271
+ text sitting inside a coloured module header bar, not a plain-background label —
272
+ so it read in a mismatched tint in both light and dark mode instead of the normal
273
+ label/body text colour. Removed the override; it now inherits the same colour as
274
+ every other label on the page, same as light and dark mode already work for those.
275
+ - `#toggle-nav-sidebar` (the chevron handle): the rule only zeroed/set the two right
276
+ corners (`border-top-right-radius`/`border-bottom-right-radius` from
277
+ `--admin-interface-tab-border-radius`) — the left corners were left to inherit
278
+ whatever a broader button/border-radius rule set elsewhere, giving the flush left
279
+ edge a mismatched radius. Added explicit `border-top-left-radius: 0` and
280
+ `border-bottom-left-radius: 0`.
281
+ - Modal close button (`button.mfp-close`, Magnific Popup): moved off the default
282
+ `right: 0` (which sat flush against the modal edge, overlapping the scrollbar in
283
+ taller modals like the schedule editor) to `right: 10px`, made the position rule
284
+ explicit (`position: absolute; top: 0;`), and enlarged from `1.375rem` to `2rem`
285
+ so it's easier to see and click.
286
+ - Finished the `#user-tools` (top-right "WELCOME,ADMIN. CHANGE PASSWORD / LOG OUT")
287
+ font-weight/alignment fix from the marketing manager's review list — the earlier
288
+ pass only set `font-weight: 400` on the `a` elements, leaving the plain
289
+ "WELCOME,ADMIN." text node and the `#logout-form button` off the same rule; moved
290
+ it onto the `#user-tools` container itself in `header.css` so every child inherits
291
+ consistently. Also added `line-height: 1` to `#logout-form button` (was unset,
292
+ relying on the browser's default button line-height, which is not guaranteed to
293
+ match the surrounding text's line-height across browsers/zoom levels — this is
294
+ what caused "LOG OUT" to look vertically offset from "CHANGE PASSWORD" in one
295
+ browser but not another).
296
+
297
+ ## 2026.08.29.04
298
+
299
+ - Fixed wide fields (Description/Command textareas, URL fields, etc.) rendering
300
+ full-width on their own row instead of staying beside the label — a side effect of
301
+ `2026.08.29.03`'s `flex-wrap: wrap` addition (needed so `.help`/`.errorlist` fall
302
+ onto their own line). Django's own `.vLargeTextField`/`.vXMLLargeTextField` is a
303
+ fixed 48em, `.vURLField` 30em — wider than the space left after the 160px label
304
+ column. Flexbox gives form controls an "automatic minimum size" floor based on
305
+ their intrinsic/specified size rather than zero, unless overridden; with wrapping
306
+ now enabled, a field whose floor still exceeds the remaining row space wraps onto
307
+ its own line instead of shrinking below it, landing at the container's own left
308
+ edge rather than indented past the label. Fixed with `min-width: 0` on the field
309
+ (deliberately not touching `flex-grow`, which stays at its default `0` — ordinary
310
+ fields keep their natural/specified width, e.g. `.vTextField`'s 20em, rather than
311
+ stretching to fill leftover row space, which would be a new visual change, not a
312
+ restoration of the old behaviour). `.related-widget-wrapper` (FK fields) already
313
+ had this protection; ordinary fields never did.
314
+ - All labels and legends now render at a uniform `font-weight: 700`, deliberately
315
+ overriding Django's own required-vs-optional bold/normal distinction
316
+ (`label { font-weight: normal }` / `label.required { font-weight: bold }` — this
317
+ mechanism is unchanged between Django versions, confirmed identical in both CSS and
318
+ `AdminField.label_tag()`; it was never a bug, just no longer a distinction this
319
+ project wants surfaced this way). A blank required field already gets its own clear
320
+ "This field is required." error on submit, so the extra visual hint wasn't valued
321
+ and the inconsistent weight across a form read as untidy instead.
322
+ - Fixed every ordinary field sitting 10px further right than intended, discovered via
323
+ the `ScheduledTask` date/time widget looking "slightly off" relative to everything
324
+ around it — the datetime widget hadn't moved, everything else had. Root cause:
325
+ Django 6.1's own `.flex-container` rule added `gap: 10px` (6.0.7 had none at all),
326
+ which none of this session's earlier fixes reset. The label already provides 10px
327
+ of separation via its own `padding: 4px 10px 0 0` — `gap`'s column-gap component
328
+ added a second, independent 10px on top of that, double-counting the spacing for
329
+ every ordinary field. `.datetime-sublabel`'s hand-built CSS doesn't route through
330
+ this same generic container, so it wasn't affected and looked out of step by
331
+ comparison. Fixed with `column-gap: 0` (not the `gap` shorthand) on the base
332
+ `.flex-container` rule — `row-gap` must stay at Django's 10px, since `.help`/
333
+ `div.help` (Django's own `base.css`) carries no margin of its own at all; the
334
+ vertical space between a field's row and its wrapped help-text row underneath
335
+ comes entirely from `row-gap`, and zeroing it too would collapse that spacing
336
+ instead of just the horizontal double-count.
337
+
338
+ ## 2026.08.29.03
339
+
340
+ - Fixed the remaining pieces of the same Django 6.1 `fieldset.html` restructure from
341
+ `2026.08.29.02`. Django 6.1's per-field DOM order inside `.flex-container` is
342
+ `label -> help -> errors -> field` — both `<div class="help">` and
343
+ `<ul class="errorlist">` (field/line validation errors) now render *before* the
344
+ field itself, whereas in 6.0.7 both were siblings positioned *after* the closing
345
+ `.flex-container` div entirely. Two problems followed from pinning
346
+ `.flex-container` to `flex-direction: row` to fix label/field placement:
347
+ - Help text and errors became ordinary items in that same row instead of falling
348
+ below it (help sat between the label and the field on one line).
349
+ - Naively forcing just `.help` onto its own full-width row via `flex-wrap` +
350
+ `flex: 0 0 100%` doesn't work on its own: flex-wrap doesn't backfill earlier
351
+ rows once one has wrapped, so anything *after* help in DOM order — the
352
+ errorlist, and critically the field itself — would also get pushed onto their
353
+ own separate rows, undoing the label/field pairing for any field with help text
354
+ or a validation error (i.e. most fields).
355
+ - Fixed with `order: 999` on both `.flex-container > .help` and
356
+ `.flex-container > ul.errorlist`, alongside `flex: 0 0 100%`: label and the
357
+ field (whatever it renders as — input/select/textarea/`.related-widget-wrapper`/
358
+ `.readonly`, all left at the default order) settle onto row one together
359
+ regardless of what sits between them in the DOM, while help and errors each
360
+ wrap onto their own full-width row afterward.
361
+
362
+ ## 2026.08.29.02
363
+
364
+ - Fixed a second Django 6.1 layout regression from the same root cause as
365
+ `2026.08.29.01`'s `.flex-container` fix, missed there because it's a different half
366
+ of the same upstream redesign: **every ordinary field's label and input rendered
367
+ stacked in a single column instead of side by side.** Checked Django's actual
368
+ `fieldset.html` include template directly (not just CSS) — every field, not just
369
+ checkboxes, is wrapped in `<div class="flex-container fieldBox ...">{{ label }}{{
370
+ field }}</div>` in both 6.0.7 and 6.1. The `2026.08.29.01` fix only pinned
371
+ `flex-direction: row` on `.flex-container.checkbox-row`, missing that the *base*
372
+ `.flex-container` rule needed it too, since ordinary fields use only the base class.
373
+ Django 6.1 also deleted `.aligned legend, .aligned label { display: block; width:
374
+ 160px; ... }` from its own forms.css entirely with no replacement — the other half of
375
+ the same redesign (their new column-based layout no longer needs a fixed label
376
+ width). Fixed by moving `flex-direction: row` onto the base `.flex-container` rule
377
+ (removing it from `.checkbox-row`, now redundant) and restoring the `.aligned label`
378
+ rule verbatim from Django's own pre-6.1 CSS. The 160px value isn't arbitrary — it's
379
+ already depended on directly by `.aligned p.datetime .datetime-sublabel`'s own
380
+ comment ("matches Django's .aligned label column").
381
+
382
+ ## 2026.08.29.01
383
+
384
+ - Fixed several layout regressions under Django 6.1, all caused by the same root pattern:
385
+ our CSS relied on Django's own base admin CSS to supply a `display`/direction/spacing
386
+ declaration we never set explicitly ourselves, and Django 6.1 changed several of those
387
+ defaults.
388
+ - **Boolean field checkboxes rendered stacked/centered instead of in a row.** Django
389
+ 6.1's `.flex-container` base rule gained `flex-direction: column` (previously
390
+ undeclared, defaulting to the browser's `row`). Our `.flex-container.checkbox-row`
391
+ override never declared its own direction, so it silently inherited whichever default
392
+ Django supplied. Fixed by declaring `display: flex` on the `.flex-container` base rule
393
+ and `flex-direction: row` on `.checkbox-row` explicitly.
394
+ - **Breadcrumbs lost their theme color on every standard admin page, and lost their
395
+ padding on custom pages with a hardcoded breadcrumb (e.g. the Client Management Status
396
+ page).** Django 6.1 changed its own breadcrumb markup from `<div class="breadcrumbs">`
397
+ to a semantic `<ol class="breadcrumbs"><li>...</li></ol>` (accessibility improvement)
398
+ and dropped its `div.breadcrumbs` CSS rule entirely in favor of `ol.breadcrumbs`. Our
399
+ own `.admin-interface div.breadcrumbs` selector was tag-locked, so it stopped matching
400
+ Django's own breadcrumb block (theme color silently lost, falling back to Django's
401
+ generic un-themed blue) while a custom template hardcoding its own `<div
402
+ class="breadcrumbs">` kept matching our rule but lost all padding (nothing supplied it
403
+ anymore, since Django removed its own `div.breadcrumbs` rule). Fixed by broadening the
404
+ selector to `.admin-interface .breadcrumbs` (class-only, matches both forms) and
405
+ declaring `padding`/`margin`/`border` explicitly instead of relying on Django's base
406
+ CSS for them.
407
+ - Also hardened two spots that hadn't broken yet but shared the identical risk: `.submit-row`
408
+ (Save/Continue/Delete button ordering) and `#main` (the nav-sidebar/content flex split)
409
+ now both declare `display: flex` explicitly instead of depending on Django's own base
410
+ CSS to supply it.
411
+
412
+ ## 2026.08.12.01
413
+
414
+ - Django dependency split by `python_version` marker: `Django>=4.2,<6.0` on Python < 3.12,
415
+ `Django>=4.2` (no upper bound) on Python >= 3.12. Django 6.0 itself requires Python 3.12+;
416
+ this makes the "Python 3.10/3.11 + Django 6" combination structurally unreachable via pip's
417
+ resolver instead of failing at runtime, while still allowing 3.10/3.11 hosts to run on
418
+ Django 5.2. `requires-python` is unchanged (`>=3.10` remains the true floor).
419
+
420
+ ## 2026.08.04.01
421
+
422
+ - `export_theme` gains a `--stable` flag, written for `ophix-revisions`. This command
423
+ doesn't follow the `_build_meta`/payload-envelope pattern used elsewhere — it's a
424
+ `dumpdata`-based `.tar.gz` bundle, so `--stable` fixes two independent embedded-timestamp
425
+ sources instead: `sort_keys=True` on the fixture JSON, and zeroed mtime/ownership on every
426
+ tar entry plus a zeroed gzip header timestamp (Python's `tarfile.open(path, "w:gz")`
427
+ shortcut has no way to override either of the latter two — fixed by building the archive
428
+ via an explicit `gzip.GzipFile(mtime=0)` and a `tarfile.add(..., filter=...)` callback).
429
+ Verified standalone: two `--stable` runs of unchanged content now produce byte-identical
430
+ archives (`cmp` on the raw bytes, not just the extracted content).
431
+ - `admin_interface` gains `get_revisions_targets()`, declaring its own `theme` target for
432
+ `ophix-revisions` (if installed) to discover at runtime — no separate registration
433
+ needed anywhere else.
434
+
435
+ ## 2026.07.12.02
436
+
437
+ - Migration `0002_remove_theme_placeholder_fields` no longer uses raw SQL — it used
438
+ MySQL/MariaDB backtick-quoted identifiers (`` `admin_interface_theme` ``), which are a
439
+ syntax error on Postgres, Oracle, SQL Server, and CockroachDB. Replaced with a portable
440
+ `RunPython` that uses Django's own `SchemaEditor.remove_field()` plus a backend-agnostic
441
+ introspection check (`connection.introspection.get_table_description()`), so the same
442
+ idempotent "only drop the column if it's actually there" behaviour now works on every
443
+ supported engine, not just MariaDB. Found while scoping out multi-engine Docker-based
444
+ testing for the `ophix-dbengine-*` plugins.
445
+
446
+ ## 2026.07.12.01
447
+
448
+ - #30: Nav sidebar width reduced from 360px to 300px. Updated in lockstep across
449
+ `nav-sidebar.css`, `sticky-form-controls.css` (sticky pagination/submit-row widths),
450
+ and `rtl.css` (mirrored values, including a derived `-260px` for a padding-offset
451
+ value that isn't independently verified — RTL is not in active use anywhere in the
452
+ fleet).
453
+
454
+ ## 2026.06.11.01
455
+
456
+ - Added `css_body_background_color` field (with dark-mode pair) to the Theme model —
457
+ controls the overall page background for all admin pages. Leave blank to inherit Django's
458
+ default. Placed at the top of the Body Text section in the theme editor.
459
+ Applied via `body.admin-interface { background: var(--admin-interface-body-background-color, var(--body-bg)); }`.
460
+ Error pages use this field for `--ep-body-bg`; when blank they fall back to
461
+ `css_module_background_color` (previous behaviour unchanged).
462
+ - Auto-dismiss for message banners: reads `window.OPHIX_AUTOHIDE` config (emitted by
463
+ `ophix-admin-settings` when enabled) and auto-removes success and info banners after
464
+ the configured delay. Hover cancels the timer. Errors and warnings are never auto-hidden.
465
+ Dismiss button (×) behaviour is unchanged.
466
+
467
+ - Added `generate_error_pages` management command — renders static HTML error pages
468
+ (400, 403, 404, 500, 503) with the active theme's colors baked in as inline CSS
469
+ variables. Output goes to `INSTALL_DIR/static/error_pages/`. Dark mode uses
470
+ `@media (prefers-color-scheme: dark)` with per-color overrides from the theme's
471
+ dark-mode fields. Run standalone or called automatically by `run_install`.
472
+ - `post_save` signal on `Theme`: when an active theme is saved and
473
+ `INSTALL_DIR/static/error_pages/` already exists, `generate_error_pages` runs
474
+ automatically so error pages stay in sync with theme changes. Skips silently on
475
+ fresh installs before `run_install` has created the directory.
476
+ - Fixed `ThemeAdmin.response_change` missing `_popup` guard — when a theme was opened
477
+ in a Magnific Popup and saved, the response redirected to the changelist instead of
478
+ returning Django's popup-closing response. Added `if "_popup" in request.POST: return
479
+ super().response_change(request, obj)` as the first line of the override.
480
+ - Fixed `0002_remove_theme_placeholder_fields` migration idempotency — replaced plain
481
+ `RemoveField` operations with `SeparateDatabaseAndState` using
482
+ `ALTER TABLE ... DROP COLUMN IF EXISTS` SQL, so the migration is safe to re-run on
483
+ servers where the columns were already dropped by an earlier cleanup script.
484
+ - `dismiss.js` moved to a static file loaded in `{% block extrastyle %}` in
485
+ `base_site.html` — previously in `{% block extrascript %}` which is overridden by
486
+ `change_form.html`.
487
+ - Test message insertion now correctly targets the parent wrapper sibling of `#content`.
488
+ - `.deletelink` excluded from the link hover underline rule in `widgets.css`.
489
+
490
+ ## 2026.06.02.01
491
+
492
+ - Added per-colour dark mode overrides. Each colour field in the theme editor now has a companion "dark?" checkbox and dark colour picker. When checked and a colour is set, that colour is applied under `[data-theme="dark"]`; if the checkbox is unchecked or the colour is blank, the light value is used as fallback. The `[data-theme="dark"]` CSS block is emitted in `base_site.html` alongside the existing `:root` block — only overrides with both checkbox and colour set are emitted.
493
+ - Logo preview in the theme editor now shows the logo rendered against the current header background colour at the configured max-height and max-width — updating live as those fields are changed, before saving. Selecting a new logo file also previews it immediately.
494
+ - Logo and favicon images now appear above (not beside) the file input controls.
495
+
496
+ ## 2026.06.01.01
497
+
498
+ - Theme editor section renames: "Generic Links" → **Body Text**, "Save Buttons" → **Buttons**,
499
+ "Delete Buttons" → **Alert Buttons**, "Extended Colors" → **Notification Colors**.
500
+ - New theme fields: body font family, body font size, button font size, button border radius,
501
+ module/panel border radius, alert/danger color.
502
+ - `css_module_rounded_corners` (boolean) replaced by `css_module_border_radius` (CharField,
503
+ default `4px`). Existing themes retain 4px rounded corners. Theme packages with the old
504
+ boolean key in `theme.json` are unaffected — the key is silently ignored.
505
+ - Related Modal fieldset: description added explaining the background overlay concept.
506
+ - Logo and favicon fields now show a thumbnail preview of the current image in the editor.
507
+ - Color picker: clicking the text input now positions the cursor for direct hex editing only.
508
+ The colour swatch button (to the left of the input) opens the picker as before.
509
+
510
+ ## 2026.05.31.01
511
+
512
+ - Moved server identity settings (title, title_visible, env_name, env_visible_in_header,
513
+ env_visible_in_favicon) and language chooser settings to the new `ophix-admin-settings`
514
+ package. These are now configured in the **Settings** admin section and apply
515
+ server-wide, independent of the active theme.
516
+ - `ophix-admin-settings` is now a required dependency — install it alongside this package.
517
+ - Removed `dark_mode_link_lightness` field (full per-colour dark mode coming in a
518
+ future release).
519
+ - Removed `custom_css_vars` field.
520
+ - Retired `set_title` management command — title is now managed directly in the
521
+ Server Settings admin page.
522
+ - `env_color` (environment badge colour) remains in the theme editor under the
523
+ Header section.
524
+
525
+ ## 2026.05.30.09
526
+
527
+ - Themes list view: `env_name` column now shows with header "Env Name" rather than the
528
+ field's default verbose name ("Name"), which was ambiguous alongside the theme name column.
529
+
530
+ ## 2026.05.30.08
531
+
532
+ - Added `title` and `env_name` columns to the Themes list view.
533
+
534
+ ## 2026.05.30.07
535
+
536
+ - Added missing migration `0040`: `dark_mode_link_lightness` verbose name was renamed
537
+ from `"link lightness"` to `"accent lightness"` in 2026.05.30.02 but no migration was
538
+ created, causing `migrate` to report pending model changes.
539
+
540
+ ## 2026.05.30.06
541
+
542
+ - Documented that the built-in Ophix theme is reinstalled automatically on every
543
+ `migrate` run — operators who delete it should expect it to return. Note added to
544
+ the `delete_theme` section of the Theme Tools docs page.
545
+
546
+ ## 2026.05.30.05
547
+
548
+ - Fixed language chooser dropdown in dark mode: the OS-rendered options popup showed
549
+ near-invisible text (light header colours on white popup background). Added
550
+ `color-scheme: dark` on the `select` and explicit `background-color`/`color` on
551
+ `option` elements so the popup renders with dark background and readable text.
552
+
553
+ ## 2026.05.30.04
554
+
555
+ - Standardised theme package media layout to `media/<field>/<filename>` (flat) across all
556
+ Ophix theme packages. External theme packages previously used a redundant deep layout
557
+ (`media/admin-interface/themes/<name>/<field>/<filename>`) that mirrored the MEDIA_ROOT
558
+ destination — unnecessary since `install_bundled_theme` constructs the destination path
559
+ itself. All external packages (ocean, midnight, forest, desert, fastrack, imago, seasons)
560
+ updated to the flat layout in the same release cycle. `install_bundled_theme` code
561
+ simplified accordingly.
562
+
563
+ ## 2026.05.30.03
564
+
565
+ - Fixed `install_bundled_theme`: the built-in Ophix theme favicon was always copied to
566
+ `logo/` instead of `favicon/` because the code used `parts[3]`, which only worked for
567
+ the (now-retired) deep layout used by external theme packages.
568
+
569
+ ## 2026.05.30.02
570
+
571
+ - Dark mode accent lightness now applies to docs index section headings and toggle
572
+ buttons as well as generic links; field label renamed from "link lightness" to
573
+ "accent lightness".
574
+
575
+ ## 2026.05.30.01
576
+
577
+ - Added per-theme `dark_mode_link_lightness` setting: controls how much white is
578
+ mixed into link and heading colours in dark mode. Default 30%. Increase for themes
579
+ with darker accent colours.
580
+ - Removed bulk action checkboxes from Themes list view (`actions = None`).
581
+
582
+ ## 2026.05.28.02
583
+
584
+ - Added inline docs page (Theme Tools) in the Getting Started section, covering all theme
585
+ management commands: `set_theme`, `set_title`, `list_themes`, `export_theme`,
586
+ `import_theme`, `delete_theme`. Loaded automatically by `run_install`; load manually
587
+ with `ophix-manage update_docs --include-app-docs admin_interface`.
588
+ - Added `--admin-interface-success-color` to themes
589
+
590
+ ## 2026.05.27.08
591
+
592
+ - `set_title` now accepts an optional positional argument — `ophix-manage set_title "My Title"`
593
+ sets the title directly without prompting.
594
+
595
+ ## 2026.05.27.0
596
+
597
+ - `set_theme` now shows a numbered interactive picker when called without a theme name,
598
+ with `[active]` marking the current theme. Ctrl+C cancels cleanly.
599
+ - `set_theme` falls back to the interactive picker when the supplied name is not found,
600
+ printing the error before showing the list.
601
+
602
+ ## 2026.05.27.06
603
+
604
+ - `set_title` prompt now reads `Enter new title [current]:` and Ctrl+C exits cleanly
605
+ with "Cancelled. No changes made." rather than a traceback.
606
+
607
+ ## 2026.05.27.05
608
+
609
+ - Added `set_title` management command — prompts for a new title on the active theme.
610
+ Enter keeps the existing value; `--clear` removes it; `--use-existing` copies the title
611
+ from a previously active (now inactive) theme.
612
+ - `set_theme` and `set_title` now print the correct `systemctl restart` command using
613
+ `SERVICE_NAME` from settings rather than a generic `<slug>` placeholder.
614
+ - `set_theme` hints to run `set_title` if the newly activated theme has no title set.
615
+ - Removed "Django administration" fallback from `base_site.html` — blank title now renders
616
+ blank in both the browser tab and the branding header.
617
+
618
+ ## 2026.05.27.01
619
+
620
+ - Fixed `install_bundled_theme` media path detection — field name (`logo`/`favicon`) is now
621
+ read from the correct position in the namespaced layout used by all theme packages
622
+ (`media/admin-interface/themes/<name>/<field>/<file>`). Previously files landed under
623
+ `…/admin-interface/` instead of `…/logo/` or `…/favicon/`.
624
+ - `Theme.title` default changed from `"Django administration"` to `""` so new installs
625
+ start with no title unless the operator sets one.
626
+
627
+ ## 2026.05.26.01
628
+
629
+ - Removed remaining `logo_dark` source references (export_theme, import_theme, utils)
630
+ that were left over after migration 0036 dropped the field
631
+ - Added classifiers, keywords, and project URLs to `pyproject.toml` for PyPI publishing
632
+ - Added `README.md`
633
+
634
+ ## 2026.05.25.01
635
+
636
+ - Remove `logo_dark` field — the header background colour is fixed per theme, so a dark-mode logo variant is never needed
637
+
638
+ ## 2026.05.24.01
639
+
640
+ - Add bundled Ophix theme (logo, favicon) installed automatically on migrate
641
+ - Update logo and favicon to match new Ophix branding
642
+
643
+ ## 2026.05.22.01
644
+
645
+ - Theme deletion now removes the namespaced media directory (`MEDIA_ROOT/admin-interface/themes/<name>/`)
646
+ via `post_delete` signal, covering both UI deletion and the `delete_theme` management command.
647
+ Previously, UI-initiated deletions left media files on disk. `delete_theme --preserve-media` still
648
+ suppresses cleanup when needed.
649
+
650
+ ## 2026.05.21.01
651
+
652
+ - Fixed migration 0035: `logo_dark` `upload_to` was stored as a static string
653
+ instead of the callable, causing Django to report pending model changes after
654
+ the migration had already been applied.
655
+
656
+ ## 2026.05.20.02
657
+
658
+ - Fixed bundled Ophix theme media path: logo file was copied to the wrong
659
+ destination under `MEDIA_ROOT`. Restructured bundled `media/` directory to
660
+ use the flat `media/<field>/` layout that `install_bundled_theme` expects.
661
+ - `install_bundled_theme` now always copies media files on every migrate run,
662
+ not only when the theme is first installed. Fixes missing assets on existing
663
+ installs after an upgrade.
664
+ - Bundled Ophix theme title is now populated from `SERVER_NAME` on first
665
+ install (e.g. "Ophix credserver"). Only fills a blank title — never
666
+ overwrites a value the operator has already set.
667
+
668
+ ## 2026.05.20.01
669
+
670
+ - Bundled "Ophix" default theme with Ophix Project branding; installed automatically
671
+ on `migrate` — active by default on fresh installs, inactive alongside existing themes.
672
+ - Added `logo_dark` field to `Theme` model — optional logo shown when the OS/browser
673
+ is in dark mode. When set, the header logo switches automatically via `<picture>` +
674
+ `prefers-color-scheme: dark`. Leave blank to use the standard logo in all modes.
675
+ Theme authors: store the dark variant in `media/admin-interface/themes/<Name>/logo_dark/`.
676
+
677
+ ## 2026.05.19.01
678
+
679
+ - Added `OPHIX_RELEASE_NOTES.md` for release notes delivery.