Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely access request object in Django models

What I am trying to do:

I am trying to access request object in my django models so that I can get the currently logged in user with request.user.

What I have tried:

I found a hack on this site. But someone in the comments pointed out not to do it when in production.

I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'

Models.py:

class TestManager(models.Manager):
    def user_test(self):
        return self.filter(user=self.request.user, viewed=False)

class Test(models.Model):

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

    user = models.ForeignKey(User, related_name='test') 
    viewed = models.BooleanField(default=False)
    objects = TestManager()
like image 395
Ahtisham Avatar asked Sep 07 '18 09:09

Ahtisham


People also ask

What is request object in Django?

Django uses request and response objects to pass state through the system. When a page is requested, Django creates an HttpRequest object that contains metadata about the request. Then Django loads the appropriate view, passing the HttpRequest as the first argument to the view function.

What kind of data is passed into a view in the request object?

The request object has view input field values in name/value pairs. When we create a submit button then the request type POST is created and calls the POST method. We have four data, those are in Name-Value pairs.

What is request Meta in the request object?

META contains all the metadata of the HTTP request that is coming to your Django server, it can contain the user agent, ip address, content type, and so on.

What are request get and request post objects?

So, to request a response from the server, there are mainly two methods: GET : to request data from the server. POST : to submit data to be processed to the server.


1 Answers

I trying to access request object in my Django models so that I can get the currently logged in user with request.user.

Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.

The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.

Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).

You thus will need to pass the request, or user object to the user_test method. For example with:

from django.http import HttpRequest

class TestManager(models.Manager):
    def user_test(self, request_or_user):
        if isinstance(request_or_user, HttpRequest):
            return self.filter(user=request_or_user.user, viewed=False)
        else:
            return self.filter(user=request_or_user, viewed=False)

one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:

class TestManager(models.Manager):
    def user_test(self, user):
            return self.filter(user=user, viewed=False)

So in a view one can use this as:

def some_view(request):
    some_tests = Test.objects.user_test(request.user)
    # ...
    # return Http response

For example if we want to render a template with this queryset, we can pass it like:

def some_view(request):
    some_tests = Test.objects.user_test(request.user)
    # ...
    return render(request, 'my_template.html', {'some_tests': some_tests})
like image 174
Willem Van Onsem Avatar answered Sep 28 '22 06:09

Willem Van Onsem