Cookbooks

How To Improve User Analytics With Django Sessions


In this guide, we will look at how to improve interactions with anonymous users via sessions. This expands on the users apps docs . If you haven’t already we suggest you check that out first.

What is a session

Django sessions let you store and retrieve data on a per-site-visitor-basis. The data is stored on the server and only the session ID is sent to the user.

Tracking anonymous users

We can use Django sessions to keep track of anonymous user visits. This can tell give you important information about app usage, the time it takes to convert users, etc.

Before we can solve this problem, there are two important points to understand about Django sessions.

  • By default, Django will only save sessions to the database once the session has been modified. This means that for anonymous users, a session ID will only be sent once the session is modified.

  • When a session is modified, a new session object is created. This means that at sign-up or sign-in for example the session_key will change since the session object is modified.

Let’s get started. We want Django to persist the session in the database, for that, we will have to modify the session object.

One approach would be to do it in a view.

def example_view(request):
    if request.user.is_anonymous:
        request.session['cached_session_key'] = request.session.session_key

Using the view means the cached_session_key will only be saved when a user visits this page. Another approach is to apply the same logic to every single request. This can be achieved by creating a simple middleware:

apps/users/middleware.py

class AnonymousUserTrackingMiddleware:
    """
    Adds a session key for anonymous users
    This should be placed after the 
    SessionMiddleware but before AuthenticationMiddleware
    """
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        response = self.get_response(request)
        if request.user.is_anonymous:
            has_key = request.session.get('cached_session_key', None)
            if has_key is None:
                request.session['cached_session_key'] = request.session.session_key

        return response

config/settings/base.py

DJANGO_MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    "apps.users.middleware.CustomSessionMiddleware",
     ....
    "apps.users.middleware.AnonymousUserTrackingMiddleware", # enable tracking
    "django.contrib.auth.middleware.AuthenticationMiddleware",

The order of the middleware is important.

  • The tracking middleware is placed after both Authentication and CustomSessionMiddleware.
  • The logic for attaching the cached_session_key parameter only runs after the view and other middleware below have processed their response.
  • Since we are checking if a user is authenticated, we should only do this after the AuthenticationMiddleware has attached a user object if the user signed in.

Associating an authenticated user to a cached session key

The last piece of the puzzle is to associate the cached session key with a user when they sign up. Luckily this part is the easiest. In apps/users/signals.py there is a signal receiver that listens to user signed_up events. We can extract the cached_session_key from the request here and map it to the current user.

@receiver(user_signed_up)
def user_signed_up(sender, request, user, **kwargs):
    cached_session_key = request.session.get('cached_session_key')

    # do something with key for example, see when the session started
    session = CustomUserSession.objects.filter(session_key=cached_session_key).first()
    print(session.last_activity)

Caveats

  • Sessions are stored on the server, but they rely on the browser to send the session id of the current session. This means they are controlled by the user. A user could disable/clear cookies triggering the logic to create a new session.
  • Sessions expire. The same anonymous user could have multiple sessions. If the user uses the same IP you can search for sessions with the same IP.
  • In-app data collected about sessions is limited, i.e. only user agent and IP data are stored. You could extend the CustomUserSession model to store more data about the user, or alternatively, use an analytics platform to track important data about the user and then when a user signs up, use the session ID to associate user data on the platform with the current user. Posthog provides a good example of this.