1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #35189 -- Improved admin collapsible fieldsets by using <details> elements.

This work improves the accessibility of the add and change pages in the
admin site by adding <details> and <summary> 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>
This commit is contained in:
Marijke Luttekes 2024-05-20 14:39:09 -03:00 committed by nessita
parent 01ed59f753
commit e4a693f50a
11 changed files with 119 additions and 88 deletions

View File

@ -18,6 +18,7 @@ from django.db.models.fields.related import (
from django.forms.utils import flatatt from django.forms.utils import flatatt
from django.template.defaultfilters import capfirst, linebreaksbr from django.template.defaultfilters import capfirst, linebreaksbr
from django.urls import NoReverseMatch, reverse 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.html import conditional_escape, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext from django.utils.translation import gettext
@ -116,10 +117,14 @@ class Fieldset:
@property @property
def media(self): def media(self):
if "collapse" in self.classes:
return forms.Media(js=["admin/js/collapse.js"])
return forms.Media() 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): def __iter__(self):
for field in self.fields: for field in self.fields:
yield Fieldline( yield Fieldline(
@ -438,6 +443,12 @@ class InlineAdminFormSet:
def forms(self): def forms(self):
return self.formset.forms 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): def non_form_errors(self):
return self.formset.non_form_errors() return self.formset.non_form_errors()

View File

@ -2398,8 +2398,6 @@ class InlineModelAdmin(BaseModelAdmin):
js = ["vendor/jquery/jquery%s.js" % extra, "jquery.init.js", "inlines.js"] js = ["vendor/jquery/jquery%s.js" % extra, "jquery.init.js", "inlines.js"]
if self.filter_vertical or self.filter_horizontal: if self.filter_vertical or self.filter_horizontal:
js.extend(["SelectBox.js", "SelectFilter2.js"]) 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]) return forms.Media(js=["admin/js/%s" % url for url in js])
def get_extra(self, request, obj=None, **kwargs): def get_extra(self, request, obj=None, **kwargs):

View File

