-
-
Notifications
You must be signed in to change notification settings - Fork 167
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5487fd6
commit adee3f7
Showing
4 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
""" | ||
Google Analytics template tags and filters, using the new gtag.js library. | ||
gtag.js documentation found at: https://developers.google.com/analytics/devguides/collection/gtagjs/ | ||
API reference at: https://developers.google.com/gtagjs/reference/api | ||
""" | ||
|
||
from __future__ import absolute_import | ||
|
||
import re | ||
import json | ||
from django.conf import settings | ||
from django.template import Library, Node, TemplateSyntaxError | ||
|
||
from analytical.utils import ( | ||
disable_html, | ||
get_required_setting, | ||
is_internal_ip, | ||
) | ||
|
||
GA_MEASUREMENT_ID_RE = re.compile(r'[a-zA-Z\d_\-]+') | ||
|
||
SETUP_CODE = """ | ||
<script async src="https://www.googletagmanager.com/gtag/js?id={property_id}"></script> | ||
<script> | ||
window.dataLayer = window.dataLayer || []; | ||
function gtag(){{dataLayer.push(arguments);}} | ||
gtag('js', new Date()); | ||
{set_commands} | ||
gtag('config', '{property_id}', {config_parameters_json}); | ||
</script> | ||
""" | ||
|
||
CUSTOM_SET_KV_CODE = "gtag('set', '{key}', {value_json});" | ||
CUSTOM_SET_DATA_CODE = "gtag('set', {value_json});" | ||
|
||
# You are allowed to config more than one GA_MEASUREMENT_ID on a page. | ||
# This could be used, but for now is not. | ||
CUSTOM_CONFIG_CODE = "gtag('config', '{property_id}', {config_parameters_json});" | ||
|
||
register = Library() | ||
|
||
|
||
@register.tag | ||
def google_gtag_js(parser, token): | ||
""" | ||
Google Analytics Global Site Tag tracking template tag. | ||
Renders Javascript code to track page visits. You must supply | ||
your website property ID (as a string) in the | ||
``GOOGLE_GTAG_JS_PROPERTY_ID`` setting. | ||
""" | ||
bits = token.split_contents() | ||
if len(bits) > 1: | ||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) | ||
return GoogleGTagJsNode() | ||
|
||
|
||
class GoogleGTagJsNode(Node): | ||
def __init__(self): | ||
self.property_id = get_required_setting( | ||
'GOOGLE_GTAG_JS_PROPERTY_ID', GA_MEASUREMENT_ID_RE, | ||
"must be a string like a slug") | ||
|
||
def render(self, context): | ||
config_parameters = self._get_config_parameters(context) | ||
config_parameters_json = self._to_json(config_parameters) | ||
|
||
set_commands = self._get_set_commands(context) | ||
|
||
html = SETUP_CODE.format( | ||
property_id=self.property_id, | ||
config_parameters_json=config_parameters_json, | ||
set_commands=" ".join(set_commands), | ||
) | ||
if is_internal_ip(context, 'GOOGLE_ANALYTICS'): | ||
html = disable_html(html, 'Google Analytics') | ||
return html | ||
|
||
def _get_config_parameters(self, context): | ||
config_data = getattr( | ||
settings, 'GOOGLE_GTAG_JS_DEFAULT_CONFIG', {}, | ||
) | ||
|
||
config_data.update(context.get('google_gtag_js_config_data', {})) | ||
|
||
return config_data | ||
|
||
def _to_json(self, data, default="{}"): | ||
try: | ||
return json.dumps(data) | ||
except ValueError: | ||
return default | ||
except TypeError: | ||
return default | ||
|
||
def _get_set_commands(self, context): | ||
commands = [] | ||
|
||
if 'google_gtag_js_set_data' in context: | ||
try: | ||
commands.append(CUSTOM_SET_DATA_CODE.format( | ||
value_json=json.dumps(context['google_gtag_js_set_data']), | ||
)) | ||
except ValueError: | ||
pass | ||
|
||
values = ( | ||
context.get('google_gtag_js_set%s' % i) for i in range(1, 6) | ||
) | ||
params = [(i, v) for i, v in enumerate(values, 1) if v is not None] | ||
|
||
for _, var in params: | ||
key_name = var[0] | ||
value = var[1] | ||
try: | ||
value_json = json.dumps(value) | ||
except ValueError: | ||
value_json = json.dumps(str(value)) | ||
commands.append(CUSTOM_SET_KV_CODE.format( | ||
key=key_name, | ||
value_json=value_json, | ||
)) | ||
return commands | ||
|
||
|
||
def contribute_to_analytical(add_node): | ||
GoogleGTagJsNode() # ensure properly configured | ||
add_node('head_top', GoogleGTagJsNode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
""" | ||
Tests for the Google Analytics template tags and filters, using the new analytics.js library. | ||
""" | ||
|
||
from django.http import HttpRequest | ||
from django.template import Context | ||
from django.test.utils import override_settings | ||
|
||
from analytical.templatetags.google_gtag_js import GoogleGTagJsNode | ||
from analytical.tests.utils import TagTestCase | ||
from analytical.utils import AnalyticalException | ||
|
||
|
||
@override_settings(GOOGLE_GTAG_JS_PROPERTY_ID='UA-123456-7') | ||
class GoogleGTagJsTagTestCase(TagTestCase): | ||
""" | ||
Tests for the ``google_gtag_js`` template tag. | ||
""" | ||
|
||
def test_tag(self): | ||
r = self.render_tag('google_gtag_js', 'google_gtag_js') | ||
self.assertTrue("""<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script> | ||
<script> | ||
window.dataLayer = window.dataLayer || []; | ||
function gtag(){dataLayer.push(arguments);} | ||
gtag('js', new Date());""" in r, r) | ||
self.assertTrue("gtag('config', 'UA-123456-7'" in r, r) | ||
|
||
def test_node(self): | ||
r = GoogleGTagJsNode().render(Context()) | ||
self.assertTrue("""<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script> | ||
<script> | ||
window.dataLayer = window.dataLayer || []; | ||
function gtag(){dataLayer.push(arguments);} | ||
gtag('js', new Date());""" in r, r) | ||
self.assertTrue("gtag('config', 'UA-123456-7'" in r, r) | ||
|
||
@override_settings(GOOGLE_GTAG_JS_PROPERTY_ID=None) | ||
def test_no_property_id(self): | ||
self.assertRaises(AnalyticalException, GoogleGTagJsNode) | ||
|
||
def test_custom_set_vars(self): | ||
context = Context({ | ||
'google_gtag_js_set1': ('test1', 'foo'), | ||
'google_gtag_js_set2': ('test2', 'bar'), | ||
'google_gtag_js_set4': ('test4', 1), | ||
'google_gtag_js_set5': ('test5', 2.2), | ||
}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue("""gtag('set', 'test1', "foo");""" in r, r) | ||
self.assertTrue("""gtag('set', 'test2', "bar");""" in r, r) | ||
self.assertTrue("""gtag('set', 'test4', 1);""" in r, r) | ||
self.assertTrue("""gtag('set', 'test5', 2.2);""" in r, r) | ||
|
||
def test_custom_set_data(self): | ||
context = Context({ | ||
'google_gtag_js_set_data': {'test1': 'foo'}, | ||
}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue("""gtag('set', {"test1": "foo"});""" in r, r) | ||
|
||
def test_custom_set_data_not_ignored(self): | ||
context = Context({ | ||
'google_gtag_js_set_data': {'test1': 'foo'}, | ||
'google_gtag_js_set2': ('test2', 'bar'), | ||
}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue("""gtag('set', {"test1": "foo"});""" in r, r) | ||
self.assertTrue("""gtag('set', 'test2', "bar");""" in r, r) | ||
|
||
def test_custom_config_context_dic(self): | ||
context = Context({ | ||
'google_gtag_js_config_data': {'test1': True}, | ||
'google_gtag_js_set1': ('shouldnt_affect', 'config'), | ||
}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue("""gtag('config', 'UA-123456-7', {"test1": true});""" in r, r) | ||
|
||
@override_settings( | ||
GOOGLE_GTAG_JS_DEFAULT_CONFIG={'test1': True}, | ||
) | ||
def test_custom_config_defaults_dic(self): | ||
context = Context({ | ||
'google_gtag_js_config_data': {}, | ||
}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue("""gtag('config', 'UA-123456-7', {"test1": true});""" in r, r) | ||
|
||
@override_settings( | ||
GOOGLE_GTAG_JS_DEFAULT_CONFIG={'test1': True}, | ||
) | ||
def test_custom_config_context_overrides_defaults(self): | ||
context = Context({ | ||
'google_gtag_js_config_data': {'test1': False}, | ||
}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue("""gtag('config', 'UA-123456-7', {"test1": false});""" in r, r) | ||
|
||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) | ||
def test_render_internal_ip(self): | ||
req = HttpRequest() | ||
req.META['REMOTE_ADDR'] = '1.1.1.1' | ||
context = Context({'request': req}) | ||
r = GoogleGTagJsNode().render(context) | ||
self.assertTrue(r.startswith( | ||
'<!-- Google Analytics disabled on internal IP address'), r) | ||
self.assertTrue(r.endswith('-->'), r) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
====================================== | ||
Google Analytics (gtag.js) -- traffic analysis | ||
====================================== | ||
|
||
`Google Analytics`_ is the well-known web analytics service from | ||
Google. The product is aimed more at marketers than webmasters or | ||
technologists, supporting integration with AdWords and other e-commence | ||
features. | ||
This uses the gtags.js version of google analytics. | ||
|
||
.. _`Google Analytics`: https://developers.google.com/analytics/ | ||
|
||
|
||
.. google-analytics-installation: | ||
Installation | ||
============ | ||
|
||
To start using the Google Analytics integration, you must have installed | ||
the django-analytical package and have added the ``analytical`` | ||
application to :const:`INSTALLED_APPS` in your project | ||
:file:`settings.py` file. See :doc:`../install` for details. | ||
|
||
Next you need to add the Google Analytics template tag to your | ||
templates. This step is only needed if you are not using the generic | ||
:ttag:`analytical.*` tags. If you are, skip to | ||
:ref:`google-analytics-configuration`. | ||
|
||
The Google Analytics tracking code is inserted into templates using a | ||
template tag. Load the :mod:`google_gtag_js` template tag library and | ||
insert the :ttag:`google_gtag_js` tag. Because every page that you | ||
want to track must have the tag, it is useful to add it to your base | ||
template. Insert the tag at the top of the HTML head:: | ||
|
||
{% load google_gtag_js %} | ||
<html> | ||
<head> | ||
{% google_gtag_js %} | ||
... | ||
|
||
</head> | ||
... | ||
|
||
|
||
.. _google-analytics-configuration: | ||
|
||
Configuration | ||
============= | ||
|
||
Before you can use the Google Analytics integration, you must first set | ||
your website property ID. Then you can set any analytics config variables | ||
you wish Google Analytics to track. | ||
|
||
|
||
.. _google-analytics-property-id: | ||
|
||
Setting the property ID | ||
----------------------- | ||
|
||
Every website you track with Google Analytics gets its own property ID, | ||
and the :ttag:`google_gtag_js` tag will include it in the rendered | ||
Javascript code. You can find the web property ID on the overview page | ||
of your account. Set :const:`GOOGLE_GTAG_JS_PROPERTY_ID` in the | ||
project :file:`settings.py` file:: | ||
|
||
GOOGLE_GTAG_JS_PROPERTY_ID = 'UA-XXXXXX-X' | ||
|
||
If you do not set a property ID, the tracking code will not be rendered. | ||
|
||
|
||
Google Analytics Tracking Config | ||
--------------------- | ||
|
||
:ttag:`google_gtag_js` works by letting you use gtag.js 's | ||
'set' and 'config' `javascript commands | ||
<https://developers.google.com/gtagjs/reference/api>`_ | ||
. | ||
The 'set' gtag commands are inserted before the 'config' commands. | ||
|
||
You are given the option to 'set' gtag values for a request | ||
via context variable ``google_gtag_js_set_data``. If used, this should be | ||
a json serializable object, to be used like ``gtag('set', <value>)``. | ||
|
||
Additionally, you can use ``google_gtag_js_set1`` though | ||
``google_gtag_js_set5``, which should each be key value pairs, to be | ||
used like ``gtag('set', <key>, <value>)``. | ||
Key should be a string, and value a json serializable object (which includes strings). | ||
|
||
|
||
At the present, :ttag:`google_gtag_js` only supports | ||
configuring one 'GA_MEASUREMENT_ID' property. | ||
The options for this config can be set in the following ways: | ||
|
||
1. Via setting: :const:`GOOGLE_GTAG_JS_DEFAULT_CONFIG` - which should | ||
be a json serializable dictionary of all default config | ||
options. This value will be used as the default config on all pages. | ||
2. Via the context variable ``google_gtag_js_config_data``, again a | ||
json serializable dictionary. | ||
The resultant config options will be made from the default config | ||
updated with the values of this 'per request' config. | ||
|
||
Note re config options: | ||
Provided you find the right key name, you should be able to configure | ||
the gtag tracking however you need it. | ||
You can use :file:`settings.py` options such as:: | ||
|
||
GOOGLE_GTAG_JS_DEFAULT_CONFIG = { | ||
'anonymize_ip': True, | ||
'send_page_view': False, | ||
'custom_map': { | ||
'dimension<Index>': 'dimension_name', | ||
}, | ||
} | ||
|
||
|
||
|
||
You may also like to create a context processor for setting the gtag | ||
'set' and 'config' options per request, that you add to the | ||
:data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`, eg:: | ||
|
||
def process_google_gtag_options(request): | ||
google_gtag_options = {} | ||
google_gtag_options['google_gtag_js_set1'] = ('dimension1', request.some_data) | ||
google_gtag_options['google_gtag_js_config_data'] = { | ||
'currency': 'USD', | ||
'country': 'US', | ||
'custom_map': {'metric5': 'avg_page_load_time'}, | ||
} | ||
return google_gtag_options | ||
|
||
|
||
|
||
Internal IP addresses | ||
--------------------- | ||
|
||
Usually you do not want to track clicks from your development or | ||
internal IP addresses. By default, if the tags detect that the client | ||
comes from any address in the :const:`GOOGLE_ANALYTICS_INTERNAL_IPS` | ||
setting, the tracking code is commented out. It takes the value of | ||
:const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is | ||
:const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for | ||
important information about detecting the visitor IP address. | ||
|
||
|