From e4a693f50a8342ed1a54b74e1a202b44c8e62981 Mon Sep 17 00:00:00 2001 From: Marijke Luttekes Date: Mon, 20 May 2024 14:39:09 -0300 Subject: [PATCH] Fixed #35189 -- Improved admin collapsible fieldsets by using
elements. This work improves the accessibility of the add and change pages in the admin site by adding
and elements to the collapsible fieldsets. This has the nice side effect of no longer requiring custom JavaScript helpers to implement the fieldsets' show/hide capabilities. Thanks to James Scholes for the accessibility advice, and to Sarah Boyce and Tom Carrick for reviews. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> --- django/contrib/admin/helpers.py | 15 +++- django/contrib/admin/options.py | 2 - .../contrib/admin/static/admin/css/forms.css | 55 ++++++------ .../admin/static/admin/css/responsive.css | 4 - .../templates/admin/edit_inline/stacked.html | 3 + .../templates/admin/edit_inline/tabular.html | 3 + .../templates/admin/includes/fieldset.html | 3 + docs/ref/contrib/admin/index.txt | 21 +++-- docs/releases/5.1.txt | 6 ++ tests/admin_inlines/tests.py | 86 ++++++++++--------- tests/admin_views/tests.py | 9 +- 11 files changed, 119 insertions(+), 88 deletions(-) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index c4613fa24e..a4aa8e40e3 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -18,6 +18,7 @@ from django.db.models.fields.related import ( from django.forms.utils import flatatt from django.template.defaultfilters import capfirst, linebreaksbr from django.urls import NoReverseMatch, reverse +from django.utils.functional import cached_property from django.utils.html import conditional_escape, format_html from django.utils.safestring import mark_safe from django.utils.translation import gettext @@ -116,10 +117,14 @@ class Fieldset: @property def media(self): - if "collapse" in self.classes: - return forms.Media(js=["admin/js/collapse.js"]) return forms.Media() + @cached_property + def is_collapsible(self): + if any([field in self.fields for field in self.form.errors]): + return False + return "collapse" in self.classes + def __iter__(self): for field in self.fields: yield Fieldline( @@ -438,6 +443,12 @@ class InlineAdminFormSet: def forms(self): return self.formset.forms + @cached_property + def is_collapsible(self): + if any(self.formset.errors): + return False + return "collapse" in self.classes + def non_form_errors(self): return self.formset.non_form_errors() diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 47b4821fcc..12467de74d 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -2398,8 +2398,6 @@ class InlineModelAdmin(BaseModelAdmin): js = ["vendor/jquery/jquery%s.js" % extra, "jquery.init.js", "inlines.js"] if self.filter_vertical or self.filter_horizontal: js.extend(["SelectBox.js", "SelectFilter2.js"]) - if self.classes and "collapse" in self.classes: - js.append("collapse.js") return forms.Media(js=["admin/js/%s" % url for url in js]) def get_extra(self, request, obj=None, **kwargs): diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index fe60536d62..539a11ae61 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -76,6 +76,20 @@ form ul.inline li { padding-right: 7px; } +/* FIELDSETS */ + +fieldset .fieldset-heading, +fieldset .inline-heading, +:not(.inline-related) .collapse summary { + border: 1px solid var(--header-bg); + margin: 0; + padding: 8px; + font-weight: 400; + font-size: 0.8125rem; + background: var(--header-bg); + color: var(--header-link-color); +} + /* ALIGNED FIELDSETS */ .aligned label { @@ -207,35 +221,16 @@ form div.help ul { width: 450px; } -/* COLLAPSED FIELDSETS */ +/* COLLAPSIBLE FIELDSETS */ -fieldset.collapsed * { - display: none; -} - -fieldset.collapsed h2, fieldset.collapsed { - display: block; -} - -fieldset.collapsed { - border: 1px solid var(--hairline-color); - border-radius: 4px; - overflow: hidden; -} - -fieldset.collapsed h2 { - background: var(--darkened-bg); - color: var(--body-quiet-color); -} - -fieldset .collapse-toggle { - color: var(--header-link-color); -} - -fieldset.collapsed .collapse-toggle { +.collapse summary .fieldset-heading, +.collapse summary .inline-heading { background: transparent; + border: none; + color: currentColor; display: inline; - color: var(--link-fg); + margin: 0; + padding: 0; } /* MONOSPACE TEXTAREAS */ @@ -387,14 +382,16 @@ body.popup .submit-row { position: relative; } -.inline-related h3 { +.inline-related h4, +.inline-related:not(.tabular) .collapse summary { margin: 0; color: var(--body-quiet-color); padding: 5px; font-size: 0.8125rem; background: var(--darkened-bg); - border-top: 1px solid var(--hairline-color); - border-bottom: 1px solid var(--hairline-color); + border: 1px solid var(--hairline-color); + border-left-color: var(--darkened-bg); + border-right-color: var(--darkened-bg); } .inline-related h3 span.delete { diff --git a/django/contrib/admin/static/admin/css/responsive.css b/django/contrib/admin/static/admin/css/responsive.css index b58cbd964e..932e824c1c 100644 --- a/django/contrib/admin/static/admin/css/responsive.css +++ b/django/contrib/admin/static/admin/css/responsive.css @@ -565,10 +565,6 @@ input[type="submit"], button { padding-top: 15px; } - fieldset.collapsed .form-row { - display: none; - } - .aligned label { width: 100%; min-width: auto; diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index b0331c65f8..73f459ee47 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -4,6 +4,7 @@ data-inline-type="stacked" data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
+ {% if inline_admin_formset.is_collapsible %}
{% endif %}

