14

For my website pretty much every page has a header bar displaying "Welcome, ABC" where "ABC" is the username. That means request.user will be called for every single request resulting in database hits over and over again.

But once a user is logged in, I should be able to store his user instance in his cookie and encrypt it. That way I can avoid hitting the database repeatedly and just retrieve request.user from the cookie instead.

How would you modify Django to do this? Is there any Django plugins that does what I need?

Thanks

Continuation
  • 12,722
  • 20
  • 82
  • 106

4 Answers4

6

You want to use the session middleware, and you'll want to read the documentation. The session middleware supports multiple session engines. Ideally you'd use memcached or redis, but you could write your own session engine to store all the data in the user's cookie. Once you enable the middleware, it's available as part of the request object. You interact with request.session, which acts like a dict, making it easy to use. Here are a couple of examples from the docs:

This simplistic view sets a has_commented variable to True after a user posts a comment. It doesn’t let a user post a comment more than once:

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

This simplistic view logs in a "member" of the site:

def login(request):
    m = Member.objects.get(username=request.POST['username'])
    if m.password == request.POST['password']:
        request.session['member_id'] = m.id
        return HttpResponse("You're logged in.")
    else:
        return HttpResponse("Your username and password didn't match.")
Zach Kelling
  • 52,505
  • 13
  • 109
  • 108
  • 1
    But I thought the session object is still stored in the back end session engine, not in a cookie. So retrieving the session object would still require hitting the database, no? – Continuation May 11 '11 at 02:08
  • 1
    @Continuation No not necessarily, the session engine supports many different backends, one of which is a db-backend. I'll try to clarify my answer. – Zach Kelling May 11 '11 at 03:03
6

This smells of over-optimisation. Getting a user from the db is a single hit per request, or possibly two if you use a Profile model as well. If your site is such that an extra two queries makes a big difference to performance, you may have bigger problems.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 1
    Agree totally. This is not significant enough to worry about. If you are truly worried about database queries, you should already be using something like memcached, and that makes it even more pointless. If you're not using memcached, stop and install it now. That alone will due more for your db optimization efforts than anything else you can tweak in Django. – Chris Pratt May 11 '11 at 17:12
  • 4
    Not very sure it is an over-optimisation. For a website designed for over 1M users. I think it good to try to avoid database hit when possible. Espeically here, it's not very expensive to do that. – Jcyrss Apr 19 '17 at 03:10
  • I am developing an app where each DRF view has a series of custom permissions and each permission checks for for request.user at least once. The app is getting quite large, it not makes 60.000 requests / 30 seconds to the users table on production. Granted, these are the simplest requests ever (select * from users where id=...) and the ID is a PK, but still we are starting to feel a need for optimization. I would avoid storing user data in a cookie for security reasons. I am still trying to figure out a way to optimize these requests but still maintain a db-based session engine. – Chris Feb 18 '21 at 13:06
2

The user is attached to the request object using the Authentication Middleware provided by django (django.contrib.auth.middleware). It users a function the get_user function in django.contrib.auth.init to get the user from the backend you are using. You can easily change this function to look for the user in another location (e.g. cookie).

When a user is logged in, django puts the userid in the session (request.session[SESSION_KEY]=user.id). When a user logs off, it erases the user's id from the session. You can override these login and logoff functions to also store a user object in the browsers cookie / erase user object from cookie in the browser. Both of these functions are also in django.contrib.auth.init

See here for settting cookies: Django Cookies, how can I set them?

Community
  • 1
  • 1
systemizer
  • 355
  • 2
  • 6
1

Once you have proper caching the number of database hits should be reduced significantly - then again I'm not really and expert on caching. I think it would be a bad idea to modify request.user to solve your problem. I think a better solution would be to create some utility, method or custom template tag that attempts to load your require user data from the cookie, and return the result. If the user data is not found in the cookie, then a call to request.user should be made, save the data to the cookie, and then return the result. You could possibly use a post_save signal to check for changes to the user data, so that you can make update to the cookie as required.

solartic
  • 4,249
  • 3
  • 25
  • 26