diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 69a5fbf389..db7df891e3 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -249,16 +249,17 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass): return None db = kwargs.get('using') - autocomplete_fields = self.get_autocomplete_fields(request) - if db_field.name in autocomplete_fields: - kwargs['widget'] = AutocompleteSelectMultiple(db_field.remote_field, self.admin_site, using=db) - elif db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db) - elif db_field.name in [*self.filter_vertical, *self.filter_horizontal]: - kwargs['widget'] = widgets.FilteredSelectMultiple( - db_field.verbose_name, - db_field.name in self.filter_vertical - ) + if 'widget' not in kwargs: + autocomplete_fields = self.get_autocomplete_fields(request) + if db_field.name in autocomplete_fields: + kwargs['widget'] = AutocompleteSelectMultiple(db_field.remote_field, self.admin_site, using=db) + elif db_field.name in self.raw_id_fields: + kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db) + elif db_field.name in [*self.filter_vertical, *self.filter_horizontal]: + kwargs['widget'] = widgets.FilteredSelectMultiple( + db_field.verbose_name, + db_field.name in self.filter_vertical + ) if 'queryset' not in kwargs: queryset = self.get_field_queryset(db, db_field, request) diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 719d0d8095..96e0d7ff0d 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -184,6 +184,44 @@ class AdminFormfieldForDBFieldTests(SimpleTestCase): 'Hold down “Control”, or “Command” on a Mac, to select more than one.' ) + def test_formfield_for_manytomany_widget_override(self): + """ + The widget can be overridden in formfield_for_manytomany(). + """ + class BandAdmin(admin.ModelAdmin): + def formfield_for_manytomany(self, db_field, request, **kwargs): + if db_field.name == 'members': + kwargs['widget'] = forms.CheckboxSelectMultiple + return super().formfield_for_manytomany(db_field, request, **kwargs) + + ma = BandAdmin(Band, admin.site) + ff = ma.formfield_for_dbfield(Band._meta.get_field('members'), request=None) + # Unwrap the widget wrapper if needed + if isinstance(ff.widget, widgets.RelatedFieldWidgetWrapper): + widget = ff.widget.widget + else: + widget = ff.widget + self.assertIsInstance(widget, forms.CheckboxSelectMultiple) + + def test_formfield_for_foreignkey_widget_override(self): + """ + The widget can be overridden in formfield_for_foreignkey(). + """ + class EventAdmin(admin.ModelAdmin): + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if db_field.name == 'main_band': + kwargs['widget'] = forms.RadioSelect + return super().formfield_for_foreignkey(db_field, request, **kwargs) + + ma = EventAdmin(Event, admin.site) + ff = ma.formfield_for_dbfield(Event._meta.get_field('main_band'), request=None) + # Unwrap the widget wrapper + if isinstance(ff.widget, widgets.RelatedFieldWidgetWrapper): + widget = ff.widget.widget + else: + widget = ff.widget + self.assertIsInstance(widget, forms.RadioSelect) + @override_settings(ROOT_URLCONF='admin_widgets.urls') class AdminFormfieldForDBFieldWithRequestTests(TestDataMixin, TestCase):