110

I am trying to request.user for a form's clean method, but how can I access the request object? Can I modify the clean method to allow variables input?

juliomalegria
  • 24,229
  • 14
  • 73
  • 89
nubela
  • 1
  • 24
  • 75
  • 123

11 Answers11

178

The answer by Ber - storing it in threadlocals - is a very bad idea. There's absolutely no reason to do it this way.

A much better way is to override the form's __init__ method to take an extra keyword argument, request. This stores the request in the form, where it's required, and from where you can access it in your clean method.

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyForm, self).__init__(*args, **kwargs)


    def clean(self):
        ... access the request object via self.request ...

and in your view:

myform = MyForm(request.POST, request=request)
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 4
    You are right in this case. However, it may not be desired to modified Forms/Views in this was. Also, there are use cases for thread local store where adding method parameters or instance variables is impossible. Think about callable argument to a query filter that needs access to request data. You can neither add a parameter to the call, nor is there any instance to reference. – Ber Jun 29 '09 at 14:18
  • this is perfect, thanks. threadlocals is useful tho, in the instance of overriding model's save methods whereby request is useful. – nubela Jun 30 '09 at 09:17
  • 4
    It isn't useful when you are extending an admin form, because you can init your form passing the request var. Any idea¿? – Mordi May 26 '11 at 08:48
  • 15
    Why do you say using thread-local storage is a very bad idea? It avoids having to drop the code for passing the request around everywhere. – Michael Mior Dec 22 '11 at 18:43
  • 9
    I wouldn't pass the request object itself to the form, but rather the fields of request that you need (ie user), otherwise you tie your form logic to the request/response cycle which makes testing harder. – Andrew Ingram Jan 29 '13 at 10:34
  • 2
    Chris Pratt has a good solution too, for when dealing with forms in admin.ModelAdmin – radtek Jan 08 '15 at 18:25
  • 1
    `kwargs.pop('request', None)` returns None for me on Django 1.10. I printed kwargs and it doesnt have the request object at all. Did something for Django 1.10? – Anupam Jul 21 '17 at 10:31
  • 2
    @Anupam you need to pass that kwarg in the view, as I show in the second snippet. – Daniel Roseman Jul 21 '17 at 10:33
  • 1
    The solution given by Ber is good if your logic lies in a reusable component (a custom field type), which should be seeminglessly replaceable by the user. – Luis Masuelli Oct 30 '17 at 19:18
  • When you want to access it throught "prepared" Django class views like `CreateView` there's a small trick to know (= your solution doesn't work out of the box) – Olivier Pons Apr 17 '20 at 14:15
47

For what it's worth, if you're using Class Based Views, instead of function based views, override get_form_kwargs in your editing view. Example code for a custom CreateView:

from braces.views import LoginRequiredMixin

class MyModelCreateView(LoginRequiredMixin, CreateView):
    template_name = 'example/create.html'
    model = MyModel
    form_class = MyModelForm
    success_message = "%(my_object)s added to your site."

    def get_form_kwargs(self):
        kw = super(MyModelCreateView, self).get_form_kwargs()
        kw['request'] = self.request # the trick!
        return kw

    def form_valid(self):
        # do something

The above view code will make request available as one of the keyword arguments to the form's __init__ constructor function. Therefore in your ModelForm do:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        # important to "pop" added kwarg before call to parent's constructor
        self.request = kwargs.pop('request')
        super(MyModelForm, self).__init__(*args, **kwargs)