@ -76,6 +76,20 @@ form ul.inline li {
padding-right: 7px; 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 FIELDSETS */
.aligned label { .aligned label {
@ -207,35 +221,16 @@ form div.help ul {
width: 450px; width: 450px;
} }
/* COLLAPSED FIELDSETS */ /* COLLAPSIBLE FIELDSETS */
fieldset.collapsed * { .collapse summary .fieldset-heading,
display: none; .collapse summary .inline-heading {
}
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 {
background: transparent; background: transparent;
border: none;
color: currentColor;
display: inline; display: inline;
color: var(--link-fg); margin: 0;
padding: 0;
} }
/* MONOSPACE TEXTAREAS */ /* MONOSPACE TEXTAREAS */
@ -387,14 +382,16 @@ body.popup .submit-row {
position: relative; position: relative;
} }
.inline-related h3 { .inline-related h4,
.inline-related:not(.tabular) .collapse summary {
margin: 0; margin: 0;
color: var(--body-quiet-color); color: var(--body-quiet-color);
padding: 5px; padding: 5px;
font-size: 0.8125rem; font-size: 0.8125rem;
background: var(--darkened-bg); background: var(--darkened-bg);
border-top: 1px solid var(--hairline-color); border: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color); border-left-color: var(--darkened-bg);
border-right-color: var(--darkened-bg);
} }
.inline-related h3 span.delete { .inline-related h3 span.delete {

View File

@ -565,10 +565,6 @@ input[type="submit"], button {
padding-top: 15px; padding-top: 15px;
} }
fieldset.collapsed .form-row {
display: none;
}
.aligned label { .aligned label {
width: 100%; width: 100%;
min-width: auto; min-width: auto;

View File

@ -4,6 +4,7 @@
data-inline-type="stacked" data-inline-type="stacked"
data-inline-formset="{{ inline_admin_formset.inline_formset_data }}"> data-inline-formset="{{ inline_admin_formset.inline_formset_data }}">
<fieldset class="module {{ inline_admin_formset.classes }}" aria-labelledby="{{ inline_admin_formset.formset.prefix }}-heading"> <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"> <h2 id="{{ inline_admin_formset.formset.prefix }}-heading" class="inline-heading">
{% if inline_admin_formset.formset.max_num == 1 %} {% if inline_admin_formset.formset.max_num == 1 %}
{{ inline_admin_formset.opts.verbose_name|capfirst }} {{ inline_admin_formset.opts.verbose_name|capfirst }}
@ -11,6 +12,7 @@
{{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
{% endif %} {% endif %}
</h2> </h2>
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
{{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.management_form }}
{{ inline_admin_formset.formset.non_form_errors }} {{ 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.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
{% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %} {% if inline_admin_form.fk_field %}{{ inline_admin_form.fk_field.field }}{% endif %}
</div>{% endfor %} </div>{% endfor %}
{% if inline_admin_formset.is_collapsible %}</details>{% endif %}
</fieldset> </fieldset>
</div> </div>

View File

@ -5,6 +5,7 @@
<div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
{{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.management_form }}
<fieldset class="module {{ inline_admin_formset.classes }}" aria-labelledby="{{ inline_admin_formset.formset.prefix }}-heading"> <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"> <h2 id="{{ inline_admin_formset.formset.prefix }}-heading" class="inline-heading">
{% if inline_admin_formset.formset.max_num == 1 %} {% if inline_admin_formset.formset.max_num == 1 %}
{{ inline_admin_formset.opts.verbose_name|capfirst }} {{ inline_admin_formset.opts.verbose_name|capfirst }}
@ -12,6 +13,7 @@
{{ inline_admin_formset.opts.verbose_name_plural|capfirst }} {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
{% endif %} {% endif %}
</h2> </h2>
{% if inline_admin_formset.is_collapsible %}</summary>{% endif %}
{{ inline_admin_formset.formset.non_form_errors }} {{ inline_admin_formset.formset.non_form_errors }}
<table> <table>
<thead><tr> <thead><tr>
@ -63,6 +65,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if inline_admin_formset.is_collapsible %}</details>{% endif %}
</fieldset> </fieldset>
</div> </div>
</div> </div>

View File

@ -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 %} {% 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 %}> <fieldset class="module aligned {{ fieldset.classes }}"{% if name %} aria-labelledby="{{ prefix }}-{{ id_prefix}}-{{ name }}-{{ id_suffix }}-heading"{% endif %}>
{% if name %} {% 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 }}> <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 %} {% endif %}
{% if fieldset.description %} {% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div> <div class="description">{{ fieldset.description|safe }}</div>
@ -34,5 +36,6 @@
{% if not line.fields|length == 1 %}</div>{% endif %} {% if not line.fields|length == 1 %}</div>{% endif %}
</div> </div>
{% endfor %} {% endfor %}
{% if name and fieldset.is_collapsible %}</details>{% endif %}
</fieldset> </fieldset>
{% endwith %} {% endwith %}

View File

@ -424,9 +424,16 @@ subclass::
"classes": ["wide", "collapse"], "classes": ["wide", "collapse"],
} }
Fieldsets with the ``collapse`` style will be initially collapsed in Fieldsets with the ``wide`` style will be given extra horizontal
the admin and replaced with a small "click to expand" link. Fieldsets space in the admin interface.
with the ``wide`` style will be given extra horizontal space. 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`` * ``description``
A string of optional extra text to be displayed at the top of each 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 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 is rendered for the inlines. Defaults to ``None``. As with classes
configured in :attr:`~ModelAdmin.fieldsets`, inlines with a ``collapse`` configured in :attr:`~ModelAdmin.fieldsets`, inlines with a ``collapse``
class will be initially collapsed and their header will have a small "show" class will be initially collapsed using an expandable widget.
link.
.. versionchanged:: 5.1
``fieldsets`` using the ``collapse`` class now use ``<details>`` and
``<summary>`` elements, provided they define a ``name``.
.. attribute:: InlineModelAdmin.extra .. attribute:: InlineModelAdmin.extra

View File

@ -419,6 +419,12 @@ Miscellaneous
a ``<footer>`` tag instead of a ``<div>``, and also moved below the a ``<footer>`` tag instead of a ``<div>``, and also moved below the
``<div id="main">`` element. ``<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.
* :meth:`.SimpleTestCase.assertURLEqual` and * :meth:`.SimpleTestCase.assertURLEqual` and
:meth:`~django.test.SimpleTestCase.assertInHTML` now add ``": "`` to the :meth:`~django.test.SimpleTestCase.assertInHTML` now add ``": "`` to the
``msg_prefix``. This is consistent with the behavior of other assertions. ``msg_prefix``. This is consistent with the behavior of other assertions.

View File

@ -4,7 +4,9 @@ from django.contrib.admin.tests import AdminSeleniumTestCase
from django.contrib.auth.models import Permission, User from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import RequestFactory, TestCase, override_settings from django.test import RequestFactory, TestCase, override_settings
from django.test.selenium import screenshot_cases
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext
from .admin import InnerInline from .admin import InnerInline
from .admin import site as admin_site from .admin import site as admin_site
@ -1790,6 +1792,10 @@ class TestInlineWithFieldsets(TestDataMixin, TestCase):
if "tabular" in inline_admin_formset.opts.template: if "tabular" in inline_admin_formset.opts.template:
continue continue
if "collapse" in inline_admin_formset.classes:
formset_heading = f"<summary>{formset_heading}</summary>"
self.assertContains(response, formset_heading, html=True, count=1)
# Headings for every formset (the amount depends on `extra`). # Headings for every formset (the amount depends on `extra`).
for y, inline_admin_form in enumerate(inline_admin_formset): for y, inline_admin_form in enumerate(inline_admin_formset):
y_plus_one = y + 1 y_plus_one = y + 1
@ -1813,6 +1819,12 @@ class TestInlineWithFieldsets(TestDataMixin, TestCase):
f"Details</h4>" f"Details</h4>"
) )
self.assertContains(response, fieldset_heading) self.assertContains(response, fieldset_heading)
if "collapse" in fieldset.classes:
self.assertContains(
response,
f"<summary>{fieldset_heading}</summary>",
html=True,
)
self.assertContains(response, f'id="{heading_id}"', count=1) self.assertContains(response, f'id="{heading_id}"', count=1)
else: else:
@ -2182,10 +2194,11 @@ class SeleniumTests(AdminSeleniumTestCase):
"form#profilecollection_form tr.dynamic-profile_set#profile_set-2", 1 "form#profilecollection_form tr.dynamic-profile_set#profile_set-2", 1
) )
@screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
def test_collapsed_inlines(self): def test_collapsed_inlines(self):
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
# Collapsed inlines have SHOW/HIDE links. # Collapsed inlines use details and summary elements.
self.admin_login(username="super", password="secret") self.admin_login(username="super", password="secret")
self.selenium.get( self.selenium.get(
self.live_server_url + reverse("admin:admin_inlines_author_add") self.live_server_url + reverse("admin:admin_inlines_author_add")
@ -2195,19 +2208,21 @@ class SeleniumTests(AdminSeleniumTestCase):
"#id_nonautopkbook_set-0-title", "#id_nonautopkbook_set-0-title",
"#id_nonautopkbook_set-2-0-title", "#id_nonautopkbook_set-2-0-title",
] ]
show_links = self.selenium.find_elements(By.LINK_TEXT, "SHOW") summaries = self.selenium.find_elements(By.TAG_NAME, "summary")
self.assertEqual(len(show_links), 3) self.assertEqual(len(summaries), 3)
self.take_screenshot("loaded")
for show_index, field_name in enumerate(test_fields, 0): for show_index, field_name in enumerate(test_fields, 0):
self.wait_until_invisible(field_name) self.wait_until_invisible(field_name)
show_links[show_index].click() summaries[show_index].click()
self.wait_until_visible(field_name) self.wait_until_visible(field_name)
hide_links = self.selenium.find_elements(By.LINK_TEXT, "HIDE") self.take_screenshot("expanded")
self.assertEqual(len(hide_links), 2)
for hide_index, field_name in enumerate(test_fields, 0): for hide_index, field_name in enumerate(test_fields, 0):
self.wait_until_visible(field_name) self.wait_until_visible(field_name)
hide_links[hide_index].click() summaries[hide_index].click()
self.wait_until_invisible(field_name) self.wait_until_invisible(field_name)
self.take_screenshot("collapsed")
@screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
def test_added_stacked_inline_with_collapsed_fields(self): def test_added_stacked_inline_with_collapsed_fields(self):
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@ -2215,20 +2230,22 @@ class SeleniumTests(AdminSeleniumTestCase):
self.selenium.get( self.selenium.get(
self.live_server_url + reverse("admin:admin_inlines_teacher_add") self.live_server_url + reverse("admin:admin_inlines_teacher_add")
) )
self.selenium.find_element(By.LINK_TEXT, "Add another Child").click() add_text = gettext("Add another %(verbose_name)s") % {"verbose_name": "Child"}
self.selenium.find_element(By.LINK_TEXT, add_text).click()
test_fields = ["#id_child_set-0-name", "#id_child_set-1-name"] test_fields = ["#id_child_set-0-name", "#id_child_set-1-name"]
show_links = self.selenium.find_elements(By.LINK_TEXT, "SHOW") summaries = self.selenium.find_elements(By.TAG_NAME, "summary")
self.assertEqual(len(show_links), 2) self.assertEqual(len(summaries), 3)
self.take_screenshot("loaded")
for show_index, field_name in enumerate(test_fields, 0): for show_index, field_name in enumerate(test_fields, 0):
self.wait_until_invisible(field_name) self.wait_until_invisible(field_name)
show_links[show_index].click() summaries[show_index].click()
self.wait_until_visible(field_name) self.wait_until_visible(field_name)
hide_links = self.selenium.find_elements(By.LINK_TEXT, "HIDE") self.take_screenshot("expanded")
self.assertEqual(len(hide_links), 2)
for hide_index, field_name in enumerate(test_fields, 0): for hide_index, field_name in enumerate(test_fields, 0):
self.wait_until_visible(field_name) self.wait_until_visible(field_name)
hide_links[hide_index].click() summaries[hide_index].click()
self.wait_until_invisible(field_name) self.wait_until_invisible(field_name)
self.take_screenshot("collapsed")
def assertBorder(self, element, border): def assertBorder(self, element, border):
width, style, color = border.split(" ") width, style, color = border.split(" ")
@ -2264,9 +2281,9 @@ class SeleniumTests(AdminSeleniumTestCase):
self.wait_until_visible("#id_dummy") self.wait_until_visible("#id_dummy")
self.selenium.find_element(By.ID, "id_dummy").send_keys(1) self.selenium.find_element(By.ID, "id_dummy").send_keys(1)
fields = ["id_inner5stacked_set-0-dummy", "id_inner5tabular_set-0-dummy"] fields = ["id_inner5stacked_set-0-dummy", "id_inner5tabular_set-0-dummy"]
show_links = self.selenium.find_elements(By.LINK_TEXT, "SHOW") summaries = self.selenium.find_elements(By.TAG_NAME, "summary")
for show_index, field_name in enumerate(fields): for show_index, field_name in enumerate(fields):
show_links[show_index].click() summaries[show_index].click()
self.wait_until_visible("#" + field_name) self.wait_until_visible("#" + field_name)
self.selenium.find_element(By.ID, field_name).send_keys(1) self.selenium.find_element(By.ID, field_name).send_keys(1)
@ -2304,49 +2321,40 @@ class SeleniumTests(AdminSeleniumTestCase):
self.selenium.get( self.selenium.get(
self.live_server_url + reverse("admin:admin_inlines_holder5_add") self.live_server_url + reverse("admin:admin_inlines_holder5_add")
) )
stacked_inline_formset_selector = ( stacked_inline_details_selector = (
"div#inner5stacked_set-group fieldset.module.collapse" "div#inner5stacked_set-group fieldset.module.collapse details"
) )
tabular_inline_formset_selector = ( tabular_inline_details_selector = (
"div#inner5tabular_set-group fieldset.module.collapse" "div#inner5tabular_set-group fieldset.module.collapse details"
) )
# Inlines without errors, both inlines collapsed # Inlines without errors, both inlines collapsed
self.selenium.find_element(By.XPATH, '//input[@value="Save"]').click() self.selenium.find_element(By.XPATH, '//input[@value="Save"]').click()
self.assertCountSeleniumElements( self.assertCountSeleniumElements(
stacked_inline_formset_selector + ".collapsed", 1 stacked_inline_details_selector + ":not([open])", 1
) )
self.assertCountSeleniumElements( self.assertCountSeleniumElements(
tabular_inline_formset_selector + ".collapsed", 1 tabular_inline_details_selector + ":not([open])", 1
) )
show_links = self.selenium.find_elements(By.LINK_TEXT, "SHOW") summaries = self.selenium.find_elements(By.TAG_NAME, "summary")
self.assertEqual(len(show_links), 2) self.assertEqual(len(summaries), 2)
# Inlines with errors, both inlines expanded # Inlines with errors, both inlines expanded
test_fields = ["#id_inner5stacked_set-0-dummy", "#id_inner5tabular_set-0-dummy"] test_fields = ["#id_inner5stacked_set-0-dummy", "#id_inner5tabular_set-0-dummy"]
for show_index, field_name in enumerate(test_fields): for show_index, field_name in enumerate(test_fields):
show_links[show_index].click() summaries[show_index].click()
self.wait_until_visible(field_name) self.wait_until_visible(field_name)
self.selenium.find_element(By.ID, field_name[1:]).send_keys(1) self.selenium.find_element(By.ID, field_name[1:]).send_keys(1)
hide_links = self.selenium.find_elements(By.LINK_TEXT, "HIDE")
self.assertEqual(len(hide_links), 2)
for hide_index, field_name in enumerate(test_fields): for hide_index, field_name in enumerate(test_fields):
hide_link = hide_links[hide_index] summary = summaries[hide_index]
self.selenium.execute_script( self.selenium.execute_script(
"window.scrollTo(0, %s);" % hide_link.location["y"] "window.scrollTo(0, %s);" % summary.location["y"]
) )
hide_link.click() summary.click()
self.wait_until_invisible(field_name) self.wait_until_invisible(field_name)
with self.wait_page_loaded(): with self.wait_page_loaded():
self.selenium.find_element(By.XPATH, '//input[@value="Save"]').click() self.selenium.find_element(By.XPATH, '//input[@value="Save"]').click()
with self.disable_implicit_wait(): self.assertCountSeleniumElements(stacked_inline_details_selector, 0)
self.assertCountSeleniumElements( self.assertCountSeleniumElements(tabular_inline_details_selector, 0)
stacked_inline_formset_selector + ".collapsed", 0
)
self.assertCountSeleniumElements(
tabular_inline_formset_selector + ".collapsed", 0
)
self.assertCountSeleniumElements(stacked_inline_formset_selector, 1)
self.assertCountSeleniumElements(tabular_inline_formset_selector, 1)
def test_inlines_verbose_name(self): def test_inlines_verbose_name(self):
""" """

