From 8b12e33e69e7f11cb505b95adcc0fc5ff0cf234d Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Wed, 9 Jul 2025 17:28:48 +0200 Subject: [PATCH] Add more config options for Woopra Closes #100 --- CHANGELOG.rst | 1 + analytical/templatetags/woopra.py | 42 ++++++++++++-- docs/services/woopra.rst | 32 +++++++++++ tests/unit/test_tag_woopra.py | 95 ++++++++++++++++++++++++++++++- 4 files changed, 164 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f00dbdc..a35cb34 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Unreleased license metadata field (Peter Bittner) * Remove AUTHORS file to avoid confusion; this is now metadata maintained in pyproject.toml (Peter Bittner) +* Add more configuration options for Woopra (Peter Bittner) Version 3.1.0 ------------- diff --git a/analytical/templatetags/woopra.py b/analytical/templatetags/woopra.py index 9f8956d..80cf9f4 100644 --- a/analytical/templatetags/woopra.py +++ b/analytical/templatetags/woopra.py @@ -4,11 +4,13 @@ Woopra template tags and filters. import json import re +from contextlib import suppress from django.conf import settings from django.template import Library, Node, TemplateSyntaxError from analytical.utils import ( + AnalyticalException, disable_html, get_identity, get_required_setting, @@ -66,10 +68,42 @@ class WoopraNode(Node): def _get_settings(self, context): variables = {'domain': self.domain} - try: - variables['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT) - except AttributeError: - pass + woopra_int_settings = { + 'idle_timeout': 'WOOPRA_IDLE_TIMEOUT', + } + woopra_str_settings = { + 'cookie_name': 'WOOPRA_COOKIE_NAME', + 'cookie_domain': 'WOOPRA_COOKIE_DOMAIN', + 'cookie_path': 'WOOPRA_COOKIE_PATH', + 'cookie_expire': 'WOOPRA_COOKIE_EXPIRE', + } + woopra_bool_settings = { + 'click_tracking': 'WOOPRA_CLICK_TRACKING', + 'download_tracking': 'WOOPRA_DOWNLOAD_TRACKING', + 'outgoing_tracking': 'WOOPRA_OUTGOING_TRACKING', + 'outgoing_ignore_subdomain': 'WOOPRA_OUTGOING_IGNORE_SUBDOMAIN', + 'ignore_query_url': 'WOOPRA_IGNORE_QUERY_URL', + 'hide_campaign': 'WOOPRA_HIDE_CAMPAIGN', + } + + for key, name in woopra_int_settings.items(): + with suppress(AttributeError): + variables[key] = getattr(settings, name) + if type(variables[key]) is not int: + raise AnalyticalException(f'{name} must be an int value') + + for key, name in woopra_str_settings.items(): + with suppress(AttributeError): + variables[key] = getattr(settings, name) + if type(variables[key]) is not str: + raise AnalyticalException(f'{name} must be a string value') + + for key, name in woopra_bool_settings.items(): + with suppress(AttributeError): + variables[key] = getattr(settings, name) + if type(variables[key]) is not bool: + raise AnalyticalException(f'{name} must be a boolean value') + return variables def _get_visitor(self, context): diff --git a/docs/services/woopra.rst b/docs/services/woopra.rst index 631109c..1a8b122 100644 --- a/docs/services/woopra.rst +++ b/docs/services/woopra.rst @@ -94,6 +94,38 @@ a page reporting. So it’s important to keep the number reasonable in order to accurately make predictions. +Cookie control +-------------- + +Optional settings that influence the cookie behavior:: + + WOOPRA_COOKIE_NAME = "wooTracker" + WOOPRA_COOKIE_DOMAIN = ".example.com" + WOOPRA_COOKIE_PATH = "/" + WOOPRA_COOKIE_EXPIRE = "Fri Jan 01 2027 15:00:00 GMT+0000" + +See the `Woopra documentation`_ for more specific details. + + +Tracking control +---------------- + +Optional settings that influence the tracking as such:: + + WOOPRA_CLICK_TRACKING = False + WOOPRA_DOWNLOAD_TRACKING = False + WOOPRA_OUTGOING_TRACKING = False + WOOPRA_OUTGOING_IGNORE_SUBDOMAIN = True + WOOPRA_IGNORE_QUERY_URL = True + WOOPRA_HIDE_CAMPAIGN = False + +See the `Woopra documentation`_ for more specific details. + + +.. _`Woopra documentation`: + https://docs.woopra.com/reference/woopraconfig#configuring-your-tracker + + Internal IP addresses --------------------- diff --git a/tests/unit/test_tag_woopra.py b/tests/unit/test_tag_woopra.py index fa97367..8369323 100644 --- a/tests/unit/test_tag_woopra.py +++ b/tests/unit/test_tag_woopra.py @@ -2,6 +2,8 @@ Tests for the Woopra template tags and filters. """ +from datetime import datetime + import pytest from django.contrib.auth.models import AnonymousUser, User from django.http import HttpRequest @@ -41,8 +43,97 @@ class WoopraTagTestCase(TagTestCase): def test_idle_timeout(self): r = WoopraNode().render(Context({})) assert ( - 'var woo_settings = {"domain": "example.com", "idle_timeout": "1234"};' in r - ) + 'var woo_settings = {"domain": "example.com", "idle_timeout": 1234};' + ) in r + + @override_settings(WOOPRA_COOKIE_NAME='foo') + def test_cookie_name(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"cookie_name": "foo", "domain": "example.com"};' + ) in r + + @override_settings(WOOPRA_COOKIE_DOMAIN='.example.com') + def test_cookie_domain(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"cookie_domain": ".example.com",' + ' "domain": "example.com"};' + ) in r + + @override_settings(WOOPRA_COOKIE_PATH='/foo/cookie/path') + def test_cookie_path(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"cookie_path": "/foo/cookie/path",' + ' "domain": "example.com"};' + ) in r + + @override_settings(WOOPRA_COOKIE_EXPIRE='Fri Jan 01 2027 15:00:00 GMT+0000') + def test_cookie_expire(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"cookie_expire":' + ' "Fri Jan 01 2027 15:00:00 GMT+0000", "domain": "example.com"};' + ) in r + + @override_settings(WOOPRA_CLICK_TRACKING=True) + def test_click_tracking(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"click_tracking": true, "domain": "example.com"};' + ) in r + + @override_settings(WOOPRA_DOWNLOAD_TRACKING=True) + def test_download_tracking(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"domain": "example.com", "download_tracking": true};' + ) in r + + @override_settings(WOOPRA_OUTGOING_TRACKING=True) + def test_outgoing_tracking(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"domain": "example.com", "outgoing_tracking": true};' + ) in r + + @override_settings(WOOPRA_OUTGOING_IGNORE_SUBDOMAIN=False) + def test_outgoing_ignore_subdomain(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"domain": "example.com",' + ' "outgoing_ignore_subdomain": false};' + ) in r + + @override_settings(WOOPRA_IGNORE_QUERY_URL=False) + def test_ignore_query_url(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"domain": "example.com", "ignore_query_url": false};' + ) in r + + @override_settings(WOOPRA_HIDE_CAMPAIGN=True) + def test_hide_campaign(self): + r = WoopraNode().render(Context({})) + assert ( + 'var woo_settings = {"domain": "example.com", "hide_campaign": true};' + ) in r + + @override_settings(WOOPRA_IDLE_TIMEOUT='1234') + def test_invalid_int_setting(self): + with pytest.raises(AnalyticalException, match=r'must be an int'): + WoopraNode().render(Context({})) + + @override_settings(WOOPRA_HIDE_CAMPAIGN='tomorrow') + def test_invalid_bool_setting(self): + with pytest.raises(AnalyticalException, match=r'must be a boolean'): + WoopraNode().render(Context({})) + + @override_settings(WOOPRA_COOKIE_EXPIRE=datetime.now()) + def test_invalid_str_setting(self): + with pytest.raises(AnalyticalException, match=r'must be a string'): + WoopraNode().render(Context({})) def test_custom(self): r = WoopraNode().render(