Google analytics is an integral part of almost any website. Integration requires pasting a simple JavaScript in your webpage.

Although it sounds easy, there are some things that we can take in mind:

  • Making it more configurable, without overkilling with a standalone django app.
  • Actually disabling google analytics for everything that's not production - we don't want to track fake views.

The setup

TL;DR - we are going to setup a Django project with website app there.

Everything below is for Python 3.

For our example, we are going to start with a plain django project, called django_google_analytics:

$ pip install Django # 1.11 by the time of writing
$ django-admin startproject django_google_analytics

Firsts things first, we are going to create a django app called website and configure everything we need for the example:

$ python manage.py startapp website

Adding the app:

# django_google_analytics/settings.py
# ...

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Important line V
    'website.apps.WebsiteConfig'
]

# ...

Url configuration:

# django_google_analytics/urls.py
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('website.urls', namespace='website'))
]
# website/urls.py
from django.conf.urls import url

from .views import index


urlpatterns = [
    url(r'^$', index, name='index')
]

And view:

from django.shortcuts import render


def index(request):
    return render(request, 'index.html')

Templates

To lay foundations for google analytics, we are going to start from the templates. We are going to have base.html and index.html.

In a snippets/ga.html we are going to put the script required for having google analytics.

website
├── templates
    ├── base.html
    └── index.html
    └── snippets
        └── ga.html

In base.html we are going to define a block for google analytics:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  {% block content %}
  {% endblock content %}

  {% block ga %}
  {% endblock ga %}
</body>
</html>

And for the sake of the presentation, let's do a simple index.html:

{% extends "base.html" %}

{% block content %}
  <h1>Hello!</h1>
{% endblock content %}

Our snippets/ga.html is going to contain the following:

<script id="google-analytics" type="text/javascript">
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  ga('create', 'THE_TRACKING_ID_HERE', 'auto');
  ga('send', 'pageview');
</script>

In the ga block we are going to put our google analytics script:

{% block ga %}
  {% include "snippets/ga.html" %}
{% endblock ga %}

You can read more about include here.

Now we have almost everything in place to make things work:

  • We need to provide value for THE_TRACKING_ID_HERE
  • We need to show that snippet only "in production"

GA tracking id

According to Google, the tracking id is:

The tracking ID is a string like UA-000000-2. It must be included in your tracking code to tell Analytics which account and property to send data to.

We need to pass that to our template.

If we have that ga_tracking_id loaded in our template context, we can use template interpolation in snippets/ga.html:

// ...

ga('create', '{{ ga_tracking_id }}', 'auto');

// ...

and change the include in base.html as follows:

{% block ga %}
  {% include "snippets/ga.html" with ga_tracking_id=ga_tracking_id %}
{% endblock ga %}

Now, lets add a settings variable to keep the GA_TRACKING_ID:

# django_google_analytics/settings.py

# ...

GA_TRACKING_ID = 'UA-YOUR-TRACKING-ID'

# ...

Here, we have options. For example, we can use django-environ for that. For the sake of simplicity, we are going to use a hardcoded value.

Injecting GA_TRACKING_ID into template context

Now, we need a way to inject the value of GA_TRACKING_ID into every Django template that we render.

One option is to do it in the view, but this is going to require every view doing it, leading to a lot of repetition.

Fortunately, there's a neat feature in Django called context processors

Quoting the documentation:

A context processor has a very simple interface: It’s a Python function that takes one argument, an HttpRequest object, and returns a dictionary that gets added to the template context.

This means - whatever we return from the context processor, it's going to be available in every template.

We need to write such function and provide a dotted path to it in TEMPLATES[0]['OPTIONS']['context_processors'].

In a file called website/context_processors.py lets do that:

# website/context_processors.py
from django.conf import settings


def ga_tracking_id(request):
    return {'ga_tracking_id': settings.GA_TRACKING_ID}

A small comment - the name of the file can be variable since we provide the exact path to the function. But for consistency reasons, we call it context_processors.py.

Now, the last step is to add that ga_tracking_id as a context processor for our templates.

Change TEMPLATES settings to the following:

# django_google_analytics/settings.py

# ...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                # Important line V
                'website.context_processors.ga_tracking_id'
            ],
        },
    },
]

# ...

Now, if we run our Django & open / we should see that google analytics script is loaded and the value of settings.GA_TRACKING_ID is there.

Production only

There's one last thing that we need to do - make sure we put that GA script only in production.

Now the big question here is - how do we know that we are in production? There are many ways to determine that.

That's why we are going to use a simple feature switch approach - add a boolean USE_GA in settings.py and read from env, defaulting to False. This way, when deploying to production, we can set the right value for the env variable.

In django_google_analytics/settings.py do the following:

# django_google_analytics/settings.py

# ...

"""
1) os.environ is a dict
2) We are using a simple dict literal to parse the string value to boolean
"""
USE_GA = os.environ.get('DJANGO_USE_GA', False)
USE_GA = {'True': True, 'False': False}.get(USE_GA, False)

A small comment - using django-environ is a better solution for the code above.

As you can see, we are defaulting to False by using the second argument of get

Now, we are going to write another context processor, called use_ga which returns the value of USE_GA.

In website/context_processors.py do the following:

# website/context_processors.py
from django.conf import settings


def ga_tracking_id(request):
    return {'ga_tracking_id': settings.GA_TRACKING_ID}


def use_ga(request):
    return {'use_ga': settings.USE_GA}

Before we use it, we need to add that context processor to template settings:

# django_google_analytics/settings.py

# ...

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'website.context_processors.ga_tracking_id',
                # Important line V
                'website.context_processors.use_ga'
            ],
        },
    },
]

# ...

Now, having this in every template, we can finish the GA block in base.html:

{% block ga %}
  {% if use_ga %}
    {% include "snippets/ga.html" with ga_tracking_id=ga_tracking_id %}
  {% endif %}
{% endblock ga %}

And with this, we are ready. Google analytics is going to show up only in production.

You can test this by running the server with different values:

$ DJANGO_USE_GA=True python manage.py runserver

and

$ DJANGO_USE_GA=True python manage.py runserver

Code quality improvement

As you can see, we have 2 context processors, providing values for 2 different settings.

We can make a simple template tag that fetches values from django settings like so:

{% get_from_settings 'USE_GA' %}

To do that, we need to create the following structure:

website
├── templatetags
    ├── __init__.py
    └── settings.py

In website/templatetags/settings.py do the following:

from django import template
from django.conf import settings

register = template.Library()


@register.simple_tag
def get_from_settings(key, default=None):
    return getattr(settings, key, default)

Now we are ready to use that, if we want to. For example:

{% load settings %}

...

{% block ga %}
  {% get_from_settings 'USE_GA' as use_ga %}

  {% if use_ga %}
    {% include "snippets/ga.html" with ga_tracking_id=ga_tracking_id%}
  {% endif %}
{% endblock ga %}

And with that, we are ready.