Opened 9 years ago

Closed 9 years ago

#24453 closed Bug (invalid)

Initial values for ManyToMany form fields can't be hidden

Reported by: girzel Owned by: nobody
Component: Forms Version: 1.7
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Initial values can be provided for the ManyToMany field of a ModelForm, to be used in a ModelFormSet, but if the widget is switched to the HiddenInput class, the (as far as I can tell correct) initial value creates a validation error:

(Hidden field <field_name>) Enter a list of values.

Take this model:

class Work(models.Model):
  authors = models.ManyToManyField(Author)

initial = dict(authors=[some_author_instance])

formset = WorkFormSet(queryset=Work.objects.none(), initial=[initial])

This is supposing one extra form in the formset. If the "authors" field remains unhidden, the initial value works as expected. If we switch the "authors" widget to a forms.HiddenInput, however, I get the above validation error.

I don't think simply hiding the input should affect how the initial values are treated. FWIW, here's an example what the hidden input looks like in the HTML:

<input id="id_form-1-authors" name="form-1-authors" type="hidden" value="[37L]" />

So presumably "[37L]" isn't getting read as a list, somewhere...

Change History (5)

comment:1 by Collin Anderson, 9 years ago

Hi, I think the initial is expecting integers/long instead of instances. When I pass in an instance and call as_hidden() I get multiple hidden inputs as expected.

Though I also didn't try with a formset. How are you making it hidden?

https://groups.google.com/d/topic/django-users/jcPx8TBgRr4/discussion

Last edited 9 years ago by Collin Anderson (previous) (diff)

comment:2 by girzel, 9 years ago

Thanks for the response! I previously tried it with a list of pks instead of instances, and it behaves exactly the same way (the value shows up in the HTML as "[37L]" in both cases), with the same error. I'm constructing a list of field names to be hidden, and putting them in a variable called "hidden", and then:

WorkFormSet = modelformset_factory(Work,
    widgets=dict([(f, forms.HiddenInput())
                               for f in hidden]),)

That's how I hide them. It works correctly for any field that isn't a ManyToManyField.

Should I be hiding this field some other way?

comment:3 by Collin Anderson, 9 years ago

Ahh... I think you want to use a MultipleHiddenInput. Cause, you know, apparently that exists :)

https://docs.djangoproject.com/en/1.7/ref/forms/widgets/#composite-widgets

I wonder if there's a good place to document your situation so other people don't run into the same thing. Or if it would be safe to make HiddenInput() error when given a list.

comment:4 by girzel, 9 years ago

I had no idea! It works perfectly. Consider this a documentation bug report, then. :)

First, it might be nice to note this requirement here:

https://docs.djangoproject.com/en/1.7/ref/forms/fields/#django.forms.ModelMultipleChoiceField

And just say that, if you want to hide the input, you have to use MultipleHiddenInput. Second, the HiddenInput widget documentation (which I did look at) says this:

Note that there also is a MultipleHiddenInput widget that encapsulates a set of hidden input elements.

"A set of hidden input elements" is not at all how I was thinking of a ManyToManyField. Perhaps something like "that is used to hide inputs that return multiple values". Or, "that return a list of values". Or...

What would probably be nicest is to have a way to say fields["authors"].hidden = True, and let Django figure it out. That would be a much larger project, however.

Also I found this intriguing, though incomprehensible:

https://docs.djangoproject.com/en/1.7/releases/1.7.3/#database-denial-of-service-with-modelmultiplechoicefield

Thanks very much for your help.

comment:5 by girzel, 9 years ago

Resolution: invalid
Status: newclosed
Note: See TracTickets for help on using tickets.
Back to Top