Django

Code

Making User info available outside requests

A pretty common requirement is to have the current user's info available outside requests. Most of the time, the simplest, easiest and best way to handle this is to write a function, or a method on your model, which accepts the user as an argument; then your view function can simply call it and pass in the user. What follows below is a hackish solution which makes the user available in a far more complex way; it's almost always better to avoid the solution below in favor of writing better-designed code which correctly passes the user around as an argument, however.

Note This technique has become obsolete in the latest django.contrib.admin. If you were previously using this technique, it is strongly suggested that you stop and switch to using built-in functionality of the admin instead. See CookBookNewformsAdminAndUser for one approach or (best solution) take a look at django/contrib/admin/options.py to look at the methods you can override in there -- everything from form creation to object saving is completely customizable, and the methods all have access to the current HTTP request.


Let's suppose you have a model like the following:

from django.db import models
from django.contrib.auth.models import User

class NewsItem(models.Model):
    owner = models.ForeignKey(User,related_name="owner",blank=True, editable=False)
    last_edited_by = models.ForeignKey(User,related_name="news_edited_last",blank=True, editable=False)
    text = models.TextField()

You want to set the owner when a news item is added, and set last_edited_by when a news item is changed. What now? You can't get the user object outside of requests! Fortunately, there's a middleware that makes it possible.

Create a new "middleware" directory in your project directory. Create an empty __init__.py file inside the "middleware" directory. Now, create a "threadlocals.py" file in the "middleware" directory, with the following contents:

# threadlocals middleware
try:
    from threading import local
except ImportError:
    from django.utils._threading_local import local

_thread_locals = local()
def get_current_user():
    return getattr(_thread_locals, 'user', 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.user = getattr(request, 'user', None)

In your project's settings.py file, add "yourproject.middleware.threadlocals.ThreadLocals" to the MIDDLEWARE_CLASSES dir, e.g.:

MIDDLEWARE_CLASSES = (
    "django.middleware.common.CommonMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "yourproject.middleware.threadlocals.ThreadLocals", 
)

Now you only need to override your model's save() method to set the owner and last_edited_by when appropriate:

from django.db import models
from django.contrib.auth.models import User
from yourproject.middleware import threadlocals

class NewsItem(models.Model):
    owner = models.ForeignKey(User,related_name="owner",blank=True)
    last_edited_by = models.ForeignKey(User,related_name="last_edited_by",blank=True)
    text = models.TextField()

    # Set the owner and last_edited_by when appropriate
    def save(self):
        # If the object already existed, it will already have an id
        if self.id:
            # This object is being edited, not saved, set last_edited_by
            self.last_edited_by = threadlocals.get_current_user()
        else:
            # This is a new object, set the owner 
            self.owner = self.last_edited_by = threadlocals.get_current_user()
        super(NewsItem,self).save()

Now, when you save a NewsItem object, the owner and last_edited_by fields are set automatically. That's it! Not that difficult, eh :) You can get the user object anywhere, all you need to to is to call the get_current_user() method.

Tip:

It is important not to specify the project in the path to your middleware class in MIDDLEWARE_CLASSES when using applications within your project and the threadlocals is defined within an application. If not, the middleware does seem to work, but the get_current_user() method will return 'None'. So use:

  myapp.middleware.threadlocals.ThreadLocals

instead of:

  myproject.myapp.middleware.threadlocals.ThreadLocals

(This has been tested on Django version 0.96.1 and 1.0 final)