#6407 closed (worksforme)
ModelChoiceField with widget=HiddenInput doesn't behave as expected
Reported by: | Owned by: | nobody | |
---|---|---|---|
Component: | Forms | Version: | dev |
Severity: | Keywords: | ModelChoiceField HiddenInput | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I'm attempting to render a ModelChoiceField as a HiddenInput widget. I generate the form instance within a template tag, allowing template engineers to drop in the form wherever they want. They pass in a string parameter to the tag representing a User model instance, the tag's Node resolves it, creates a new form instance, passing initial=resolved_user_var
to the form's __init__
method. I'd expect the HTML that gets generated to have an <input type="hidden" name="user" value="<id of user instance>"/>
. However, the value attribute of the input tag is getting set to __unicode__
instead.
Here are the details:
I have a model with a ForeignKey to contrib.auth.models.User. The model looks like this:
class MyModel(models.Models): body = models.TextField() user = models.ForeignKey(User)
I then have a ModelForm for MyModel, called MyForm.
class MyForm(forms.ModelForm): body = forms.CharField(widget=forms.Textarea) user = forms.ModelChoiceField(queryset=User.objects.none(), widget=forms.HiddenInput) class Meta: model = MyModel
When creating the form, which I'm currently doing using a template tag, I know the exact user instance I care about, so I pass that into as the initial argument of MyForm's init:
# Normally a resolved template tag instance, but this will illustrate the point u = User.objects.get(id=1) f = MyForm(initial={'user':u})
However, when rendering the widget from my form, the value attribute of the <input> tag seems to be the result of __unicode__
rather than the ID of the user I supplied:
f = MyForm(initial={'user':u}) f.as_p() >>> u'<p><label for="id_body">Body:</label> <textarea id="id_body" rows="10" cols="40" name="body"></textarea><input type="hidden" name="user" value="andy" id="id_user" /></p>'
I also tried supplying the ID to user_id, rather than the model instance, which outputted no value attribute at all:
f = MyForm(initial={'user_id':u.id}) f.as_p() >>> u'<p><label for="id_body">Body:</label> <textarea id="id_body" rows="10" cols="40" name="body"></textarea><input type="hidden" name="user" id="id_user" /></p>'
The user attribute in question is not the currently logged in user, but another user, hence why I can't just use request.user and need to pass the ID through the POST.
I'm attempting to work on a patch, but wanted to ensure the behavior I'm anticipating is what is expected.
Change History (8)
comment:1 by , 17 years ago
Resolution: | → worksforme |
---|---|
Status: | new → closed |
follow-up: 3 comment:2 by , 16 years ago
Resolution: | worksforme |
---|---|
Status: | closed → reopened |
I'm reopening this ticket becaues there is still a problem, though not exactly as described above. I'm doing essentially the same thing as Andy, and my code is basically identical. When I pass initial={'user': user.id}
the id appears properly as the value of the field, however the form fails to validate. Instead I get this error: (Hidden field user) Select a valid choice. 63 is not one of the available choices.
follow-up: 4 comment:3 by , 16 years ago
Appologies, I did have a typo, I was making the field a ChoiceField rather than a ModelChoiceField. However having corrected that it still is failing, though for a different reason. The form validates just fine now, however I instead get the error that the field does not accept null values.
comment:4 by , 16 years ago
Resolution: | → worksforme |
---|---|
Status: | reopened → closed |
I'm unable to reproduce this.
I don't see how MyForm
would ever validate: for the value of user
to be a valid choice it has to be in the
EmptyQuerySet
produced by
User.objects.none()
, which by definition is empty.
Changing MyForm
so that the
user
field gets passed e.g.
queryset=User.objects.all()
, the form validates just fine:
>>> from django.contrib.auth.models import User >>> from t6407.forms import MyForm >>> >>> u = User.objects.get(pk=1) >>> f = MyForm(initial={'user': u.pk}) >>> print f <tr><th><label for="id_body">Body:</label></th><td><textarea id="id_body" rows=" 10" cols="40" name="body"></textarea><input type="hidden" name="user" value="1" id="id_user" /></td></tr> >>> >>> f = MyForm({'body': '...', 'user': '1'}) >>> f.is_valid() True >>> f.save() <MyModel: MyModel object>
follow-up: 7 comment:5 by , 16 years ago
Resolution: | worksforme |
---|---|
Status: | closed → reopened |
But there's no reason to have to use User.objects.all(). You know the exact object you want. Plus, having a User.objects.all() is dangerous - you're selecting all user objects and exposing to a selected user. Its just too easy to imagine a simple mistake where all users or other type of foreign key objects are exposed to the end user. Isn't there a way to restrict the queryset based on Model's id?
comment:6 by , 16 years ago
Well I found a solution that worked very well. Now I don't have objects.all() throughout my forms.py. This would be great to have in django.
http://www.davidcramer.net/code/109/modelchoicefields-as-charfields-in-django.html
Btw - django rocks
follow-up: 8 comment:7 by , 16 years ago
Resolution: | → worksforme |
---|---|
Status: | reopened → closed |
Replying to oceantara:
But there's no reason to have to use User.objects.all().
If you use User.objects.none()
it is impossible for the form to be valid: for the form to be valid, the user has to be in the queryset of the ModelChoiceField
and User.objects.none()
is empty by definition. You don't have to use User.objects.all()
; it was just an example of a queryset you could use if you want to allow for the possibility of a form that actually validates.
You know the exact object you want.
Great, but it still has to be in the queryset of the ModelChoiceField
for the form to validate, so using User.objects.none()
is not going to work.
And besides, how do you know which object you want? Because the submitted form tells you that's the one you want? If so, how do you know the user hasn't changed the value you sent in the hidden field? If not, then why are you passing around data you already know?
Plus, having a User.objects.all() is dangerous - you're selecting all user objects and exposing to a selected user. Its just too easy to imagine a simple mistake where all users or other type of foreign key objects are exposed to the end user.
No, you're only saying that the form cannot be valid unless the user is one of those in User.objects.all()
. Again, User.objects.all()
is just an example; use whatever non-empty queryset you like.
Isn't there a way to restrict the queryset based on Model's id?
Please use the mailing list for usage questions.
Replying to oceantara:
Well I found a solution that worked very well. Now I don't have objects.all() throughout my forms.py. This would be great to have in django.
http://www.davidcramer.net/code/109/modelchoicefields-as-charfields-in-django.html
So what are you using as the queryset for this InlineModelChoiceField
?
Anyway, that code is broken as-is. (You would have found out if you'd actually tried to validate an instance of MyForm
that uses this field.) You'll at least want to use this in the try
block:
try: key = self.to_field_name or 'pk' return self.queryset.filter(**{key: value}).get() except: self.queryset.model.DoesNotExist:
There is no Django bug here, closing again.
comment:8 by , 16 years ago
No idea what happened there; that section of code should read:
try: key = self.to_field_name or 'pk' return self.queryset.filter(**{key: value}).get() except self.queryset.model.DoesNotExist:
I'm pretty sure that you should just be using: