diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c27024b..bf2c686 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 diff --git a/analytical/templatetags/google_analytics.py b/analytical/templatetags/google_analytics.py index 24fcdde..0aad6f3 100644 --- a/analytical/templatetags/google_analytics.py +++ b/analytical/templatetags/google_analytics.py @@ -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 = """ """ +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)) diff --git a/analytical/tests/test_tag_google_analytics.py b/analytical/tests/test_tag_google_analytics.py index 477a08b..3885337 100644 --- a/analytical/tests/test_tag_google_analytics.py +++ b/analytical/tests/test_tag_google_analytics.py @@ -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( ''), 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) \ No newline at end of file diff --git a/analytical/tests/test_utils.py b/analytical/tests/test_utils.py index c0aceca..b975d66 100644 --- a/analytical/tests/test_utils.py +++ b/analytical/tests/test_utils.py @@ -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']) diff --git a/analytical/utils.py b/analytical/utils.py index 202b699..db31706 100644 --- a/analytical/utils.py +++ b/analytical/utils.py @@ -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 = "