Passing kwargs to ModelForm and changing field value // 1-minute guide 🍬👻 (upd. 07.18.20)

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>

🤷

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.