Allow overriding widget in formfield_for_manytomany()

Align behavior with formfield_for_foreignkey() so that passing a
widget param to formfield_for_manytomany() is respected. This enables
custom widgets for ManyToMany fields via admin customization.
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 23:00:57 +00:00
parent 5b884d45ac
commit b80dab3295
2 changed files with 49 additions and 10 deletions

View file

@ -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)

View file

@ -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):