View File

@ -1939,7 +1939,6 @@ class AdminJavaScriptTest(TestCase):
self.assertContains(response, "vendor/jquery/jquery.min.js") self.assertContains(response, "vendor/jquery/jquery.min.js")
self.assertContains(response, "prepopulate.js") self.assertContains(response, "prepopulate.js")
self.assertContains(response, "actions.js") self.assertContains(response, "actions.js")
self.assertContains(response, "collapse.js")
self.assertContains(response, "inlines.js") self.assertContains(response, "inlines.js")
with override_settings(DEBUG=True): with override_settings(DEBUG=True):
response = self.client.get(reverse("admin:admin_views_section_add")) response = self.client.get(reverse("admin:admin_views_section_add"))
@ -1947,7 +1946,6 @@ class AdminJavaScriptTest(TestCase):
self.assertNotContains(response, "vendor/jquery/jquery.min.js") self.assertNotContains(response, "vendor/jquery/jquery.min.js")
self.assertContains(response, "prepopulate.js") self.assertContains(response, "prepopulate.js")
self.assertContains(response, "actions.js") self.assertContains(response, "actions.js")
self.assertContains(response, "collapse.js")
self.assertContains(response, "inlines.js") self.assertContains(response, "inlines.js")
@ -6086,11 +6084,8 @@ class SeleniumTests(AdminSeleniumTestCase):
) )
self.assertFalse(self.selenium.find_element(By.ID, "id_title").is_displayed()) self.assertFalse(self.selenium.find_element(By.ID, "id_title").is_displayed())
self.take_screenshot("collapsed") self.take_screenshot("collapsed")
self.selenium.find_elements(By.LINK_TEXT, "Show")[0].click() self.selenium.find_elements(By.TAG_NAME, "summary")[0].click()
self.assertTrue(self.selenium.find_element(By.ID, "id_title").is_displayed()) self.assertTrue(self.selenium.find_element(By.ID, "id_title").is_displayed())
self.assertEqual(
self.selenium.find_element(By.ID, "fieldsetcollapser0").text, "Hide"
)
self.take_screenshot("expanded") self.take_screenshot("expanded")
@screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
@ -6104,7 +6099,7 @@ class SeleniumTests(AdminSeleniumTestCase):
) )
url = self.live_server_url + reverse("admin7:admin_views_pizza_add") url = self.live_server_url + reverse("admin7:admin_views_pizza_add")
self.selenium.get(url) self.selenium.get(url)
self.selenium.find_elements(By.ID, "fieldsetcollapser0")[0].click() self.selenium.find_elements(By.TAG_NAME, "summary")[0].click()
from_filter_box = self.selenium.find_element(By.ID, "id_toppings_filter") from_filter_box = self.selenium.find_element(By.ID, "id_toppings_filter")
from_box = self.selenium.find_element(By.ID, "id_toppings_from") from_box = self.selenium.find_element(By.ID, "id_toppings_from")
to_filter_box = self.selenium.find_element(By.ID, "id_toppings_filter_selected") to_filter_box = self.selenium.find_element(By.ID, "id_toppings_filter_selected")