I have a form with a field
that I don’t want to show on the page. In the template I don’t use {{ form }}
, but {{ form.field }}
where field
is every model field that I want to be seen by users.
The problem here is that if you know what to use and you know forms a bit, you will find a solution in no time. Others, join me!
Template
So, I don’t use {{ form }}
in the template, but each visible field is presented {{ form.field }}
. Like this:
<form method="post">
{% csrf_token %}
{{ form.spirits }}
{{ form.snacks }}
{{ form.candy }}
</form>
You see form fields spirits, snacks and candy. But my form has another field – user_location
😈.
So I want to pass it to the form from the view.
View
Option 1
Version 1
I can do that with 'initial={'user_location': 'Moscow'}
:
def delicious_view(request):
template = 'delicious.html'
template_thanks = 'thanks_sweety.html'
if request.method == 'POST':
form = DeliciousApplyForm(request.POST)
if form.is_valid():
apply = form.save()
return render(request, template_thanks, {})
else:
form = DeliciousApplyForm(initial={'user_location': 'Moscow'})
context = {'form': form}
return render(request, template, context)
Option 2
Or you can pass kwargs
to the GET’s form initialization in the view instead of initial={'user_location' : 'Moscow'}
:
form = DeliciousApplyForm({'user_location': 'Moscow'})
And in the form’s __init__
:
def __init__(self, *args, **kwargs):
user_location_val = kwargs.pop('user_location', 'Moscow')
super(DeliciousApplyForm, self).__init__(*args, **kwargs)
self.fields['user_location'].initial = user_location_val
Option 3
The same effect can be achieved with these few lines in the form’s __init__
:
def __init__(self, *args, **kwargs):
kwargs.update(initial={'user_location': 'Moscow'})
super(DeliciousApplyForm, self).__init__(*args, **kwargs)
You can just write ‘Moscow’ or pass kwargs
to the form like in Option 2 and then get them and then update kwargs
, lol. Redundant, but for the sake of it:
def __init__(self, *args, **kwargs):
user_location_val = kwargs.pop('user_location', 'Moscow')
kwargs.update(initial={'user_location': user_location_val})
super(DeliciousApplyForm, self).__init__(*args, **kwargs)
Or use args
…
Option 4 (upd. 07.18.20)
Or you can change the request.POST
(it is a QueryDict
instance) and pass the changed one to the ModelForm
:
def delicious_view(request):
template = 'delicious.html'
template_thanks = 'thanks_sweety.html'
if request.method == 'POST':
new_post = request.POST.copy()
new_post['user_location'] = 'Moscow'
form = DeliciousApplyForm(new_post)
if form.is_valid():
apply = form.save()
return render(request, template_thanks, {})
context = {'form': form}
return render(request, template, context)
This Option 4 does not require to do anything with your ModelForm
.
The problem is that request.POST
is mutable, meaning it can’t be changed. And this Option 4 works changes that, if only for one view
. I don’t approve of changing Django design like this. Either way, I decided to add this for educational purposes.
Form
In order to for this to work together, you need to give user_location
a forms.HiddenInput()
widget in the form’s widgets
:
class SupportApplyForm(ModelForm):
class Meta:
model = SupportApply
fields = ['spirits', 'snacks', 'candy', 'user_location']
widgets = {
'user_location': forms.HiddenInput()
}
It won’t work unless…
You add {{ form.user_location }}
to the template:
<form method="post">
{% csrf_token %}
{{ form.spirits }}
{{ form.snacks }}
{{ form.candy }}
{{ form.user_location }}
</form>
🤷