This commit is contained in:
Basel Mahmoud 2026-03-13 05:57:29 +00:00 committed by GitHub
commit c836e48947
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 219 additions and 0 deletions

View file

@ -25,6 +25,7 @@ TAG_MODULES = [
'analytical.gosquared',
'analytical.heap',
'analytical.hotjar',
'analytical.contentsquare',
'analytical.hubspot',
'analytical.intercom',
'analytical.kiss_insights',

View file

@ -0,0 +1,63 @@
"""
Contentsquare template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import disable_html, get_required_setting, is_internal_ip
CONTENTSQUARE_TRACKING_CODE = """\
<script>
(function(c,s,q,u,a,r,e){
c.hj=c.hj||function(){(c.hj.q=c.hj.q||[]).push(arguments)};
c._hjSettings={hjid:a};
r=s.getElementsByTagName('head')[0];
e=s.createElement('script');
e.async=true;
e.src=q+c._hjSettings.hjid+u;
r.appendChild(e);
})(window,document,'https://static.hj.contentsquare.net/c/csq-','.js',%(CONTENTSQUARE_SITE_ID)s);
</script>
"""
register = Library()
def _validate_no_args(token):
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
@register.tag
def contentsquare(parser, token):
"""
Contentsquare template tag.
"""
_validate_no_args(token)
return ContentsquareNode()
class ContentsquareNode(Node):
def __init__(self):
self.site_id = get_required_setting(
'CONTENTSQUARE_SITE_ID',
re.compile(r'^\d+$'),
"must be (a string containing) a number",
)
def render(self, context):
html = CONTENTSQUARE_TRACKING_CODE % {'CONTENTSQUARE_SITE_ID': self.site_id}
if is_internal_ip(context, 'CONTENTSQUARE'):
return disable_html(html, 'Contentsquare')
else:
return html
def contribute_to_analytical(add_node):
# ensure properly configured
ContentsquareNode()
add_node('head_bottom', ContentsquareNode)

View file

@ -0,0 +1,74 @@
=====================================
Contentsquare -- enterprise digital experience analytics
=====================================
`Contentsquare`_ is an enterprise digital experience analytics platform that provides comprehensive insights into user behavior across web, mobile, and other digital touchpoints.
.. _`Contentsquare`: https://contentsquare.com/
.. contentsquare-installation:
Installation
============
To start using the Contentsquare 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 Contentsquare 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:`contentsquare-configuration`.
The Contentsquare code is inserted into templates using template tags.
Because every page that you want to track must have the tag,
it is useful to add it to your base template.
At the top of the template, load the :mod:`contentsquare` template tag library.
Then insert the :ttag:`contentsquare` tag at the bottom of the head section::
{% load contentsquare %}
<html>
<head>
...
{% contentsquare %}
</head>
...
</html>
.. _contentsquare-configuration:
Configuration
=============
Before you can use the Contentsquare integration, you must first set your Site ID.
.. _contentsquare-id:
Setting the Contentsquare Site ID
----------------------------------
You can find the Contentsquare Site ID in the "Sites & Organizations" section of your Contentsquare account.
Set :const:`CONTENTSQUARE_SITE_ID` in the project :file:`settings.py` file::
CONTENTSQUARE_SITE_ID = 'XXXXXXXXX'
If you do not set a Contentsquare Site ID, the tracking code will not be rendered.
.. _contentsquare-internal-ips:
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:`CONTENTSQUARE_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.

View file

@ -0,0 +1,81 @@
"""
Tests for the Contentsquare template tags.
"""
import pytest
from django.http import HttpRequest
from django.template import Context, Template, TemplateSyntaxError
from django.test import override_settings
from utils import TagTestCase
from analytical.templatetags.analytical import _load_template_nodes
from analytical.templatetags.contentsquare import ContentsquareNode
from analytical.utils import AnalyticalException
expected_html = """\
<script>
(function(c,s,q,u,a,r,e){
c.hj=c.hj||function(){(c.hj.q=c.hj.q||[]).push(arguments)};
c._hjSettings={hjid:a};
r=s.getElementsByTagName('head')[0];
e=s.createElement('script');
e.async=true;
e.src=q+c._hjSettings.hjid+u;
r.appendChild(e);
})(window,document,'https://static.hj.contentsquare.net/c/csq-','.js',123456789);
</script>
"""
@override_settings(CONTENTSQUARE_SITE_ID='123456789')
class ContentsquareTagTestCase(TagTestCase):
maxDiff = None
def test_tag(self):
html = self.render_tag('contentsquare', 'contentsquare')
assert expected_html == html
def test_node(self):
html = ContentsquareNode().render(Context({}))
assert expected_html == html
def test_tags_take_no_args(self):
with pytest.raises(TemplateSyntaxError, match="'contentsquare' takes no arguments"):
Template('{% load contentsquare %}{% contentsquare "arg" %}').render(Context({}))
@override_settings(CONTENTSQUARE_SITE_ID=None)
def test_no_id(self):
with pytest.raises(AnalyticalException, match="CONTENTSQUARE_SITE_ID setting is not set"):
ContentsquareNode()
@override_settings(CONTENTSQUARE_SITE_ID='invalid')
def test_invalid_id(self):
expected_pattern = (
r"^CONTENTSQUARE_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$")
with pytest.raises(AnalyticalException, match=expected_pattern):
ContentsquareNode()
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
def test_render_internal_ip(self):
request = HttpRequest()
request.META['REMOTE_ADDR'] = '1.1.1.1'
context = Context({'request': request})
actual_html = ContentsquareNode().render(context)
disabled_html = '\n'.join([
'<!-- Contentsquare disabled on internal IP address',
expected_html,
'-->',
])
assert disabled_html == actual_html
def test_contribute_to_analytical(self):
"""
`contentsquare.contribute_to_analytical` registers the head and body nodes.
"""
template_nodes = _load_template_nodes()
assert template_nodes == {
'head_top': [],
'head_bottom': [ContentsquareNode],
'body_top': [],
'body_bottom': [],
}