diff options
| author | David Smith <smithdc@gmail.com> | 2026-03-30 23:16:32 +0100 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-04-02 11:10:12 -0400 |
| commit | ea8e293fda0dd1e78f5489efa73e5d19e195b4f0 (patch) | |
| tree | 649ded01c2f1dbe20fc5d848fbe379ca880e81a6 | |
| parent | 1f0abb0595ed806a14695398627fc77052bb6b63 (diff) | |
[6.0.x] Fixed #36949 -- Improved RelatedFieldWidgetWrapper <label>s.
Regression in 4187da258fe212d494cb578a0bc2b52c4979ab95.
Backport of 253f552c5809fa096116b601bd842ca4f3504860 from main.
| -rw-r--r-- | django/contrib/admin/widgets.py | 4 | ||||
| -rw-r--r-- | docs/releases/6.0.4.txt | 4 | ||||
| -rw-r--r-- | tests/admin_views/tests.py | 13 | ||||
| -rw-r--r-- | tests/modeladmin/tests.py | 49 |
4 files changed, 68 insertions, 2 deletions
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index f5c3939012..1dfac1b323 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -28,6 +28,8 @@ class FilteredSelectMultiple(forms.SelectMultiple): catalog has been loaded in the page """ + use_fieldset = True + class Media: js = [ "admin/js/core.js", @@ -300,7 +302,7 @@ class RelatedFieldWidgetWrapper(forms.Widget): self.can_view_related = supported and can_view_related # To check if the related object is registered with this AdminSite. self.admin_site = admin_site - self.use_fieldset = True + self.use_fieldset = widget.use_fieldset def __deepcopy__(self, memo): obj = copy.copy(self) diff --git a/docs/releases/6.0.4.txt b/docs/releases/6.0.4.txt index e24397d9bc..1967edacc8 100644 --- a/docs/releases/6.0.4.txt +++ b/docs/releases/6.0.4.txt @@ -14,3 +14,7 @@ Bugfixes and :func:`~django.contrib.auth.alogout` did not respectively set or clear ``request.user`` if it had already been materialized (e.g., by sync middleware) (:ticket:`37017`). + +* Fixed a regression in Django 6.0 in admin forms where + ``RelatedFieldWidgetWrapper`` incorrectly wrapped all widgets in a + ``<fieldset>`` (:ticket:`36949`). diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 203e1d25a9..f766794c54 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -6922,17 +6922,28 @@ class SeleniumTests(AdminSeleniumTestCase): "Difficulty:", "Materials:", "Start datetime:", - "Categories:", ] url = reverse("admin:admin_views_course_change", args=(course.pk,)) self.selenium.get(self.live_server_url + url) fieldsets = self.selenium.find_elements( By.CSS_SELECTOR, "fieldset.aligned fieldset" ) + self.assertEqual(len(fieldsets), len(expected_legend_tags_text)) for index, fieldset in enumerate(fieldsets): legend = fieldset.find_element(By.TAG_NAME, "legend") self.assertEqual(legend.text, expected_legend_tags_text[index]) + # FilteredSelectMultiple uses <fieldset>. + url = reverse("admin:admin_views_camelcaserelatedmodel_add") + self.selenium.get(self.live_server_url + url) + fieldsets = self.selenium.find_elements( + By.CSS_SELECTOR, "fieldset.aligned fieldset" + ) + self.assertEqual(len(fieldsets), 1) + for index, fieldset in enumerate(fieldsets): + legend = fieldset.find_element(By.TAG_NAME, "legend") + self.assertEqual(legend.text, "M2m:") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) def test_use_fieldset_with_grouped_fields(self): from selenium.webdriver.common.by import By diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 4a92514e17..c1c8b52346 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -830,6 +830,55 @@ class ModelAdminTests(TestCase): ["extra", "transport", "id", "DELETE", "main_band"], ) + def test_foreign_key_as_custom_widget(self): + class CustomSelectMultiple(forms.SelectMultiple): + def build_attrs(self, base_attrs, extra_attrs=None): + attrs = super().build_attrs(base_attrs, extra_attrs) + attrs["data-custom-widget"] = "true" + return attrs + + class ConcertAdmin(ModelAdmin): + formfield_overrides = { + models.ForeignKey: {"widget": CustomSelectMultiple}, + } + + cma = ConcertAdmin(Concert, self.site) + cmafa = cma.get_form(request) + expected = ( + '<div><label for="id_main_band">Main band:</label><div ' + 'class="related-widget-wrapper" data-model-ref="band"><select ' + 'name="main_band" data-context="available-source" required ' + 'id="id_main_band" data-custom-widget="true" multiple>' + '<option value="">---------</option><option value="1">The Doors</option>' + "</select></div></div>" + ) + self.assertInHTML(expected, cmafa().render()) + + def test_foreign_key_as_custom_widget_with_fieldset(self): + class CustomSelectMultipleFieldset(forms.RadioSelect): + use_fieldset = True + + def build_attrs(self, base_attrs, extra_attrs=None): + attrs = super().build_attrs(base_attrs, extra_attrs) + attrs["use_fieldset"] = "true" + return attrs + + class ConcertAdmin(ModelAdmin): + formfield_overrides = { + models.ForeignKey: {"widget": CustomSelectMultipleFieldset}, + } + + cma = ConcertAdmin(Concert, self.site) + cmafa = cma.get_form(request) + expected = ( + '<fieldset><legend>Main band:</legend><div class="related-widget-wrapper" ' + 'data-model-ref="band"><div id="id_main_band"><div><label ' + 'for="id_main_band_0"><input type="radio" name="main_band" value="1" ' + 'data-context="available-source" required id="id_main_band_0" ' + 'use_fieldset="true">The Doors</label></div></div></div></fieldset>' + ) + self.assertInHTML(expected, cmafa().render()) + def test_log_actions(self): ma = ModelAdmin(Band, self.site) mock_request = MockRequest() |
