Add multiple domains support for Google Analytics

This commit is contained in:
Joost Cassee 2011-09-17 23:13:25 +02:00
parent 39af53a8c8
commit 1ae5d5c4d7
6 changed files with 161 additions and 10 deletions

View file

@ -1,5 +1,6 @@
Development
-----------
* Added multiple domains support for Google Analytics.
* Fixed bug in deleted settings testing code (Eric Davis).
Version 0.9.2

View file

@ -6,9 +6,11 @@ from __future__ import absolute_import
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
from analytical.utils import is_internal_ip, disable_html, \
get_required_setting, get_domain, AnalyticalException
def enumerate(sequence, start=0):
"""Copy of the Python 2.6 `enumerate` builtin for compatibility."""
@ -18,6 +20,10 @@ def enumerate(sequence, start=0):
n += 1
TRACK_SINGLE_DOMAIN = 1
TRACK_MULTIPLE_SUBDOMAINS = 2
TRACK_MULTIPLE_DOMAINS = 3
SCOPE_VISITOR = 1
SCOPE_SESSION = 2
SCOPE_PAGE = 3
@ -38,6 +44,9 @@ SETUP_CODE = """
</script>
"""
DOMAIN_CODE = "_gaq.push(['_setDomainName', '%s']);"
NO_ALLOW_HASH_CODE = "_gaq.push(['_setAllowHash', false]);"
ALLOW_LINKER_CODE = "_gaq.push(['_setAllowLinker', true]);"
CUSTOM_VAR_CODE = "_gaq.push(['_setCustomVar', %(index)s, '%(name)s', " \
"'%(value)s', %(scope)s]);"
@ -65,13 +74,31 @@ class GoogleAnalyticsNode(Node):
"must be a string looking like 'UA-XXXXXX-Y'")
def render(self, context):
commands = self._get_custom_var_commands(context)
commands = self._get_domain_commands(context)
commands.extend(self._get_custom_var_commands(context))
html = SETUP_CODE % {'property_id': self.property_id,
'commands': " ".join(commands)}
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')
return html
def _get_domain_commands(self, context):
commands = []
tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE',
TRACK_SINGLE_DOMAIN)
if tracking_type == TRACK_SINGLE_DOMAIN:
pass
else:
domain = get_domain(context, 'google_analytics')
if domain is None:
raise AnalyticalException("tracking multiple domains with Google"
" Analytics requires a domain name")
commands.append(DOMAIN_CODE % domain)
commands.append(NO_ALLOW_HASH_CODE)
if tracking_type == TRACK_MULTIPLE_DOMAINS:
commands.append(ALLOW_LINKER_CODE)
return commands
def _get_custom_var_commands(self, context):
values = (context.get('google_analytics_var%s' % i)
for i in range(1, 6))

View file

@ -5,12 +5,15 @@ Tests for the Google Analytics template tags and filters.
from django.http import HttpRequest
from django.template import Context
from analytical.templatetags.google_analytics import GoogleAnalyticsNode
from analytical.tests.utils import TagTestCase, override_settings, SETTING_DELETED
from analytical.templatetags.google_analytics import GoogleAnalyticsNode, \
TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS
from analytical.tests.utils import TestCase, TagTestCase, override_settings, \
without_apps, SETTING_DELETED
from analytical.utils import AnalyticalException
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7')
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7',
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN)
class GoogleAnalyticsTagTestCase(TagTestCase):
"""
Tests for the ``google_analytics`` template tag.
@ -34,6 +37,22 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
def test_wrong_property_id(self):
self.assertRaises(AnalyticalException, GoogleAnalyticsNode)
@override_settings(
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS,
GOOGLE_ANALYTICS_DOMAIN='example.com')
def test_track_multiple_subdomains(self):
r = GoogleAnalyticsNode().render(Context())
self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r)
self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r)
@override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
GOOGLE_ANALYTICS_DOMAIN='example.com')
def test_track_multiple_domains(self):
r = GoogleAnalyticsNode().render(Context())
self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r)
self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r)
self.assertTrue("_gaq.push(['_setAllowLinker', true]);" in r, r)
def test_custom_vars(self):
context = Context({'google_analytics_var1': ('test1', 'foo'),
'google_analytics_var5': ('test2', 'bar', 1)})
@ -52,3 +71,15 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
self.assertTrue(r.startswith(
'<!-- Google Analytics disabled on internal IP address'), r)
self.assertTrue(r.endswith('-->'), r)
@without_apps('django.contrib.sites')
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7',
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
GOOGLE_ANALYTICS_DOMAIN=SETTING_DELETED,
ANALYTICAL_DOMAIN=SETTING_DELETED)
class NoDomainTestCase(TestCase):
def test_exception_without_domain(self):
context = Context()
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
context)

View file

@ -2,14 +2,15 @@
Tests for the analytical.utils module.
"""
from django.conf import settings
from django.contrib.sites.models import Site
from django.http import HttpRequest
from django.template import Context
from django.conf import settings
from analytical.utils import (
is_internal_ip, get_required_setting, AnalyticalException)
get_domain, is_internal_ip, get_required_setting, AnalyticalException)
from analytical.tests.utils import (
TestCase, override_settings, SETTING_DELETED)
TestCase, override_settings, with_apps, SETTING_DELETED)
class SettingDeletedTestCase(TestCase):
@ -40,6 +41,37 @@ class SettingDeletedTestCase(TestCase):
user_id = get_required_setting("USER_ID", "\d+", "invalid USER_ID")
@override_settings(ANALYTICAL_DOMAIN="example.org")
class GetDomainTestCase(TestCase):
def test_get_service_domain_from_context(self):
context = Context({'test_domain': 'example.com'})
self.assertEqual(get_domain(context, 'test'), 'example.com')
def test_get_analytical_domain_from_context(self):
context = Context({'analytical_domain': 'example.com'})
self.assertEqual(get_domain(context, 'test'), 'example.com')
@override_settings(TEST_DOMAIN="example.net")
def test_get_service_domain_from_settings(self):
context = Context()
self.assertEqual(get_domain(context, 'test'), 'example.net')
def test_get_analytical_domain_from_settings(self):
context = Context()
self.assertEqual(get_domain(context, 'test'), 'example.org')
@with_apps('django.contrib.sites')
@override_settings(TEST_DOMAIN=SETTING_DELETED,
ANALYTICAL_DOMAIN=SETTING_DELETED)
class GetDomainTestCaseWithSites(TestCase):
def test_get_domain_from_site(self):
site = Site.objects.create(domain="example.com", name="test")
with override_settings(SITE_ID=site.id):
context = Context()
self.assertEqual(get_domain(context, 'test'), 'example.com')
class InternalIpTestCase(TestCase):
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])

