Django: how to pass the user object into form classes
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.
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.
Django Forms for Many-To-Many Fields
Building forms with Django is straightforward if your fields are simple inputs like text fields, but what if your forms…
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.
model = Meal
fields = [‘name’, ‘date’, ‘members’]
name = forms.CharField()
date = forms.DateInput() members = forms.ModelMultipleChoiceField(
Failing to filter objects from the Member model gives me this:
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?
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?
- Pass the request object from the view to the form instance.
- Define a Queryset that filters for users.
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.