Django: persist some session state across login/logout boundaries

By definition session state is tied to the session. When you log out, you obviously don't want the server remembering who you are. Likewise, session state is typically lost when an formerly anonymous user logs in. But very occasionally, there might be some pieces of information you're storing against the session that you DO want to persist even when the user logs in or out.

For example, say you're keeping track of where a user is coming from when they come to your site. So you put request.META.HTTP_REFERER into request.session['HTTP_REFERER']. Maybe you're going to use this information after they create an account, if they ever do.

If you find yourself in such a situation, here is a decorator that you can throw onto the view(s) where the user is being logged in/out to preserve a subset of the session variables.

from functools import wraps

class persist_session_vars(object):
    """ Some views, such as login and logout, will reset all session state.
    However, we occasionally want to persist some of those session variables.

    session_backup = {}

    def __init__(self, vars):
        self.vars = vars

    def __enter__(self):
        for var in self.vars:
            self.session_backup[var] = self.request.session.get(var)

    def __exit__(self, exc_type, exc_value, traceback):
        for var in self.vars:
            self.request.session[var] = self.session_backup.get(var)

    def __call__(self, test_func, *args, **kwargs):

        def inner(*args, **kwargs):
            if not args:
                raise Exception('Must decorate a view, ie a function taking request as the first parameter')
            self.request = args[0]
            with self:
                return test_func(*args, **kwargs)

        return inner

This class is both a decorator and a context manager. In fact, it's more like a decorator that manages the context of the function that it's decorating.

If you're calling auth.login or auth.logout yourself, you would put the decorator on the view where that happens. If you're delegating to the built-in view for those, you can easily wrap them.

from django.contrib.auth import views

def login(request, *args, **kwargs):
    """ we want to persist SOME session variables, even after logout """
    return views.login(request, *args, **kwargs)