django: how to access current request user in ModelForm?

If you're using Class Based Views (CBVs) then passing an extra argument in the form constructor (e.g. in get_forms_class) or in form_class will not work, as <form> object is not callable will be shown.

The solution for CBVs is to use get_form_kwargs(), e.g.:

views.py:

class MyUpdateView(UpdateView):

    model = MyModel
    form_class = MyForm

    # Sending user object to the form, to verify which fields to display/remove (depending on group)
    def get_form_kwargs(self):
        kwargs = super(MyUpdateView, self).get_form_kwargs()
        kwargs.update({'user': self.request.user})
        return kwargs

forms.py:

class MyForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user')  # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole

        super(MyForm, self).__init__(*args, **kwargs)

        # If the user does not belong to a certain group, remove the field
        if not self.user.groups.filter(name__iexact='mygroup').exists():
            del self.fields['confidential']

you can pass the user object as an extra argument in the form constructor.

e.g.

f = MyForm(user=request.user)

and the constructor will look like:

class MyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
         self.user = kwargs.pop('user',None)
         super(MyForm, self).__init__(*args, **kwargs)

and then use user in the clean_XX forms as you wish


You may reference the user object using the instance attribute within the instance it self.

Ex; self.instance.user

class StatusForm(ModelForm):

    # def __init__(self, *args, **kwargs):
    #     self.user = kwargs.pop('user', None)
    #     super(StatusForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Status
        fields = [
            'user',
            'content',
            'image'
        ]

    def clean_content(self):
        content = self.cleaned_data.get("content", None)
        if len(content) > 240:
            raise ValidationError(f"Hey {self.instance.user.username}, the content is too long")
        return content

My small addition,

I had a requirement where one of the model choice fields of the form is dependent on the request.user, and it took a while to take my head around.

The idea is that

  1. you need to have a __init__ method in the model form class,

  2. and you access the request or other parameters from the arguments of the __init__ method,

  3. then you need to call the super constructor to new up the form class
  4. and then you set the queryset of the required field

code sample

class CsvUploadForm(forms.Form):

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user')
        super(CsvUploadForm, self).__init__(*args, **kwargs)
        self.fields['lists'].queryset = List.objects.filter(user=user)

    lists = forms.ModelChoiceField(queryset=None, widget=forms.Select, required=True)

as you can see, the lists variable is dependent on the current user, which is available via request object, so we set the queryset of the field as null, and its assigned dynamically from the constructor later.

Take a look into the order of the statements in the above code

you can pass the user variable like this from the view file

form = CsvUploadForm(user=request.user)

or with other POST, FILE data like below

form = CsvUploadForm(request.POST, request.FILES, user=request.user)