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>
🤷