ModelChoiceField in forms.Form won't validate

2020-05-09 09:34发布

I have a django ModelChoiceField that won't validate if I override the queryset.

class PersonalNote(forms.Form):
    tile    = ModelChoiceField(queryset=Tile.objects.none())
    note    = forms.CharField()

form = PersonalNote()
form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)

The form.is_valid() error is: "Select a valid choice. That choice is not one of the available choices". If Tile.objects.none() is replaced with Tile.objects.all() it validates, but loads far too much data from the database. I've also tried:

class PersonalNote(forms.Form):
    tile    = ModelChoiceField(queryset=Tile.objects.none())
    note    = forms.CharField()

    def __init__(self, *args, **kwargs):
        yyy = kwargs.pop('yyy', None)
        super(PersonalNote, self).__init__(*args, **kwargs)
        if yyy:
            self.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)

What might be wrong here? Note the real application also overrides the label, but that does not seem to be a factor here:

class ModelChoiceField2(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        assert isinstance(obj,Tile)
        return obj.child_title()

3条回答
狗以群分
2楼-- · 2020-05-09 10:04

I also had this problem. The idea is to dynamically change the queryset of a ModelChoiceField based on a condition (in my case it was a filter made by another ModelChoiceField).

So, having the next model as example:

class FilterModel(models.Model):
    name = models.CharField()

class FooModel(models.Model):
    filter_field = models.ForeignKey(FilterModel)
    name = models.CharField()

class MyModel(models.Model):
    foo_field = models.ForeignKey(FooModel)

As you can see, MyModel has a foreign key with FooModel, but not with FilterModel. So, in order to filter the FooModel options, I added a new ModelChoiceField on my form:

class MyForm(forms.ModelForm):
   class Meta:
        model = MyModel

   def __init__(self, *args, **kwargs):
       # your code here
       self.fields['my_filter_field'] = forms.ModelChoiceField(FilterModel, initial=my_filter_field_selected)
       self.fields['my_filter_field'].queryset = FilterModel.objects.all()

Then, on your Front-End you can use Ajax to load the options of foo_field, based on the selected value of my_filter_field. At this point everyting should be working. But, when the form is loaded, it will bring all the posible options from FooModel. To avoid this, you need to dynamically change the queryset of foo_field.

On my form view, I passed a new argument to MyForm:

id_filter_field = request.POST.get('my_filter_field', None)
form = MyForm(data=request.POST, id_filter_field=id_filter_field)

Now, you can use that argument on MyForm to change the queryset:

class MyForm(forms.ModelForm):
    # your code here
    def __init__(self, *args, **kwargs):
        self.id_filter_field = kwargs.pop('id_filter_field', None)
        # your code here
        if self.id_filter_field:
            self.fields['foo_field'].queryset = FooModel.objects.filter(filter_field_id=self.id_filter_field)
        else:
            self.fields['foo_field'].queryset = FooModel.objects.none()
查看更多
何必那么认真
3楼-- · 2020-05-09 10:07

After 2 hours I found the solution. Because you specified a queryset of none in the class definition, when you instantiate that PersonalNote(request.POST) to be validated it is referenceing a null query set

class PersonalNote(forms.Form):
    tile    = ModelChoiceField(queryset=Tile.objects.none())
    note    = forms.CharField() 

To fix this, when you create your form based on a POST request be sure to overwrite your queryset AGAIN before you check is_valid()

def some_view_def(request):
    form = PersonalNote(request.POST)
    **form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)**

    if form.is_valid():
         #Do whatever it is
查看更多
来,给爷笑一个
4楼-- · 2020-05-09 10:09

When you pass an empty queryset to ModelChoiceField you're saying that nothing will be valid for that field. Perhaps you could filter the queryset so there aren't too many options.

查看更多
登录 后发表回答