Django: how to pass the user object into form classes

Alice Ridgway
Analytics Vidhya
Published in
3 min readSep 27, 2020

--

Update (June 2022): I now publish Django tutorials to my own website. If you’re interested in this post (or got lost in this post), then you might find my guide to ManyToMany fields useful.

Yesterday, I published a tutorial on how to build forms with ManyToMany fields. The back-end work was almost complete, except for a bug which we will fix in this post.

Some context:

I’m currently building a meal planning app. When a user creates a new meal, I want them to have checkboxes to select which household members will be joining the meal. The list of household members will be unique to each user.

In the previous post, I implemented the form functionality using a ModelMultipleChoiceField class and replaced the default widget with checkboxes. You can read more about that here.

The Problem

The ModelMultipleChoiceField is a Django form class designed for a multiple-choice input where the choices come from another model.

This class takes a compulsory argument called Queryset. The Queryset allows the developer to filter the choices from the other model. In this case, I only want members associated with the current user to be displayed as options.


forms.py
class CreateMealForm(forms.ModelForm):
class Meta:
model = Meal
fields = [‘name’, ‘date’, ‘members’]

name = forms.CharField()
date = forms.DateInput()
members = forms.ModelMultipleChoiceField(
queryset=Member.objects.all(),
widget=forms.CheckboxSelectMultiple
)

Failing to filter objects from the Member model gives me this:

queryset=member.objects.all() doesn’t work because we can see members for all users

This doesn’t work because I can see the names of household members of another user. The current logged in user has three household members: Anna, Adelaide, and Axel. My user doesn’t want to see Jack, Jess, Jane, and Jim as well.

We need to access the user object from inside the form class

Surely I can just do this?

queryset=Member.objects.filter(user=self.request.user),

Unfortunately not.

Attempting this, Django raised the error that the ‘self’ object was not recognized.

Another issue is the request is not a default attribute of form classes.

What do we need to do?

  1. Pass the request object from the view to the form instance.
  2. Define a Queryset that filters for users.

Solution

First, in your view (I’m using class-based), add a method called ‘get_form_kwargs(self)’.

We are overriding the get_form_kwargs method so that we can add additional keyword arguments to be passed to the form instance.

Then, in your form class, we override the __init__ function to unpack the request object from the kwargs.

What’s going on here?

In views.py, we added the request object to the list of keyword arguments (kwargs) by overriding the get_form_kwargs method.

By adding ‘request’ to kwargs, forms.py now has access to it.

We still can’t call ‘request’ straight from the Queryset. ‘Request’ is attached to ‘self’, and ‘self’ refers to a single instance of a class. Queryset is part of the broader class definition (like a blueprint), so anything put there will apply to all instances (forms), which will be different for every user.

Instead, we can set the Queryset inside the __init__ function. This is the class constructor, a method which is called when the class is instantiated. Code run inside __init__ is specific to one instance (and one user), so we can use the ‘self’ object.

We specify our Queryset inside the __init__ function:

self.fields[‘members’].queryset = Member.objects.filter( user=self.request.user)

Our CustomModelMultipleChoiceField still requires a Queryset argument, but we can just say ‘None’. ‘None’ will be overwritten by the Queryset inside __init__.

Now we only see members for a single user.

Thanks for reading.

--

--