MultipleChoiceField form bug?

106 views
Skip to first unread message

Benny M

unread,
Dec 22, 2020, 9:02:48 AM12/22/20
to django...@googlegroups.com
Hi all,

I’ve ran into some unexpected behavior while testing a ModelForm with a MultipleChoiceField and am trying to determine if this is a bug, or if maybe I’m doing something out of the ordinary.

In the form, I’m generating the MultipleChoiceField choices on the fly. e.g. `OPTIONS = ((m.id, m.name) for m in Model.objects.all())` then assigning `OPTIONS` to the `choices` attribute in the field declaration.

When running the test the form generates the choices from the non-test db, which results in a failure, as I’m creating the model instance for POST in the test. So because of this behavior, the instance created in the test isn’t part of the form choices.

It seems like:
- ./manage.py test
- Generate form choices
- Instantiate Test
- Test creates expected choice(s) and calls view with POST data
- form.is_valid() fails due to generated test instance not being in choices

It seems odd to me that the form is (or at least parts of it are) being built before any direct reference to it is instantiated. But this may be normal/expected behavior. I’ve seen the same behavior in 1.11 and 3.1.4, so it’s most likely that my method for dynamically generating choices isn’t kosher.

Anyway, if this sounds like a possible bug let me know and I’ll submit an issue with steps to reproduce, or provide more information here on request. 


Thanks,
Benny

Carles Pina i Estany

unread,
Dec 22, 2020, 10:33:34 AM12/22/20
to django...@googlegroups.com


Hi,

On Dec/22/2020, Benny M wrote:

> I’ve ran into some unexpected behavior while testing a ModelForm with
> a MultipleChoiceField and am trying to determine if this is a bug, or
> if maybe I’m doing something out of the ordinary.

I think that I might guess what it is. If I could see the code it might
be easier.

Are you doing something like:

class SomeForm(forms.ModelForm):
some_field_options = a_dynamic_expression_that_queries_the_DB

def __init__(*args, **kwargs):
your code

So using a class variable instead of an instance variable?

Class variables are initialised on "startup" (I'm simplifying) and only
once (and might be used via __new__ on ModelFormMetaclass probably, read
and validated).

Either way, if it's not what I've guessed and you paste some code it
might help me / someone else to understand it better.

I had once a bug because of using datetime.now() in a class variable
when I wanted it the "now" that the view was used not the "now" when the
application was started up.

--
Carles Pina i Estany
https://carles.pina.cat

Benny M

unread,
Dec 22, 2020, 11:10:44 AM12/22/20
to django...@googlegroups.com
That’s correct. I am using a class variable since there doesn’t seem to be a DRY - or at least not messy - way of manipulating this from __init__.

That said, your explanation makes sense and does answer the “why” of this. If there’s a way of defining forms inside __init__ I haven’t found it… which is not to say that it doesn’t exist.

Here’s an example of what I’m referring to:
(Pardon my poorly labeled model/form names)

```
class M2MForm(forms.ModelForm):
“”” Populate/update M2MModel name and m2m references ””"
REF_OPTIONS = ((r.id, r.name) for r in RefModel.objects.all())

ref_models = forms.MultipleChoiceField(
widget=forms.SelectMultiple,
choices=REF_OPTIONS,
required=False
)

class Meta:
model = M2MModel
fields = ['name']
```
> --
> You received this message because you are subscribed to the Google Groups "Django users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/20201222153133.GA31856%40pina.cat.

Carles Pina i Estany

unread,
Dec 22, 2020, 7:05:21 PM12/22/20
to django...@googlegroups.com

Hi,

On Dec/22/2020, Benny M wrote:
> That’s correct. I am using a class variable since there doesn’t seem
> to be a DRY - or at least not messy - way of manipulating this from
> __init__.

It's easy to make this mistake. I was bitten by something similar in a
class based view generating a queryset...

> That said, your explanation makes sense and does answer the “why” of
> this. If there’s a way of defining forms inside __init__ I haven’t
> found it… which is not to say that it doesn’t exist.

Yes, you can

> Here’s an example of what I’m referring to: (Pardon my poorly labeled
> model/form names)
>
> ```
> class M2MForm(forms.ModelForm):
> “”” Populate/update M2MModel name and m2m references ””"
> REF_OPTIONS = ((r.id, r.name) for r in RefModel.objects.all())
>
> ref_models = forms.MultipleChoiceField(
> widget=forms.SelectMultiple,
> choices=REF_OPTIONS,
> required=False
> )
>
> class Meta:
> model = M2MModel
> fields = ['name']
> ```


Try something like:

```
class M2MForm(forms.ModelForm):
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)

# At this point self.fields['name'] already exist...
# ...if you overwriteself.fields['name'] careful
# because you need to save it in save()
#
# self.fields gets created by the __init__() method of
# the parent class (actually grandparent I think :-) )

""" Populate/update M2MModel name and m2m references """
REF_OPTIONS = ((r.id, r.name) for r in RefModel.objects.all())


# this would add the field 'ref_models' in the form, but save()
# will not save. You can overwrite save and save it yourself
self.fields['ref_models'] = forms.MultipleChoiceField(
widget=forms.SelectMultiple,
choices=REF_OPTIONS,
required=False
)

class Meta:
model = M2MModel
fields = ['name']
```

I would look at not using forms.MultipleChoiceField and use
forms.ModelMultipleChoiceField(): then you should be able to pass
queryset (first argument for the ModelMultipleChoiceField) and avoid the
REF_OPTIONS=... line

Let me know what you find :-)

Cheers,
> To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CH2PR14MB39136CDA719529325B91F0E3C0DF0%40CH2PR14MB3913.namprd14.prod.outlook.com.

Benny M

unread,
Dec 22, 2020, 8:21:52 PM12/22/20
to django...@googlegroups.com
Oh this worked out beautifully! Not sure why it hadn’t occurred to me to call super().__init__ before hand. And manipulating self.fields was the farthest thing from my mind.


As for using ModelMultipleChoiceField - that’s ultimately what I went with for the project, but I couldn’t let go of why I couldn’t figure this out. Lol
This is going to be handy in the future. I feel like I have leveled up!

Thanks a ton, Carles!

Reply all
Reply to author
Forward
0 new messages