Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #35189 -- Render admin collapsible fieldsets with <details>. #17910

Merged
merged 4 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions django/contrib/admin/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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
Expand Down Expand Up @@ -116,10 +117,14 @@ def __init__(

@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
sarahboyce marked this conversation as resolved.
Show resolved Hide resolved
sarahboyce marked this conversation as resolved.
Show resolved Hide resolved

def __iter__(self):
for field in self.fields:
yield Fieldline(
Expand Down Expand Up @@ -438,6 +443,12 @@ def inline_formset_data(self):
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

sarahboyce marked this conversation as resolved.
Show resolved Hide resolved
def non_form_errors(self):
return self.formset.non_form_errors()

Expand Down
2 changes: 0 additions & 2 deletions django/contrib/admin/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2398,8 +2398,6 @@ def media(self):
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):
Expand Down
55 changes: 26 additions & 29 deletions django/contrib/admin/static/admin/css/forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 0 additions & 4 deletions django/contrib/admin/static/admin/css/responsive.css
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,6 @@ input[type="submit"], button {
padding-top: 15px;
}

fieldset.collapsed .form-row {
display: none;
}

.aligned label {
width: 100%;
min-width: auto;
Expand Down
43 changes: 0 additions & 43 deletions django/contrib/admin/static/admin/js/collapse.js

This file was deleted.

2 changes: 1 addition & 1 deletion django/contrib/admin/templates/admin/change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

{% block field_sets %}
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% include "admin/includes/fieldset.html" with heading_level=2 id_suffix=forloop.counter0 %}
{% endfor %}
{% endblock %}

Expand Down
21 changes: 15 additions & 6 deletions django/contrib/admin/templates/admin/edit_inline/stacked.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
id="{{ inline_admin_formset.formset.prefix }}-group"
data-inline-type="stacked"
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
<fieldset class="module {{ inline_admin_formset.classes }}">
<fieldset class="module {{ inline_admin_formset.classes }}" aria-labelledby="{{ inline_admin_formset.formset.prefix }}-heading">
{% if inline_admin_formset.is_collapsible %}<details><summary>{% endif %}
<h2 id="{{ inline_admin_formset.formset.prefix }}-heading" class="inline-heading">
{% if inline_admin_formset.formset.max_num == 1 %}
<h2>{{ inline_admin_formset.opts.verbose_name|capfirst }}</h2>
{{ inline_admin_formset.opts.verbose_name|capfirst }}
{% else %}
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
{% endif %}
</h2>
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
{{ inline_admin_formset.formset.management_form }}
{{ inline_admin_formset.formset.non_form_errors }}

Expand All @@ -19,11 +23,16 @@ <h3><b>{{ inline_admin_formset.opts.verbose_name|capfirst }}:</b> <span class="i
{% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
</h3>
{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}

{% with parent_counter=forloop.counter0 %}
{% for fieldset in inline_admin_form %}
{% include "admin/includes/fieldset.html" with heading_level=4 id_prefix=parent_counter id_suffix=forloop.counter0 %}
nessita marked this conversation as resolved.
Show resolved Hide resolved
{% endfor %}
{% endwith %}

{% 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 %}
</div>{% endfor %}
{% if inline_admin_formset.is_collapsible %}</details>{% endif %}
</fieldset>
</div>
17 changes: 11 additions & 6 deletions django/contrib/admin/templates/admin/edit_inline/tabular.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }}
<fieldset class="module {{ inline_admin_formset.classes }}">
{% if inline_admin_formset.formset.max_num == 1 %}
<h2>{{ inline_admin_formset.opts.verbose_name|capfirst }}</h2>
{% else %}
<h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
{% endif %}
<fieldset class="module {{ inline_admin_formset.classes }}" aria-labelledby="{{ inline_admin_formset.formset.prefix }}-heading">
{% if inline_admin_formset.is_collapsible %}<details><summary>{% endif %}
<h2 id="{{ inline_admin_formset.formset.prefix }}-heading" class="inline-heading">
{% if inline_admin_formset.formset.max_num == 1 %}
{{ inline_admin_formset.opts.verbose_name|capfirst }}
{% else %}
{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
{% endif %}
</h2>
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
{{ inline_admin_formset.formset.non_form_errors }}
<table>
<thead><tr>
Expand Down Expand Up @@ -61,6 +65,7 @@ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
{% endfor %}
</tbody>
</table>
{% if inline_admin_formset.is_collapsible %}</details>{% endif %}
</fieldset>
</div>
</div>
11 changes: 9 additions & 2 deletions django/contrib/admin/templates/admin/includes/fieldset.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% with prefix=fieldset.formset.prefix|default:"fieldset" id_prefix=id_prefix|default:"0" id_suffix=id_suffix|default:"0" name=fieldset.name|default:""|slugify %}
<fieldset class="module aligned {{ fieldset.classes }}"{% if name %} aria-labelledby="{{ prefix }}-{{ id_prefix}}-{{ name }}-{{ id_suffix }}-heading"{% endif %}>
{% if name %}
{% if fieldset.is_collapsible %}<details><summary>{% endif %}
<h{{ heading_level|default:2 }} id="{{ prefix }}-{{ id_prefix}}-{{ name }}-{{ id_suffix }}-heading" class="fieldset-heading">{{ fieldset.name }}</h{{ heading_level|default:2 }}>
{% if fieldset.is_collapsible %}</summary>{% endif %}
{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
Expand Down Expand Up @@ -31,4 +36,6 @@
{% if not line.fields|length == 1 %}</div>{% endif %}
</div>
{% endfor %}
{% if name and fieldset.is_collapsible %}</details>{% endif %}
</fieldset>
{% endwith %}
Binary file modified docs/ref/contrib/admin/_images/fieldsets.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 16 additions & 5 deletions docs/ref/contrib/admin/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<details>``
and ``<summary>`` elements, provided they define a ``name``.

* ``description``
A string of optional extra text to be displayed at the top of each
Expand Down Expand Up @@ -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.
nessita marked this conversation as resolved.
Show resolved Hide resolved

.. versionchanged:: 5.1

``fieldsets`` using the ``collapse`` class now use ``<details>`` and
``<summary>`` elements, provided they define a ``name``.

.. attribute:: InlineModelAdmin.extra

Expand Down
9 changes: 9 additions & 0 deletions docs/releases/5.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,15 @@ Miscellaneous
a ``<footer>`` tag instead of a ``<div>``, and also moved below the
``<div id="main">`` element.

* In order to improve accessibility, the expandable widget used for
:attr:`ModelAdmin.fieldsets <django.contrib.admin.ModelAdmin.fieldsets>` and
:attr:`InlineModelAdmin.fieldsets <django.contrib.admin.InlineModelAdmin>`,
when the fieldset has a name and use the ``collapse`` class, now includes
``<details>`` and ``<summary>`` elements.

* The JavaScript file ``collapse.js`` is removed since it is no longer needed
in the Django admin site.

* :meth:`.SimpleTestCase.assertURLEqual` and
:meth:`~django.test.SimpleTestCase.assertInHTML` now add ``": "`` to the
``msg_prefix``. This is consistent with the behavior of other assertions.
Expand Down
1 change: 0 additions & 1 deletion js_tests/tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
<script src='./admin/inlines.test.js'></script>

<script src='../django/contrib/admin/static/admin/js/actions.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/collapse.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/prepopulate.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/urlify.js' data-cover></script>
<script src='./admin/URLify.test.js'></script>
Expand Down