{% if inline_admin_formset.formset.max_num == 1 %} {{ inline_admin_formset.opts.verbose_name|capfirst }} @@ -11,6 +12,7 @@ {{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {% endif %}

+ {% if inline_admin_formset.is_collapsible %}
{% endif %} {{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.non_form_errors }} @@ -31,5 +33,6 @@ {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} {% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %} {% endfor %} + {% if inline_admin_formset.is_collapsible %}
{% endif %}
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 845d3917a4..7acfda7bd1 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -5,6 +5,7 @@ diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html index 66bf714e9a..b4eef47547 100644 --- a/django/contrib/admin/templates/admin/includes/fieldset.html +++ b/django/contrib/admin/templates/admin/includes/fieldset.html @@ -1,7 +1,9 @@ {% with prefix=fieldset.formset.prefix|default:"fieldset" id_prefix=id_prefix|default:"0" id_suffix=id_suffix|default:"0" name=fieldset.name|default:""|slugify %}
{% if name %} + {% if fieldset.is_collapsible %}
{% endif %} {{ fieldset.name }} + {% if fieldset.is_collapsible %}{% endif %} {% endif %} {% if fieldset.description %}
{{ fieldset.description|safe }}
@@ -34,5 +36,6 @@ {% if not line.fields|length == 1 %}{% endif %} {% endfor %} + {% if name and fieldset.is_collapsible %}
{% endif %}
{% endwith %} diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 2a5652c24b..b7e94c7387 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -424,9 +424,16 @@ subclass:: "classes": ["wide", "collapse"], } - Fieldsets with the ``collapse`` style will be initially collapsed in - the admin and replaced with a small "click to expand" link. Fieldsets - with the ``wide`` style will be given extra horizontal space. + Fieldsets with the ``wide`` style will be given extra horizontal + space in the admin interface. + Fieldsets with a name and the ``collapse`` style will be initially + collapsed, using an expandable widget with a toggle for switching + their visibility. + + .. versionchanged:: 5.1 + + ``fieldsets`` using the ``collapse`` class now use ``
`` + and ```` elements, provided they define a ``name``. * ``description`` A string of optional extra text to be displayed at the top of each @@ -2308,8 +2315,12 @@ The ``InlineModelAdmin`` class adds or customizes: A list or tuple containing extra CSS classes to apply to the fieldset that is rendered for the inlines. Defaults to ``None``. As with classes configured in :attr:`~ModelAdmin.fieldsets`, inlines with a ``collapse`` - class will be initially collapsed and their header will have a small "show" - link. + class will be initially collapsed using an expandable widget. + + .. versionchanged:: 5.1 + + ``fieldsets`` using the ``collapse`` class now use ``
`` and + ```` elements, provided they define a ``name``. .. attribute:: InlineModelAdmin.extra diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index 68315205df..e4f0b37402 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -419,6 +419,12 @@ Miscellaneous a ``