diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 124f3307af..81b57f33aa 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -10,6 +10,7 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import URLValidator from django.db.models import CASCADE, UUIDField +from django.forms.widgets import Select from django.urls import reverse from django.urls.exceptions import NoReverseMatch from django.utils.html import smart_urlquote @@ -284,16 +285,18 @@ class RelatedFieldWidgetWrapper(forms.Widget): if can_add_related is None: can_add_related = admin_site.is_registered(rel.model) self.can_add_related = can_add_related - # XXX: The UX does not support multiple selected values. - multiple = getattr(widget, "allow_multiple_selected", False) if not isinstance(widget, AutocompleteMixin): self.attrs["data-context"] = "available-source" - self.can_change_related = not multiple and can_change_related + # Only single-select Select widgets are supported. + supported = not getattr( + widget, "allow_multiple_selected", False + ) and isinstance(widget, Select) + self.can_change_related = supported and can_change_related # XXX: The deletion UX can be confusing when dealing with cascading # deletion. cascade = getattr(rel, "on_delete", None) is CASCADE - self.can_delete_related = not multiple and not cascade and can_delete_related - self.can_view_related = not multiple and can_view_related + self.can_delete_related = supported and not cascade and can_delete_related + 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 diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 9a5c846bdd..7588c2cc32 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -978,6 +978,21 @@ class RelatedFieldWidgetWrapperTests(SimpleTestCase): """ self.assertHTMLEqual(output, expected) + def test_non_select_widget_cant_change_delete_related(self): + main_band = Event._meta.get_field("main_band") + widget = widgets.AdminRadioSelect() + wrapper = widgets.RelatedFieldWidgetWrapper( + widget, + main_band, + widget_admin_site, + can_add_related=True, + can_change_related=True, + can_delete_related=True, + ) + self.assertTrue(wrapper.can_add_related) + self.assertFalse(wrapper.can_change_related) + self.assertFalse(wrapper.can_delete_related) + @override_settings(ROOT_URLCONF="admin_widgets.urls") class AdminWidgetSeleniumTestCase(AdminSeleniumTestCase):