View file

@ -3,6 +3,8 @@ Utility function for django-analytical.
"""
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
HTML_COMMENT = "<!-- %(service)s disabled on internal IP " \
@ -78,6 +80,31 @@ def get_identity(context, prefix=None, identity_func=None, user=None):
return None
def get_domain(context, prefix):
"""
Return the domain used for the tracking code. Each service may be
configured with its own domain (called `<name>_domain`), or a
django-analytical-wide domain may be set (using `analytical_domain`.
If no explicit domain is found in either the context or the
settings, try to get the domain from the contrib sites framework.
"""
domain = context.get('%s_domain' % prefix)
if domain is None:
domain = context.get('analytical_domain')
if domain is None:
domain = getattr(settings, '%s_DOMAIN' % prefix.upper(), None)
if domain is None:
domain = getattr(settings, 'ANALYTICAL_DOMAIN', None)
if domain is None:
if 'django.contrib.sites' in settings.INSTALLED_APPS:
try:
domain = Site.objects.get_current().domain
except (ImproperlyConfigured, Site.DoesNotExist):
pass
return domain
def is_internal_ip(context, prefix=None):
"""
Return whether the visitor is coming from an internal IP address,

View file

@ -46,8 +46,9 @@ Configuration
=============
Before you can use the Google Analytics integration, you must first set
your website property ID. You can also add custom segments for Google
Analytics to track.
your website property ID. If you track multiple domains with the same
code, you also need to set-up the domain. Finally, you can add custom
segments for Google Analytics to track.
.. _google-analytics-property-id:
@ -66,6 +67,38 @@ project :file:`settings.py` file::
If you do not set a property ID, the tracking code will not be rendered.
Tracking multiple domains
-------------------------
The default code is suitable for tracking a single domain. If you track
multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE`
setting to one of the :const:`analytical.google_analytics.SCOPE_*`
constants:
============================= ===== =============================================
Constant Value Description
============================= ===== =============================================
``TRACK_SINGLE_DOMAIN`` 1 Track one domain.
``TRACK_MULTIPLE_SUBDOMAINS`` 2 Track multiple subdomains of the same top
domain (e.g. `fr.example.com` and
`nl.example.com`).
``TRACK_MULTIPLE_DOMAINS`` 3 Track multiple top domains (e.g. `example.fr`
and `example.nl`).
============================= ===== =============================================
As noted, the default tracking style is
:const:`~analytical.google_analytics.TRACK_SINGLE_DOMAIN`.
When you track multiple (sub)domains, django-analytical needs to know
what domain name to pass to Google Analytics. If you use the contrib
sites app, the domain is automatically picked up from the current
:const:`~django.contrib.sites.models.Site` instance. Otherwise, you may
either pass the domain to the template tag through the context variable
:const:`google_analytics_domain` (fallback: :const:`analytical_domain`)
or set it in the project :file:`settings.py` file using
:const:`GOOGLE_ANALYTICS_DOMAIN` (fallback: :const:`ANALYTICAL_DOMAIN`).
.. _google-analytics-internal-ips:
Internal IP addresses