From 8596dfe1fd63d496d10a483bd97a9e43d1efcfee Mon Sep 17 00:00:00 2001 From: Basel Mahmoud Date: Fri, 4 Apr 2025 08:02:55 +0200 Subject: [PATCH 1/8] added the content square docs,template tag and tests --- analytical/templatetags/content_square.py | 63 ++++++++++++++++++ docs/services/content_square.rst | 74 +++++++++++++++++++++ tests/unit/test_tag_content_square.py | 81 +++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 analytical/templatetags/content_square.py create mode 100644 docs/services/content_square.rst create mode 100644 tests/unit/test_tag_content_square.py diff --git a/analytical/templatetags/content_square.py b/analytical/templatetags/content_square.py new file mode 100644 index 0000000..97019d6 --- /dev/null +++ b/analytical/templatetags/content_square.py @@ -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 = """\ + +""" + +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) diff --git a/docs/services/content_square.rst b/docs/services/content_square.rst new file mode 100644 index 0000000..ee0a4a8 --- /dev/null +++ b/docs/services/content_square.rst @@ -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 %} + + + ... + {% contentsquare %} + + ... + + + +.. _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. + diff --git a/tests/unit/test_tag_content_square.py b/tests/unit/test_tag_content_square.py new file mode 100644 index 0000000..919ebc8 --- /dev/null +++ b/tests/unit/test_tag_content_square.py @@ -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 = """\ + +""" + +@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([ + '', + ]) + 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': [], + } From d014859a82b6030ffaf3946636e29b69e65752bf Mon Sep 17 00:00:00 2001 From: Basel Mahmoud Date: Fri, 4 Apr 2025 08:07:51 +0200 Subject: [PATCH 2/8] Update tests/unit/test_tag_content_square.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/unit/test_tag_content_square.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_tag_content_square.py b/tests/unit/test_tag_content_square.py index 919ebc8..ab7694f 100644 --- a/tests/unit/test_tag_content_square.py +++ b/tests/unit/test_tag_content_square.py @@ -8,7 +8,7 @@ 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.templatetags.content_square import ContentsquareNode from analytical.utils import AnalyticalException expected_html = """\ From 4560b8bba8539d32d1a56ee0b6010eb3bc05c969 Mon Sep 17 00:00:00 2001 From: basel Date: Thu, 27 Nov 2025 02:09:29 +0200 Subject: [PATCH 3/8] register tag in the library --- analytical/templatetags/analytical.py | 1 + 1 file changed, 1 insertion(+) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index ee6aa42..d680cea 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -25,6 +25,7 @@ TAG_MODULES = [ 'analytical.gosquared', 'analytical.heap', 'analytical.hotjar', + 'analytical.contentsquare', 'analytical.hubspot', 'analytical.intercom', 'analytical.kiss_insights', From be21039af196533dc4ec4f126bb564612a8c6cee Mon Sep 17 00:00:00 2001 From: basel Date: Thu, 27 Nov 2025 02:11:43 +0200 Subject: [PATCH 4/8] fix typo --- analytical/templatetags/analytical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index d680cea..8ef3458 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -25,7 +25,7 @@ TAG_MODULES = [ 'analytical.gosquared', 'analytical.heap', 'analytical.hotjar', - 'analytical.contentsquare', + 'analytical.content_square', 'analytical.hubspot', 'analytical.intercom', 'analytical.kiss_insights', From 268d2221d68eb29ae2ee81665e5b82afc3eba43c Mon Sep 17 00:00:00 2001 From: basel Date: Thu, 27 Nov 2025 02:14:53 +0200 Subject: [PATCH 5/8] removing the underscore in content_square --- analytical/templatetags/analytical.py | 2 +- analytical/templatetags/{content_square.py => contentsquare.py} | 0 tests/unit/test_tag_content_square.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename analytical/templatetags/{content_square.py => contentsquare.py} (100%) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index 8ef3458..d680cea 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -25,7 +25,7 @@ TAG_MODULES = [ 'analytical.gosquared', 'analytical.heap', 'analytical.hotjar', - 'analytical.content_square', + 'analytical.contentsquare', 'analytical.hubspot', 'analytical.intercom', 'analytical.kiss_insights', diff --git a/analytical/templatetags/content_square.py b/analytical/templatetags/contentsquare.py similarity index 100% rename from analytical/templatetags/content_square.py rename to analytical/templatetags/contentsquare.py diff --git a/tests/unit/test_tag_content_square.py b/tests/unit/test_tag_content_square.py index ab7694f..919ebc8 100644 --- a/tests/unit/test_tag_content_square.py +++ b/tests/unit/test_tag_content_square.py @@ -8,7 +8,7 @@ from django.test import override_settings from utils import TagTestCase from analytical.templatetags.analytical import _load_template_nodes -from analytical.templatetags.content_square import ContentsquareNode +from analytical.templatetags.contentsquare import ContentsquareNode from analytical.utils import AnalyticalException expected_html = """\ From 35d70f07fdda4470f89c6e73159da22f4222adb4 Mon Sep 17 00:00:00 2001 From: basel Date: Thu, 27 Nov 2025 02:18:28 +0200 Subject: [PATCH 6/8] matching the expected string with the test string --- tests/unit/test_tag_content_square.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_tag_content_square.py b/tests/unit/test_tag_content_square.py index 919ebc8..2c780f7 100644 --- a/tests/unit/test_tag_content_square.py +++ b/tests/unit/test_tag_content_square.py @@ -14,14 +14,14 @@ from analytical.utils import AnalyticalException expected_html = """\ """ From 8f10210ec4198bb2f223c0c9da68c72bf9678604 Mon Sep 17 00:00:00 2001 From: basel Date: Thu, 27 Nov 2025 02:21:57 +0200 Subject: [PATCH 7/8] add the actuall values in the expected string --- tests/unit/test_tag_content_square.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_tag_content_square.py b/tests/unit/test_tag_content_square.py index 2c780f7..dd67875 100644 --- a/tests/unit/test_tag_content_square.py +++ b/tests/unit/test_tag_content_square.py @@ -15,13 +15,13 @@ expected_html = """\ """ From fafe73a2a18f400d6df8710e506a617041f4bd01 Mon Sep 17 00:00:00 2001 From: basel Date: Thu, 27 Nov 2025 02:32:11 +0200 Subject: [PATCH 8/8] update the tracking code to match the correct one provided by content square --- analytical/templatetags/contentsquare.py | 14 +++++++------- tests/unit/test_tag_content_square.py | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/analytical/templatetags/contentsquare.py b/analytical/templatetags/contentsquare.py index 97019d6..5caa442 100644 --- a/analytical/templatetags/contentsquare.py +++ b/analytical/templatetags/contentsquare.py @@ -10,15 +10,15 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip CONTENTSQUARE_TRACKING_CODE = """\ """ diff --git a/tests/unit/test_tag_content_square.py b/tests/unit/test_tag_content_square.py index dd67875..e104482 100644 --- a/tests/unit/test_tag_content_square.py +++ b/tests/unit/test_tag_content_square.py @@ -13,15 +13,15 @@ from analytical.utils import AnalyticalException expected_html = """\ """