Joseph Victor Zammit
  • 14,760
  • 10
  • 76
  • 102
  • 1
    This worked for me. I make the note because I was using get_form_kwargs anyway due to complex WizardForm logic. No other answer I've seen accounted for WizardForm. – datakid Oct 03 '14 at 05:36
  • 2
    Does anyone besides me think this is just a big mess to do something that's pretty rudimentary for a web framework? Django is great but this makes me not want to use CBV at all, ever. – trpt4him Jul 20 '17 at 15:06
  • 1
    IMHO the benefits of CBVs outweigh the drawbacks of FBVs by far, especially if you work on a large project with 25+ devs writing code that aims for 100% unit test coverage. Not sure if newer versions of Django cater for having the `request` object within `get_form_kwargs` automatically. – Joseph Victor Zammit Jul 20 '17 at 16:59
  • In similar vein, is there any way to access the object instance's ID in get_form_kwargs? – Hassan Baig Mar 01 '18 at 00:22
  • 1
    @HassanBaig Possibly using `self.get_object`? The `CreateView` extends the [`SingleObjectMixin`](https://docs.djangoproject.com/en/1.7/ref/class-based-views/mixins-single-object/#django.views.generic.detail.SingleObjectMixin). But whether this works or raises an exception depends on whether you're creating a new object or updating an existing one; i.e. test both cases (and deletion of course). – Joseph Victor Zammit Mar 22 '18 at 15:24
  • It's more readable to include the additional argument explicitly in the signature: `def __init__(self, *args, request=None, **kwargs)` puts the argument value straight into the parameter, with no need for a `pop`. Then you. just need `self.request = request`. – holdenweb May 25 '23 at 07:57
  • @holdenweb I agree it makes the `pop` redundant. But then we're changing the signature of the `__init__` constructor, making it different from what's found "up" the hierarchy. So it's up to you and what you prefer. My answer prioritises function signature being identical across the hierarchy for example. – Joseph Victor Zammit May 25 '23 at 12:27
36

UPDATED 10/25/2011: I'm now using this with a dynamically created class instead of method, as Django 1.3 displays some weirdness otherwise.

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
        class ModelFormWithRequest(ModelForm):
            def __new__(cls, *args, **kwargs):
                kwargs['request'] = request
                return ModelForm(*args, **kwargs)
        return ModelFormWithRequest

Then override MyCustomForm.__init__ as follows:

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

You can then access the request object from any method of ModelForm with self.request.

Bob Whitelock
  • 167
  • 3
  • 12
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    Chris, that "def __init__(self, request=None, *args, **kwargs)" is bad, because it will end up with request in both the first positional arg and in kwargs. I changed it to "def __init__(self, *args, **kwargs)" and that works. – slinkp Dec 19 '11 at 22:08
  • 1
    Whoops. That was just a mistake on my part. I neglected to update that part of the code when I made the other update. Thanks for the catch. Updated. – Chris Pratt Dec 20 '11 at 15:10
  • 4
    Is this really a metaclass ? I think it just normal overriding, you add request to `__new__`'s kwargs which later on will be passed to the class's `__init__` method. Naming the class `ModelFormWithRequest` I think much clearer in it's meaning than `ModelFormMetaClass`. – k4ml Mar 08 '12 at 16:55
  • 2
    This in NOT a metaclass! See http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python – frnhr Sep 04 '14 at 12:42
18

The usual aproach is to store the request object in a thread-local reference using a middleware. Then you can access this from anywhere in you app, including the Form.clean() method.

Changing the signature of the Form.clean() method means you have you own, modified version of Django, which may not be what you want.

Thank middleware count look something like this:

import threading
_thread_locals = threading.local()

def get_current_request():
    return getattr(_thread_locals, 'request', None)

class ThreadLocals(object):
    """
    Middleware that gets various objects from the
    request object and saves them in thread local storage.
    """
    def process_request(self, request):
        _thread_locals.request = request

Register this middleware as described in the Django docs

Ber
  • 40,356
  • 16
  • 72
  • 88
  • 2
    Despite the above comments, this method works whereas the other method does not. Setting an attribute of the form object in __init__ does not reliably carry over to clean methods, whereas setting the thread locals does allow this data to be carried over. – rplevy Oct 21 '09 at 01:15
  • 4
    @rplevy have you actually passed the request object when you create an instance of the form? In case you haven't noticed it uses keyword arguments `**kwargs`, which means you will have to pass the request object as `MyForm(request.POST, request=request)`. – unode Mar 04 '11 at 22:57
14

For Django admin, in Django 1.8

class MyModelAdmin(admin.ModelAdmin):
    ...
    form = RedirectForm

    def get_form(self, request, obj=None, **kwargs):
        form = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form
François Constant
  • 5,531
  • 1
  • 33
  • 39
  • 1
    The top-rated method further above indeed seems to have stopped working somewhere between Django 1.6 and 1.9. This one does work and is much shorter. Thanks! – Raik Apr 27 '16 at 22:16
9

I ran into this particular problem when customizing the admin. I wanted a certain field to be validated based on the particular admin's credentials.

Since I did not want to modify the view to pass the request as an argument to the form, the following is what I did:

class MyCustomForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def clean(self):
        # make use of self.request here

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        def form_wrapper(*args, **kwargs):
            a = ModelForm(*args, **kwargs)
            a.request = request
            return a
    return form_wrapper
entropy
  • 3,134
  • 20
  • 20
6

The answer by Daniel Roseman is still the best. However, I would use the first positional argument for the request instead of the keyword argument for a few reasons:

  1. You don't run the risk of overriding a kwarg with the same name
  2. The request is optional which is not right. The request attribute should never be None in this context.
  3. You can cleanly pass the args and kwargs to the parent class without having to modify them.

Lastly, I would use a more unique name to avoid overriding an existing variable. Thus, My modified answer looks like:

class MyForm(forms.Form):

  def __init__(self, request, *args, **kwargs):
      self._my_request = request
      super(MyForm, self).__init__(*args, **kwargs)


  def clean(self):
      ... access the request object via self._my_request ...
Community
  • 1
  • 1
Andres Restrepo
  • 389
  • 5
  • 6
5

You can't always use this method (and its probably bad practice), but if you are only using the form in one view you could scope it inside the view method itself.

def my_view(request):

    class ResetForm(forms.Form):
        password = forms.CharField(required=True, widget=forms.PasswordInput())

        def clean_password(self):
            data = self.cleaned_data['password']
            if not request.user.check_password(data):
                raise forms.ValidationError("The password entered does not match your account password.")
            return data

    if request.method == 'POST':
        form = ResetForm(request.POST, request.FILES)
        if form.is_valid():

            return HttpResponseRedirect("/")
    else:
        form = ResetForm()

    return render_to_response(request, "reset.html")
Chris
  • 732
  • 7
  • 6
  • This is sometimes a really nice solution: I often do this in a CBV `get_form_class` method, if I know I need to do lots of stuff with the request. There may be some overhead in repeatedly creating the class, but that just moves it from import time to run-time. – Matthew Schinckel Feb 23 '17 at 03:44
3

fresh cheese from cheesebaker@pypi: django-requestprovider

user237419
  • 8,829
  • 4
  • 31
  • 38
3

I have another answer to this question as per your requirement you want to access the user into the clean method of the form. You can Try this. View.py

person=User.objects.get(id=person_id)
form=MyForm(request.POST,instance=person)

forms.py

def __init__(self,*arg,**kwargs):
    self.instance=kwargs.get('instance',None)
    if kwargs['instance'] is not None:
        del kwargs['instance']
    super(Myform, self).__init__(*args, **kwargs)

Now you can access the self.instance in any clean method in form.py

Nishant Kashyap
  • 819
  • 2
  • 15
  • 25
1

When you want to access it through "prepared" Django class views like CreateView there's a small trick to know (= the official solution doesn't work out of the box). In your own CreateView you'll have to add code like this:

class MyCreateView(LoginRequiredMixin, CreateView):
    form_class = MyOwnForm
    template_name = 'my_sample_create.html'

    def get_form_kwargs(self):
        result = super().get_form_kwargs()
        result['request'] = self.request
        return result

= in short this is the solution to pass request to your form with Django's Create/Update views.

Olivier Pons
  • 15,363
  • 26
  • 117
  • 213