Description

Let's say we have custom field that extends CharField, which holds comma-separated values. Then, let's say we've made a custom widget that overrides CheckboxMultipleSelect, and takes the field's choices and combines them into comma-separated values, and we want to use that in the admin:

In ModelAdmin, the formfield_for_dbfield will normally check formfield_overrides to see if there are any overrides. That is, unless, the field has choices. If the field has choices, formfield_for_choice_field is called, and formfield_overrides is ignored completely.

I know that there are other ways of overriding form fields, but it seems that ModelAdmin.formfield_overrides does not work as advertised.

For consistency between formfield_for_choice_field, formfield_for_foreignkey and formfield_for_manytomany, the fix should move the handling of formfield_overrides either up, at the beginning of formfield_for_dbfield, or down, in a common method that would wrap db_field.formfield and be called by each formfield_to_*. Actually it would have to do both, see below.

The expected order of precendence is:

formfield_overrides

values set by formfield_for_*

FORMFIELD_FOR_DBFIELD_DEFAULTS

This can't be achieved without some refactoring, becauseformfield_overrides is currently merged with FORMFIELD_FOR_DBFIELD_DEFAULTS.

The code is pretty ugly, but I haven't found any other way to solve the issue without breaking backwards compatibility. I'd merge the code to master right now and think of a better solution later.

Ideally we should introduce another dimension to formfield_overrides dict, and choose the widget for formfield based not only on the formfield class, but also on its attributes (choices attribute in particular). See #20465.

This is backwards-incompatible and breaks existing code that is working as desired.

That might be reasonable if it were a clear-cut bugfix, but it's not; I think the new behavior is less correct (and less consistent with the rest of Django) than the old.

The new behavior perhaps makes sense when formfield_overrides is being used on a single "leaf" ModelAdmin subclass. In that case, it is known in advance which fields have choices set and which do not, so the appropriate widget etc overrides can be provided with that knowledge in mind. (Although formfield_overrides operates by field class, not field name, so even in this scenario it's not really conceptually correct.)

But the new behavior is _really_ incorrect when formfield_overrides is used on a reusable ModelAdmin subclass, because in that case you may have no idea whether a given field class will be used with or without choices.

And it is rare that you actually want the same widget with or without choices (which is why in every other area of the forms library Django automatically switches to a select widget when a field has choices set).

Because of this, precedent elsewhere is to provide separate options for with-choices and without-choices (e.g. formfield methods take both form_class and choices_form_class arguments).

I think the correct fix here would be one of the following:

a) Simply document that formfield_overrides does not impact form fields with choices set; if you want to change things when choices are set, override formfield_for_dbfield (or formfield_for_choice_field).

b) Add a formfield_choice_overrides to mirror formfield_overrides for when choices are set.

c) Add a way to distinguish with-choices from without-choices within formfield_overrides.

I listed those choices in my order of preference, but I am open to suggestions and willing to implement any of them.

Marking release-blocker: I think the backwards-incompatibility means that the current fix here should be rolled back before 1.7 is released.