Fix gtag user_id setup and add support for custom dimensions (#226)

* Fixed user_id setup for gtag according to latest docs
* Added the possibility to include custom dimensions to be sent along with every event
* Added custom dimensions section to the gtag docs
* Added explicit python code blocks in the docs
* Reformat test_tag_google_analytics_gtag.py with Ruff formatter
This commit is contained in:
Erick Massip 2025-07-21 10:10:04 +01:00 committed by GitHub
parent a751ffa5e3
commit 7253a3a048
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 114 additions and 25 deletions

View file

@ -1,7 +1,9 @@
"""
Google Analytics template tags and filters, using the new analytics.js library.
Google Analytics template tags and filters, using the new gtag.js library.
https://developers.google.com/tag-platform/gtagjs/reference
"""
import json
import re
from django.template import Library, Node, TemplateSyntaxError
@ -23,13 +25,10 @@ SETUP_CODE = """
function gtag(){{dataLayer.push(arguments);}}
gtag('js', new Date());
{extra}
gtag('config', '{property_id}');
gtag('config', '{property_id}', {custom_dimensions});
</script>
"""
GTAG_SET_CODE = """gtag('set', {{'{key}': '{value}'}});"""
register = Library()
@ -59,21 +58,15 @@ class GoogleAnalyticsGTagNode(Node):
)
def render(self, context):
other_fields = {}
custom_dimensions = context.get('google_analytics_custom_dimensions', {})
identity = get_identity(context, 'google_analytics_gtag')
identity = get_identity(context, prefix='google_analytics_gtag')
if identity is not None:
other_fields['user_id'] = identity
custom_dimensions['user_id'] = identity
extra = '\n'.join(
[
GTAG_SET_CODE.format(key=key, value=value)
for key, value in other_fields.items()
]
)
html = SETUP_CODE.format(
property_id=self.property_id,
extra=extra,
custom_dimensions=json.dumps(custom_dimensions),
)
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')

View file

@ -117,3 +117,52 @@ or in the template:
{% endwith %}
.. _`Google Analytics conditions`: https://developers.google.com/analytics/solutions/crm-integration#user_id
.. _google-analytics-custom-dimensions:
Custom dimensions
----------------
As described in the Google Analytics `custom dimensions`_ documentation
page, you can define custom dimensions which are variables specific to your
business needs. These variables can include both custom event parameters as
well as customer user properties. Using the template context variable
``google_analytics_custom_dimensions``, you can let the :ttag:`google_analytics_gtag`
pass custom dimensions to Google Analytics automatically. The ``google_analytics_custom_dimensions``
variable must be set to a dictionary where the keys are the dimension names
and the values are the dimension values. You can set the context variable in your
view when you render a template containing the tracking code::
.. code-block:: python
context = RequestContext({
'google_analytics_custom_dimensions': {
'gender': 'female',
'country': 'US',
'user_properties': {
'age': 25
}
}
})
return some_template.render(context)
Note that the ``user_properties`` key is used to pass user properties to Google
Analytics. It's not necessary to always use this key, but that'd be the way of
sending user properties to Google Analytics automatically.
You may want to set custom dimensions in a context processor that you add
to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`::
.. code-block:: python
def google_analytics_segment_language(request):
try:
return {'google_analytics_custom_dimensions': {'language': request.LANGUAGE_CODE}}
except AttributeError:
return {}
Just remember that if you set the same context variable in the
:class:`~django.template.context.RequestContext` constructor and in a
context processor, the latter clobbers the former.
.. _`custom dimensions`: https://support.google.com/analytics/answer/10075209

View file

@ -16,7 +16,7 @@ from analytical.utils import AnalyticalException
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='UA-123456-7')
class GoogleAnalyticsTagTestCase(TagTestCase):
"""
Tests for the ``google_analytics_js`` template tag.
Tests for the ``google_analytics_gtag`` template tag.
"""
def test_tag(self):
@ -25,7 +25,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'UA-123456-7');" in r
assert "gtag('config', 'UA-123456-7', {});" in r
def test_node(self):
r = GoogleAnalyticsGTagNode().render(Context())
@ -33,7 +33,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'UA-123456-7');" in r
assert "gtag('config', 'UA-123456-7', {});" in r
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID=None)
def test_no_property_id(self):
@ -57,7 +57,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
def test_identify(self):
r = GoogleAnalyticsGTagNode().render(Context({'user': User(username='test')}))
assert "gtag('set', {'user_id': 'test'});" in r
assert 'gtag(\'config\', \'UA-123456-7\', {"user_id": "test"});' in r
def test_identity_context_specific_provider(self):
"""
@ -68,12 +68,13 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
Context(
{
'google_analytics_gtag_identity': 'foo_gtag_identity',
'analytical_identity': 'bar_analytical_identity',
'user': User(username='test'),
}
)
)
assert "gtag('set', {'user_id': 'foo_gtag_identity'});" in r
assert (
'gtag(\'config\', \'UA-123456-7\', {"user_id": "foo_gtag_identity"});' in r
)
def test_identity_context_general(self):
"""
@ -87,7 +88,10 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
}
)
)
assert "gtag('set', {'user_id': 'bar_analytical_identity'});" in r
assert (
'gtag(\'config\', \'UA-123456-7\', {"user_id": "bar_analytical_identity"});'
in r
)
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='G-12345678')
def test_tag_with_measurement_id(self):
@ -96,7 +100,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=G-12345678"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'G-12345678');" in r
assert "gtag('config', 'G-12345678', {});" in r
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='AW-1234567890')
def test_tag_with_conversion_id(self):
@ -105,7 +109,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=AW-1234567890"></script'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'AW-1234567890');" in r
assert "gtag('config', 'AW-1234567890', {});" in r
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='DC-12345678')
def test_tag_with_advertiser_id(self):
@ -114,4 +118,47 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=DC-12345678"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'DC-12345678');" in r
assert "gtag('config', 'DC-12345678', {});" in r
def test_tag_with_custom_dimensions(self):
r = GoogleAnalyticsGTagNode().render(
Context(
{
'google_analytics_custom_dimensions': {
'dimension_1': 'foo',
'dimension_2': 'bar',
'user_properties': {
'user_property_1': True,
'user_property_2': 'xyz',
},
},
}
)
)
assert (
"gtag('config', 'UA-123456-7', {"
'"dimension_1": "foo", '
'"dimension_2": "bar", '
'"user_properties": {'
'"user_property_1": true, '
'"user_property_2": "xyz"}});' in r
)
def test_tag_with_identity_and_custom_dimensions(self):
r = GoogleAnalyticsGTagNode().render(
Context(
{
'google_analytics_gtag_identity': 'foo_gtag_identity',
'google_analytics_custom_dimensions': {
'dimension_1': 'foo',
'dimension_2': 'bar',
},
}
)
)
assert (
"gtag('config', 'UA-123456-7', {"
'"dimension_1": "foo", '
'"dimension_2": "bar", '
'"user_id": "foo_gtag_identity"});' in r
)