Opened 4 years ago

Closed 3 years ago

#18168 closed Bug (fixed)

formfield_for_choice_field() doesn't validate choices updated in method

Reported by: Robert <weglarek.robert+djangotrac@…> Owned by: nobody
Component: contrib.admin Version: 1.3
Severity: Normal Keywords: admin, inlines
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Having field declared in model:

element = models.CharField(max_length=500, verbose_name=_('admin element'), choices=(('foo', 'bar'),))

and that function in admin:

class AdminModuleElementInline(admin.StackedInline):
    model = AdminModuleElement
    def formfield_for_choice_field(self, db_field, request=None, **kwargs):
        if db_field.name == 'element':
            kwargs['choices'] = (
                ('accepted', 'Accepted'),
                ('denied', 'Denied'),
            )
        return db_field.formfield(**kwargs)

when I try to save it I get an error:
Value u'accepted' is not a valid choice.

So it updates the list, but validator checks the choices declared in model.

Attachments (1)

Screen Shot 2012-07-15 at 21.59.02.png (17.1 KB) - added by Aymeric Augustin 4 years ago.

Download all attachments as: .zip

Change History (9)

comment:1 Changed 4 years ago by Robert <weglarek.robert+djangotrac@…>

Keywords: admin inlines added
Needs documentation: unset
Needs tests: unset
Patch needs improvement: unset
Summary: formfield_for_choice_field() doesn't update choices declared in modelformfield_for_choice_field() doesn't validate choices declared in model

comment:2 Changed 4 years ago by Robert <weglarek.robert+djangotrac@…>

Summary: formfield_for_choice_field() doesn't validate choices declared in modelformfield_for_choice_field() doesn't validate choices updated in method

comment:3 Changed 4 years ago by anonymous

any workaround for this issue ?

comment:4 Changed 4 years ago by Jannis Leidel

Could you please provide a full traceback?

comment:5 Changed 4 years ago by Aymeric Augustin

Triage Stage: UnreviewedAccepted

There is no traceback -- the validation error is displayed in the admin.

The reporter's example uses inlines. I could reproduce the issue with the following code. Screenshot attached.

# models.py

from django.db import models1

class Variable(models.Model):
    NAME_CHOICES = (
        ('foo', 'foo'),
        ('bar', 'bar'),
        ('baz', 'baz'),
    )
    name  = models.CharField(max_length=3, choices=NAME_CHOICES)

# admin.py

from django.contrib import admin
from .models import Variable

class VariableAdmin(admin.ModelAdmin):

    def formfield_for_choice_field(self, db_field, request, **kwargs):
        if db_field.name == "name":
            kwargs['choices'] = (
                ('quux', 'quux'),
                ('quuux', 'quuux'),
            )
        return super(VariableAdmin, self).formfield_for_choice_field(db_field, request, **kwargs)

admin.site.register(Variable, VariableAdmin)

I don't think it makes sense to allow in the form values that aren't allowed by the model. If I understand correctly this method can only be used to restrict changes, not to extend them. If that's true, it should be mentioned in the documentation of formfield_for_choice_field.

Changed 4 years ago by Aymeric Augustin

comment:6 Changed 4 years ago by Joeri Bekker

The function name formfield_for_choice_field suggests that you are changing the form field, not the database field which is exactly what happens. The form field choices are updated, the model field choices are not, hence validation (on the model level) fails.

If you want flexible choices, define a CharField in your model without any choices argument. Create a form where you overwrite the widget for that CharField with a forms.Select . Then use the formfield_for_dbfield function (not the formfield_for_choice_field since it's not a choice field in the database model) and fill the kwargs['widget'].choices with your list of choices.

Example:

# admin.py

from django import forms
from django.contrib import admin
from .models import Variable

class VariableForm(forms.ModelForm):
    class Meta:
        model = Variable
        widgets = {
            'name': forms.Select(),
        }

class VariableAdmin(admin.ModelAdmin):
    form = VariableForm

    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == "name":
            kwargs['widget'].choices = (
                ('quux', 'quux'),
                ('quuux', 'quuux'),
            )
        return super(VariableAdmin, self).formfield_for_dbfield(db_field, **kwargs)

admin.site.register(Variable, VariableAdmin)
Version 1, edited 4 years ago by Joeri Bekker (previous) (next) (diff)

comment:7 Changed 3 years ago by Preston Holmes

hitting an unrelated issue in some of this same code, and figured I'd address this. The best fix for now is to clarify that model choices and their validation always trump form choices.

There are a number of places where this can cause a trip up, but I'm going to go with a fix for this as a doc note.

comment:8 Changed 3 years ago by Preston Holmes <preston@…>

Resolution: fixed
Status: newclosed

In 10f8a2100279621ca0e0fa47d99ee744741a05e7:

Fixed #18168 -- clarified precedence of validation

any choices set by formfield_for_choice_field are still subject
to model validation of the model field's choices attribute

Note: See TracTickets for help on using tickets.
Back to Top