Code

Opened 2 years ago

Closed 9 months 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 aaugustin 21 months ago.

Download all attachments as: .zip

Change History (9)

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

  • Keywords admin, inlines added
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Summary changed from formfield_for_choice_field() doesn't update choices declared in model to formfield_for_choice_field() doesn't validate choices declared in model

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

  • Summary changed from formfield_for_choice_field() doesn't validate choices declared in model to formfield_for_choice_field() doesn't validate choices updated in method

comment:3 Changed 2 years ago by anonymous

any workaround for this issue ?

comment:4 Changed 22 months ago by jezdez

Could you please provide a full traceback?

comment:5 Changed 21 months ago by aaugustin

  • Triage Stage changed from Unreviewed to Accepted

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 21 months ago by aaugustin

comment:6 Changed 21 months ago by joeri

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 21 months ago by joeri (previous) (next) (diff)

comment:7 Changed 9 months ago by ptone

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 9 months ago by Preston Holmes <preston@…>

  • Resolution set to fixed
  • Status changed from new to closed

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

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.