From 637805e003f23aa98aa768f9bdd431c24467ae72 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Tue, 3 Mar 2020 18:40:04 +1100 Subject: [PATCH 001/103] docs: Fix simple typo, betweeen -> between There is a small typo in docs/services/matomo.rst, docs/services/piwik.rst. Should read `between` rather than `betweeen`. --- docs/services/matomo.rst | 2 +- docs/services/piwik.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/services/matomo.rst b/docs/services/matomo.rst index 0aa4731..d6a5b83 100644 --- a/docs/services/matomo.rst +++ b/docs/services/matomo.rst @@ -98,7 +98,7 @@ other parameters should be strings. :: return some_template.render(context) Matomo default settings allow up to 5 custom variables for both scope. Variable -mapping betweeen index and name must stay constant, or the latest name +mapping between index and name must stay constant, or the latest name override the previous one. If you use the same user variables in different views and its value can diff --git a/docs/services/piwik.rst b/docs/services/piwik.rst index 852c19f..30f6d9a 100644 --- a/docs/services/piwik.rst +++ b/docs/services/piwik.rst @@ -107,7 +107,7 @@ other parameters should be strings. :: return some_template.render(context) Piwik default settings allow up to 5 custom variables for both scope. Variable -mapping betweeen index and name must stay constant, or the latest name +mapping between index and name must stay constant, or the latest name override the previous one. If you use the same user variables in different views and its value can From 756ac787e822eee87f1753d7165be70663bc6924 Mon Sep 17 00:00:00 2001 From: Marc Bourqui Date: Wed, 24 Oct 2018 16:05:32 +0200 Subject: [PATCH 002/103] Add support for gtag.js --- .../templatetags/google_analytics_gtag.py | 65 +++++++++++++++ docs/services/google_analytics_gtag.rst | 80 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 analytical/templatetags/google_analytics_gtag.py create mode 100644 docs/services/google_analytics_gtag.rst diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py new file mode 100644 index 0000000..a2f47d7 --- /dev/null +++ b/analytical/templatetags/google_analytics_gtag.py @@ -0,0 +1,65 @@ +""" +Google Analytics template tags and filters, using the new analytics.js library. +""" + +from __future__ import absolute_import + +import re + +from django.template import Library, Node, TemplateSyntaxError + +from analytical.utils import ( + disable_html, + get_required_setting, + is_internal_ip, +) + +PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$') +SETUP_CODE = """ + + + +""" + +register = Library() + + +@register.tag +def google_analytics_gtag(parser, token): + """ + Google Analytics tracking template tag. + + Renders Javascript code to track page visits. You must supply + your website property ID (as a string) in the + ``GOOGLE_ANALYTICS_JS_PROPERTY_ID`` setting. + """ + bits = token.split_contents() + if len(bits) > 1: + raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) + return GoogleAnalyticsGTagNode() + + +class GoogleAnalyticsGTagNode(Node): + def __init__(self): + self.property_id = get_required_setting( + 'GOOGLE_ANALYTICS_GTAG_PROPERTY_ID', PROPERTY_ID_RE, + "must be a string looking like 'UA-XXXXXX-Y'") + + def render(self, context): + html = SETUP_CODE.format( + property_id=self.property_id, + ) + if is_internal_ip(context, 'GOOGLE_ANALYTICS'): + html = disable_html(html, 'Google Analytics') + return html + + +def contribute_to_analytical(add_node): + GoogleAnalyticsGTagNode() # ensure properly configured + add_node('head_top', GoogleAnalyticsGTagNode) diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst new file mode 100644 index 0000000..5a8358c --- /dev/null +++ b/docs/services/google_analytics_gtag.rst @@ -0,0 +1,80 @@ +====================================== + Google Analytics -- 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. + +.. _`Google Analytics`: http://www.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_analytics_gtag` template tag library and +insert the :ttag:`google_analytics_gtag` 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 bottom of the HTML head:: + + {% load google_analytics_gtag %} + + + {% google_analytics_gtag %} + ... + + ... + + +.. _google-analytics-configuration: + +Configuration +============= + +Before you can use the Google Analytics integration, you must first set +your website property ID. If you track multiple domains with the same +code, you also need to set-up the domain. Finally, you can add custom +segments for 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_analytics_gtag` 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_ANALYTICS_GTAG_PROPERTY_ID` in the +project :file:`settings.py` file:: + + GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = 'UA-XXXXXX-X' + +If you do not set a property ID, the tracking code will not be rendered. + + + +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. From 0ea5d391550df2bf11ce74a18cd5752b2e02b908 Mon Sep 17 00:00:00 2001 From: Marc Bourqui Date: Wed, 24 Oct 2018 16:17:58 +0200 Subject: [PATCH 003/103] Fix typos --- analytical/templatetags/google_analytics_gtag.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index a2f47d7..8b4a2d2 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -16,11 +16,10 @@ from analytical.utils import ( PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$') SETUP_CODE = """ - """ in r, r) + self.assertTrue("gtag('js', new Date());" in r, r) + self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) + + def test_node(self): + r = GoogleAnalyticsGTagNode().render(Context()) + self.assertTrue("""""" in r, r) + self.assertTrue("gtag('js', new Date());" in r, r) + self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) + + @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID=None) + def test_no_property_id(self): + self.assertRaises(AnalyticalException, GoogleAnalyticsGTagNode) + + @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='wrong') + def test_wrong_property_id(self): + self.assertRaises(AnalyticalException, GoogleAnalyticsGTagNode) + + @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 = GoogleAnalyticsGTagNode().render(context) + self.assertTrue(r.startswith( + ''), r) From 1ce265d2d97fdb065bdc1389964d65acc62cdc04 Mon Sep 17 00:00:00 2001 From: Marc Bourqui Date: Wed, 16 Jan 2019 23:30:24 +0100 Subject: [PATCH 006/103] Update test to comply with flake8 --- analytical/tests/test_tag_google_analytics_gtag.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/analytical/tests/test_tag_google_analytics_gtag.py b/analytical/tests/test_tag_google_analytics_gtag.py index 723bc34..f1eedd5 100644 --- a/analytical/tests/test_tag_google_analytics_gtag.py +++ b/analytical/tests/test_tag_google_analytics_gtag.py @@ -19,13 +19,17 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') - self.assertTrue("""""" in r, r) + self.assertTrue( + '' + in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) def test_node(self): r = GoogleAnalyticsGTagNode().render(Context()) - self.assertTrue("""""" in r, r) + self.assertTrue( + '' + in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) From 77fdb51432cce27c157994fc63c00b0df5997e43 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 11 Apr 2020 19:05:14 +0200 Subject: [PATCH 007/103] Explain the three Google Analytics options --- docs/services/google_analytics.rst | 4 ++-- docs/services/google_analytics_gtag.rst | 11 +++++++---- docs/services/google_analytics_js.rst | 11 +++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/services/google_analytics.rst b/docs/services/google_analytics.rst index 94587de..12d008c 100644 --- a/docs/services/google_analytics.rst +++ b/docs/services/google_analytics.rst @@ -1,6 +1,6 @@ -====================================== +============================================== Google Analytics (legacy) -- traffic analysis -====================================== +============================================== `Google Analytics`_ is the well-known web analytics service from Google. The product is aimed more at marketers than webmasters or diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 5a8358c..68fb306 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -1,13 +1,16 @@ -====================================== - Google Analytics -- traffic analysis -====================================== +=============================================== + 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. +features. The global site tag (`gtag.js`_) is a JavaScript tagging +framework and API that allows you to send event data to Google Analytics, +Google Ads, and Google Marketing Platform. .. _`Google Analytics`: http://www.google.com/analytics/ +.. _`gtag.js`: https://developers.google.com/analytics/devguides/collection/gtagjs/ .. google-analytics-installation: diff --git a/docs/services/google_analytics_js.rst b/docs/services/google_analytics_js.rst index 764e023..fa47f6d 100644 --- a/docs/services/google_analytics_js.rst +++ b/docs/services/google_analytics_js.rst @@ -1,13 +1,16 @@ -====================================== - Google Analytics -- traffic analysis -====================================== +==================================================== + Google Analytics (analytics.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. +features. The `analytics.js`_ library (also known as "the Google +Analytics tag") is a JavaScript library for measuring how users interact +with your website. .. _`Google Analytics`: http://www.google.com/analytics/ +.. _`analytics.js`: https://developers.google.com/analytics/devguides/collection/analyticsjs/ .. google-analytics-installation: From d9b21e96c64354abf37bbbc5dd8ac8f2e3ef4823 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Tue, 14 Apr 2020 22:21:20 +0200 Subject: [PATCH 008/103] Test against Django 3.0, Python 3.8 Modernize Travis CI setup --- .travis.yml | 44 +++++++++++++++++++++----------------------- setup.py | 3 ++- tox.ini | 29 +++++++++++++++++++---------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6731930..7cf9110 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,17 @@ +os: linux dist: xenial -sudo: true - language: python python: -- 2.7 - 3.4 - 3.5 - 3.6 - 3.7 +- 3.8 env: - DJANGO=1.11 -- DJANGO=2.1 - DJANGO=2.2 - -matrix: - allow_failures: - - env: TOXENV=bandit - exclude: - # Python/Django combinations that aren't officially supported - - { env: DJANGO=1.11, python: 3.7 } - - { env: DJANGO=2.1, python: 2.7 } - - { env: DJANGO=2.1, python: 3.4 } - - { env: DJANGO=2.2, python: 2.7 } - - { env: DJANGO=2.2, python: 3.4 } +- DJANGO=3.0 install: - pip install tox-travis @@ -36,22 +24,32 @@ stages: - deploy jobs: + allow_failures: + - env: TOXENV=bandit include: - - { stage: lint, env: TOXENV=flake8, python: 3.7 } - - { stage: lint, env: TOXENV=bandit, python: 3.7 } - - { stage: lint, env: TOXENV=readme, python: 3.7 } + - { stage: lint, python: 3.7, env: TOXENV=flake8 } + - { stage: lint, python: 3.7, env: TOXENV=bandit } + - { stage: lint, python: 3.7, env: TOXENV=readme } + - { stage: test, python: 2.7, env: DJANGO=1.11 } + exclude: + # Python/Django combinations that aren't officially supported + - { stage: test, python: 3.4, env: DJANGO=2.2 } + - { stage: test, python: 3.4, env: DJANGO=3.0 } + - { stage: test, python: 3.5, env: DJANGO=3.0 } + - { stage: test, python: 3.8, env: DJANGO=1.11 } - stage: deploy - env: - python: 3.7 - install: skip - script: skip + if: tag is present deploy: provider: pypi server: https://jazzband.co/projects/django-analytical/upload distributions: sdist bdist_wheel - user: jazzband + username: jazzband password: secure: JCr5hRjAeXuiISodCJf8HWd4BTJMpl2eiHI8NciPaSM9WwOOeUXxmlcP8+lWlXxgM4BYUC/O7Q90fkwj5x06n+z4oyJSEVerTvCDcpeZ68KMMG1tR1jTbHcxfEKoEvcs2J0fThJ9dIMtfbtUbIpzusJHkZPjsIy8HAJDw8knnJs= on: tags: true repo: jazzband/django-analytical + env: + install: skip + script: skip + python: 3.7 diff --git a/setup.py b/setup.py index 66b55b1..33554a7 100644 --- a/setup.py +++ b/setup.py @@ -76,8 +76,8 @@ setup( 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', @@ -91,6 +91,7 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', ], platforms=['any'], url='https://github.com/jazzband/django-analytical', diff --git a/tox.ini b/tox.ini index 2eebb33..d67f7c9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,9 @@ [tox] envlist = # Python/Django combinations that are officially supported - py{27,34,35,36}-django111 - py{35,36,37}-django{21,22} + py{27,34,35,36,37}-django111 + py{35,36,37,38}-django22 + py{36,37,38}-django30 flake8 bandit readme @@ -10,40 +11,48 @@ envlist = clean [testenv] -commands = - coverage run setup.py test - sh -c 'coveralls | true' +description = Unit tests deps = coverage coveralls django111: Django>=1.11,<2.0 - django21: Django>=2.1,<2.2 django22: Django>=2.2,<3.0 + django30: Django>=3.0,<3.1 +commands = + coverage run setup.py test + sh -c 'coveralls | true' passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH whitelist_externals = sh [testenv:bandit] -deps = bandit +description = PyCQA security linter +deps = bandit<1.6 commands = bandit -r --ini tox.ini [testenv:clean] -deps = pyclean +description = Clean up bytecode and build artifacts +deps = commands = - py3clean -v {toxinidir} rm -rf .tox/ django_analytical.egg-info/ build/ dist/ docs/_build/ + find {toxinidir} -type f -name '*.pyc' -delete + find {toxinidir} -type d -name '__pycache__' -delete whitelist_externals = + find rm [testenv:docs] +description = Build the HTML documentation deps = sphinx commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html whitelist_externals = make [testenv:flake8] +description = Static code analysis and code style deps = flake8 commands = flake8 [testenv:readme] +description = Ensure README renders on PyPI deps = twine commands = {envpython} setup.py -q sdist bdist_wheel @@ -52,8 +61,8 @@ commands = [travis:env] DJANGO = 1.11: django111 - 2.1: django21 2.2: django22 + 3.0: django30 [bandit] exclude = .cache,.git,.tox,build,dist,docs,tests From c10759d3787d170002887f9fd2b889db4b8101a0 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Tue, 14 Apr 2020 22:30:13 +0200 Subject: [PATCH 009/103] Bump version, release v2.6.0 --- CHANGELOG.rst | 7 +++++++ analytical/__init__.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6ddaa21..e1dc085 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,10 @@ +Version 2.6.0 +------------- +* Support Django 3.0 and Python 3.8, drop Django 2.1 +* Add support for Google Analytics Tag Manager (Marc Bourqui) +* Add Matomo, the renamed version of Piwik (Scott Karlin) +* Move Joost's project over to the Jazzband + Version 2.5.0 ------------- * Add support for Google analytics.js (Marc Bourqui) diff --git a/analytical/__init__.py b/analytical/__init__.py index 9a9d9b1..ee25a09 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -4,6 +4,6 @@ Analytics service integration for Django projects __author__ = "Joost Cassee" __email__ = "joost@cassee.net" -__version__ = "2.5.0" -__copyright__ = "Copyright (C) 2011-2019 Joost Cassee and contributors" +__version__ = "2.6.0" +__copyright__ = "Copyright (C) 2011-2020 Joost Cassee and contributors" __license__ = "MIT" From 962af837af654bc81b1c96fee9a977b8247d5b79 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Wed, 15 Apr 2020 15:04:31 +0200 Subject: [PATCH 010/103] Include deploy stage (excluded by error) --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7cf9110..b00cac1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,17 +26,17 @@ stages: jobs: allow_failures: - env: TOXENV=bandit - include: - - { stage: lint, python: 3.7, env: TOXENV=flake8 } - - { stage: lint, python: 3.7, env: TOXENV=bandit } - - { stage: lint, python: 3.7, env: TOXENV=readme } - - { stage: test, python: 2.7, env: DJANGO=1.11 } exclude: # Python/Django combinations that aren't officially supported - { stage: test, python: 3.4, env: DJANGO=2.2 } - { stage: test, python: 3.4, env: DJANGO=3.0 } - { stage: test, python: 3.5, env: DJANGO=3.0 } - { stage: test, python: 3.8, env: DJANGO=1.11 } + include: + - { stage: lint, python: 3.7, env: TOXENV=flake8 } + - { stage: lint, python: 3.7, env: TOXENV=bandit } + - { stage: lint, python: 3.7, env: TOXENV=readme } + - { stage: test, python: 2.7, env: DJANGO=1.11 } - stage: deploy if: tag is present deploy: From 438b1408fa3d5bec968167b8aceb64466ba10729 Mon Sep 17 00:00:00 2001 From: Sean Wallace Date: Sat, 27 Jun 2020 12:08:25 -0400 Subject: [PATCH 011/103] Add user_id to Google Analytics GTag --- analytical/templatetags/google_analytics_gtag.py | 14 ++++++++++++++ analytical/tests/test_tag_google_analytics_gtag.py | 6 ++++++ docs/services/google_analytics_gtag.rst | 8 ++++++++ 3 files changed, 28 insertions(+) diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index 8b4a2d2..ce9613a 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -10,6 +10,7 @@ from django.template import Library, Node, TemplateSyntaxError from analytical.utils import ( disable_html, + get_identity, get_required_setting, is_internal_ip, ) @@ -22,10 +23,13 @@ SETUP_CODE = """ function gtag(){{dataLayer.push(arguments);}} gtag('js', new Date()); + {extra} gtag('config', '{property_id}'); """ +GTAG_SET_CODE = """gtag('set', {{'{key}': '{value}'}});""" + register = Library() @@ -51,8 +55,18 @@ class GoogleAnalyticsGTagNode(Node): "must be a string looking like 'UA-XXXXXX-Y'") def render(self, context): + other_fields = {} + + identity = get_identity(context) + if identity is not None: + other_fields['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, ) if is_internal_ip(context, 'GOOGLE_ANALYTICS'): html = disable_html(html, 'Google Analytics') diff --git a/analytical/tests/test_tag_google_analytics_gtag.py b/analytical/tests/test_tag_google_analytics_gtag.py index f1eedd5..1eebe8b 100644 --- a/analytical/tests/test_tag_google_analytics_gtag.py +++ b/analytical/tests/test_tag_google_analytics_gtag.py @@ -2,6 +2,7 @@ Tests for the Google Analytics template tags and filters, using the new gtag.js library. """ +from django.contrib.auth.models import User from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings @@ -50,3 +51,8 @@ class GoogleAnalyticsTagTestCase(TagTestCase): self.assertTrue(r.startswith( ''), r) + + @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) + def test_identify(self): + r = GoogleAnalyticsGTagNode().render(Context({'user': User(username='test')})) + self.assertTrue("gtag('set', {'user_id': 'test'});" in r, r) diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 68fb306..5a96a8b 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -81,3 +81,11 @@ 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. + +.. _google-analytics-identify-user: + +Identifying authenticated users +------------------------------- + +The username of an authenticated user is passed to Google Analytics +automatically as the `user_id`. See :ref:`identifying-visitors`. From 88197ca17ea0adc7aaeeb179439696dbfdff4eaa Mon Sep 17 00:00:00 2001 From: Sean Wallace Date: Sat, 27 Jun 2020 12:08:48 -0400 Subject: [PATCH 012/103] Fix flake8. --- analytical/templatetags/analytical.py | 4 ++-- analytical/tests/templatetags/dummy.py | 2 +- analytical/tests/test_tag_analytical.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index 72eaeb6..b411cb9 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -72,8 +72,8 @@ class AnalyticalNode(Node): def _load_template_nodes(): - template_nodes = dict((l, dict((p, []) for p in TAG_POSITIONS)) - for l in TAG_LOCATIONS) + template_nodes = dict((loc, dict((pos, []) for pos in TAG_POSITIONS)) + for loc in TAG_LOCATIONS) def add_node_cls(location, node, position=None): template_nodes[location][position].append(node) diff --git a/analytical/tests/templatetags/dummy.py b/analytical/tests/templatetags/dummy.py index f92bc98..b7667de 100644 --- a/analytical/tests/templatetags/dummy.py +++ b/analytical/tests/templatetags/dummy.py @@ -19,7 +19,7 @@ def _location_node(location): return DummyNode -_location_nodes = dict((l, _location_node(l)) for l in TAG_LOCATIONS) +_location_nodes = dict((loc, _location_node(loc)) for loc in TAG_LOCATIONS) def _location_tag(location): diff --git a/analytical/tests/test_tag_analytical.py b/analytical/tests/test_tag_analytical.py index e4e47a8..c12ac48 100644 --- a/analytical/tests/test_tag_analytical.py +++ b/analytical/tests/test_tag_analytical.py @@ -31,6 +31,6 @@ class AnalyticsTagTestCase(TagTestCase): return t.render(Context(vars)) def test_location_tags(self): - for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']: - r = self.render_location_tag(l) - self.assertTrue('dummy_%s' % l in r, r) + for loc in ['head_top', 'head_bottom', 'body_top', 'body_bottom']: + r = self.render_location_tag(loc) + self.assertTrue('dummy_%s' % loc in r, r) From 788cab447e90506c13988a110301d3c3c209c3c2 Mon Sep 17 00:00:00 2001 From: Hugo Osvaldo Barrera Date: Thu, 2 Jul 2020 17:34:23 +0200 Subject: [PATCH 013/103] Only show piwik warning if using piwik Code so far emits a warning to all users about about Piwik being deprecated, even if Piwik is not being used. Scope the warning so it only triggers for people to whom it's relevant. --- analytical/templatetags/piwik.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytical/templatetags/piwik.py b/analytical/templatetags/piwik.py index 37d9777..97a7250 100644 --- a/analytical/templatetags/piwik.py +++ b/analytical/templatetags/piwik.py @@ -15,7 +15,6 @@ from analytical.utils import (is_internal_ip, disable_html, get_required_setting, get_identity) import warnings -warnings.warn('The Piwik module is deprecated; use the Matomo module.', DeprecationWarning) # domain name (characters separated by a dot), optional port, optional URI path, no slash DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$') @@ -68,6 +67,7 @@ def piwik(parser, token): (default) or ``'visit'``. Index should be an integer and the other parameters should be strings. """ + warnings.warn('The Piwik module is deprecated; use the Matomo module.', DeprecationWarning) bits = token.split_contents() if len(bits) > 1: raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) From ac2ebf375c19afa32d2780ba0f68566e62526e84 Mon Sep 17 00:00:00 2001 From: Taha Rushain Date: Mon, 6 Jul 2020 01:38:40 +0500 Subject: [PATCH 014/103] Updated regex for gtag to accept measurement ID --- analytical/templatetags/google_analytics_gtag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index ce9613a..38bfa49 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -15,7 +15,7 @@ from analytical.utils import ( is_internal_ip, ) -PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$') +PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$|^G-[a-zA-Z0-9]+$') SETUP_CODE = """ ' + in r, r) + self.assertTrue("gtag('js', new Date());" in r, r) + self.assertTrue("gtag('config', 'G-12345678');" in r, r) + + @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='AW-1234567890') + def test_tag_with_conversion_id(self): + r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') + self.assertTrue( + '' + in r, r) + self.assertTrue("gtag('js', new Date());" in r, r) + self.assertTrue("gtag('config', 'AW-1234567890');" in r, r) + + @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='DC-12345678') + def test_tag_with_advertiser_id(self): + r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') + self.assertTrue( + '' + in r, r) + self.assertTrue("gtag('js', new Date());" in r, r) + self.assertTrue("gtag('config', 'DC-12345678');" in r, r) \ No newline at end of file diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 5a96a8b..29b4941 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -69,6 +69,12 @@ project :file:`settings.py` file:: If you do not set a property ID, the tracking code will not be rendered. +Please node that the accepted Property IDs should be one of the following formats: + +- 'UA-XXXXXX-Y' +- 'AW-XXXXXXXXXX' +- 'G-XXXXXXXX' +- 'DC-XXXXXXXX' Internal IP addresses From 1cdfb0ed0dcfcafefe4208283fad70058b3654ab Mon Sep 17 00:00:00 2001 From: Taha Rushain Date: Sun, 19 Jul 2020 01:49:38 +0500 Subject: [PATCH 016/103] Breaking long strings into multiple lines --- analytical/templatetags/google_analytics_gtag.py | 4 +++- analytical/tests/test_tag_google_analytics_gtag.py | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index 57eafe7..02ab34e 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -52,7 +52,9 @@ class GoogleAnalyticsGTagNode(Node): def __init__(self): self.property_id = get_required_setting( 'GOOGLE_ANALYTICS_GTAG_PROPERTY_ID', PROPERTY_ID_RE, - "must be a string looking like one of these patterns ('UA-XXXXXX-Y' , 'AW-XXXXXXXXXX', 'G-XXXXXXXX', 'DC-XXXXXXXX')") + '''must be a string looking like one of these patterns + ('UA-XXXXXX-Y' , 'AW-XXXXXXXXXX', + 'G-XXXXXXXX', 'DC-XXXXXXXX')''') def render(self, context): other_fields = {} diff --git a/analytical/tests/test_tag_google_analytics_gtag.py b/analytical/tests/test_tag_google_analytics_gtag.py index d95f8c0..97cf0ad 100644 --- a/analytical/tests/test_tag_google_analytics_gtag.py +++ b/analytical/tests/test_tag_google_analytics_gtag.py @@ -61,7 +61,8 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag_with_measurement_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( - '' + ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'G-12345678');" in r, r) @@ -70,7 +71,8 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag_with_conversion_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( - '' + ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'AW-1234567890');" in r, r) @@ -79,7 +81,8 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag_with_advertiser_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( - '' + ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'DC-12345678');" in r, r) \ No newline at end of file From d98e815493545431b9f0d6afc1b970a2b84abc9d Mon Sep 17 00:00:00 2001 From: Taha Rushain Date: Sun, 19 Jul 2020 01:57:56 +0500 Subject: [PATCH 017/103] Breaking long strings into multiple lines --- analytical/templatetags/google_analytics_gtag.py | 4 ++-- .../tests/test_tag_google_analytics_gtag.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index 02ab34e..93154b9 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -52,8 +52,8 @@ class GoogleAnalyticsGTagNode(Node): def __init__(self): self.property_id = get_required_setting( 'GOOGLE_ANALYTICS_GTAG_PROPERTY_ID', PROPERTY_ID_RE, - '''must be a string looking like one of these patterns - ('UA-XXXXXX-Y' , 'AW-XXXXXXXXXX', + '''must be a string looking like one of these patterns + ('UA-XXXXXX-Y' , 'AW-XXXXXXXXXX', 'G-XXXXXXXX', 'DC-XXXXXXXX')''') def render(self, context): diff --git a/analytical/tests/test_tag_google_analytics_gtag.py b/analytical/tests/test_tag_google_analytics_gtag.py index 97cf0ad..514efca 100644 --- a/analytical/tests/test_tag_google_analytics_gtag.py +++ b/analytical/tests/test_tag_google_analytics_gtag.py @@ -61,8 +61,8 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag_with_measurement_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( - ('') + ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'G-12345678');" in r, r) @@ -71,8 +71,8 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag_with_conversion_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( - ('') + ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'AW-1234567890');" in r, r) @@ -81,8 +81,9 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag_with_advertiser_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( - ('') + ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) - self.assertTrue("gtag('config', 'DC-12345678');" in r, r) \ No newline at end of file + self.assertTrue("gtag('config', 'DC-12345678');" in r, r) + From ee956230ead9595524d341430998534296e2a814 Mon Sep 17 00:00:00 2001 From: Taha Rushain Date: Sun, 19 Jul 2020 02:05:12 +0500 Subject: [PATCH 018/103] Breaking long strings into multiple lines --- analytical/tests/test_tag_google_analytics_gtag.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/analytical/tests/test_tag_google_analytics_gtag.py b/analytical/tests/test_tag_google_analytics_gtag.py index 514efca..1e5430c 100644 --- a/analytical/tests/test_tag_google_analytics_gtag.py +++ b/analytical/tests/test_tag_google_analytics_gtag.py @@ -62,7 +62,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( ('') + 'js?id=G-12345678">') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'G-12345678');" in r, r) @@ -72,7 +72,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( ('') + 'js?id=AW-1234567890">') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'AW-1234567890');" in r, r) @@ -82,8 +82,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( ('') + 'js?id=DC-12345678">') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'DC-12345678');" in r, r) - From e517bce3a6759a7a4b90cb28565ef49f7d4b21dd Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 13 Nov 2020 18:03:02 +0100 Subject: [PATCH 019/103] Add SnapEngage details to Installation section Fixes #166 --- docs/install.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/install.rst b/docs/install.rst index 1f97f07..d8af333 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -185,6 +185,10 @@ settings required to enable each service are listed here: RATING_MAILRU_COUNTER_ID = '1234567' +* :doc:`SnapEngage `:: + + SNAPENGAGE_WIDGET_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' + * :doc:`Woopra `:: WOOPRA_DOMAIN = 'abcde.com' From b9320b41d96b869adc6e71ed2b906794f7db8fbd Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 25 Nov 2020 20:56:03 +0100 Subject: [PATCH 020/103] Add initial GitHub Actions workflow. --- .github/workflows/test.yml | 47 ++++++++++++++++++++++++++++++++++++++ tox.ini | 30 +++--------------------- 2 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f8f8a5a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Test + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + matrix: + python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + -${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} + restore-keys: | + -${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox tox-gh-actions + + - name: Tox tests + run: | + tox -v + + - name: Upload coverage + uses: codecov/codecov-action@v1 + with: + name: Python ${{ matrix.python-version }} diff --git a/tox.ini b/tox.ini index d67f7c9..0dd5e0c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,45 +1,27 @@ [tox] envlist = # Python/Django combinations that are officially supported - py{27,34,35,36,37}-django111 + py{27,35,36,37}-django111 py{35,36,37,38}-django22 py{36,37,38}-django30 - flake8 - bandit - readme - docs - clean + py37-{flake8,bandit,readme,docs} [testenv] description = Unit tests deps = coverage - coveralls django111: Django>=1.11,<2.0 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 commands = coverage run setup.py test - sh -c 'coveralls | true' -passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH -whitelist_externals = sh + coverage xml [testenv:bandit] description = PyCQA security linter deps = bandit<1.6 commands = bandit -r --ini tox.ini -[testenv:clean] -description = Clean up bytecode and build artifacts -deps = -commands = - rm -rf .tox/ django_analytical.egg-info/ build/ dist/ docs/_build/ - find {toxinidir} -type f -name '*.pyc' -delete - find {toxinidir} -type d -name '__pycache__' -delete -whitelist_externals = - find - rm - [testenv:docs] description = Build the HTML documentation deps = sphinx @@ -58,12 +40,6 @@ commands = {envpython} setup.py -q sdist bdist_wheel twine check dist/* -[travis:env] -DJANGO = - 1.11: django111 - 2.2: django22 - 3.0: django30 - [bandit] exclude = .cache,.git,.tox,build,dist,docs,tests targets = . From 1ebc2a23ea27acafe30452b0532c07167909764d Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 25 Nov 2020 21:01:53 +0100 Subject: [PATCH 021/103] Add gh-action mapping. --- tox.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tox.ini b/tox.ini index 0dd5e0c..95040ac 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,14 @@ commands = coverage run setup.py test coverage xml +[gh-actions] +python = + 2.7: py27 + 3.5: py35 + 3.6: py36 + 3.7: py37 + 3.8: py38 + [testenv:bandit] description = PyCQA security linter deps = bandit<1.6 From 2322f7a0bf71ef2a723fc6782c7fc29bfe666a51 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 25 Nov 2020 21:04:49 +0100 Subject: [PATCH 022/103] Fix config section names. --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 95040ac..cdc5b18 100644 --- a/tox.ini +++ b/tox.ini @@ -25,23 +25,23 @@ python = 3.7: py37 3.8: py38 -[testenv:bandit] +[testenv:py37-bandit] description = PyCQA security linter deps = bandit<1.6 commands = bandit -r --ini tox.ini -[testenv:docs] +[testenv:py37-docs] description = Build the HTML documentation deps = sphinx commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html whitelist_externals = make -[testenv:flake8] +[testenv:py37-flake8] description = Static code analysis and code style deps = flake8 commands = flake8 -[testenv:readme] +[testenv:py37-readme] description = Ensure README renders on PyPI deps = twine commands = From de2ed4bafc2865cd77caa9876fe4f286867a4815 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 25 Nov 2020 21:11:14 +0100 Subject: [PATCH 023/103] Ignore bandit errors. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index cdc5b18..d5b4725 100644 --- a/tox.ini +++ b/tox.ini @@ -29,6 +29,7 @@ python = description = PyCQA security linter deps = bandit<1.6 commands = bandit -r --ini tox.ini +ignore_errors = true [testenv:py37-docs] description = Build the HTML documentation From 7580bc1619685b42f8ece2ba3d1c8010032e1888 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 25 Nov 2020 21:15:45 +0100 Subject: [PATCH 024/103] Ignore it for real? --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d5b4725..831fc68 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ python = [testenv:py37-bandit] description = PyCQA security linter deps = bandit<1.6 -commands = bandit -r --ini tox.ini +commands = - bandit -r --ini tox.ini ignore_errors = true [testenv:py37-docs] From 197cdbcfadc3b3059cde012a6548d4df8be20df1 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 25 Nov 2020 21:18:45 +0100 Subject: [PATCH 025/103] Add Jazzband release workflow. --- .github/workflows/release.yml | 53 +++++++++++++++++++++++++++++++++ .travis.yml | 55 ----------------------------------- README.rst | 10 +++---- 3 files changed, 58 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..59142fd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: Release + +on: + push: + tags: + - '*' + +jobs: + build: + if: github.repository == 'jazzband/django-analytical' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: release-${{ hashFiles('**/setup.py') }} + restore-keys: | + release- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U setuptools twine wheel + + - name: Build package + run: | + python setup.py --version + python setup.py sdist --format=gztar bdist_wheel + twine check dist/* + + - name: Upload packages to Jazzband + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + user: jazzband + password: ${{ secrets.JAZZBAND_RELEASE_KEY }} + repository_url: https://jazzband.co/projects/django-analytical/upload diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b00cac1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -os: linux -dist: xenial -language: python -python: -- 3.4 -- 3.5 -- 3.6 -- 3.7 -- 3.8 - -env: -- DJANGO=1.11 -- DJANGO=2.2 -- DJANGO=3.0 - -install: -- pip install tox-travis -script: -- tox - -stages: -- lint -- test -- deploy - -jobs: - allow_failures: - - env: TOXENV=bandit - exclude: - # Python/Django combinations that aren't officially supported - - { stage: test, python: 3.4, env: DJANGO=2.2 } - - { stage: test, python: 3.4, env: DJANGO=3.0 } - - { stage: test, python: 3.5, env: DJANGO=3.0 } - - { stage: test, python: 3.8, env: DJANGO=1.11 } - include: - - { stage: lint, python: 3.7, env: TOXENV=flake8 } - - { stage: lint, python: 3.7, env: TOXENV=bandit } - - { stage: lint, python: 3.7, env: TOXENV=readme } - - { stage: test, python: 2.7, env: DJANGO=1.11 } - - stage: deploy - if: tag is present - deploy: - provider: pypi - server: https://jazzband.co/projects/django-analytical/upload - distributions: sdist bdist_wheel - username: jazzband - password: - secure: JCr5hRjAeXuiISodCJf8HWd4BTJMpl2eiHI8NciPaSM9WwOOeUXxmlcP8+lWlXxgM4BYUC/O7Q90fkwj5x06n+z4oyJSEVerTvCDcpeZ68KMMG1tR1jTbHcxfEKoEvcs2J0fThJ9dIMtfbtUbIpzusJHkZPjsIy8HAJDw8knnJs= - on: - tags: true - repo: jazzband/django-analytical - env: - install: skip - script: skip - python: 3.7 diff --git a/README.rst b/README.rst index 0fa7527..2c5dc22 100644 --- a/README.rst +++ b/README.rst @@ -26,12 +26,12 @@ an asynchronous version of the Javascript code if possible. .. |latest-version| image:: https://img.shields.io/pypi/v/django-analytical.svg :alt: Latest version on PyPI :target: https://pypi.org/project/django-analytical/ -.. |build-status| image:: https://img.shields.io/travis/jazzband/django-analytical/master.svg - :alt: Build status - :target: https://travis-ci.org/jazzband/django-analytical -.. |coverage| image:: https://img.shields.io/coveralls/github/jazzband/django-analytical/master.svg +.. |build-status| image:: https://github.com/jazzband/django-analytical/workflows/Test/badge.svg + :target: https://github.com/jazzband/django-analytical/actions + :alt: GitHub Actions +.. |coverage| image:: https://codecov.io/gh/jazzband/django-analytical/branch/master/graph/badge.svg :alt: Test coverage - :target: https://coveralls.io/r/jazzband/django-analytical + :target: https://codecov.io/gh/jazzband/django-analytical .. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg :target: https://pypi.org/project/django-analytical/ :alt: Python versions From 146a96fca0d281170f92ea755fb5367a2557fa3b Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 26 Nov 2020 09:41:44 +0100 Subject: [PATCH 026/103] Quote Python versions. --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8f8a5a..b217532 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + python-version: ['2.7', '3.5', '3.6', '3.7', '3.8'] steps: - uses: actions/checkout@v2 @@ -28,9 +28,9 @@ jobs: with: path: ${{ steps.pip-cache.outputs.dir }} key: - -${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} + ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} restore-keys: | - -${{ matrix.python-version }}-v1- + ${{ matrix.python-version }}-v1- - name: Install dependencies run: | From 10d109eb6c210b4e0b94991ddbb4b279e0583eab Mon Sep 17 00:00:00 2001 From: David Smith Date: Sun, 29 Nov 2020 21:27:16 +0000 Subject: [PATCH 027/103] Use pytest as test runner * Used pytest to run tests within tox environments * Removed test runner within setup.py --- setup.cfg | 3 +++ setup.py | 33 --------------------------------- tox.ini | 4 +++- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/setup.cfg b/setup.cfg index e9db078..d3d35ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,3 +5,6 @@ all_files = 1 [upload_sphinx] upload-dir = build/docs/html + +[tool:pytest] +DJANGO_SETTINGS_MODULE = analytical.tests.settings \ No newline at end of file diff --git a/setup.py b/setup.py index 33554a7..f03ae69 100644 --- a/setup.py +++ b/setup.py @@ -17,42 +17,9 @@ except ImportError: pass -class TestCommand(Command): - description = "run package tests" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - from analytical.tests.utils import run_tests - - run_tests() - - -cmdclass['test'] = TestCommand - - def read_file(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() - -try: - import django - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", - "analytical.tests.settings" - ) - django.setup() -except ImportError: - print( - "Could not import django. " - "This is fine, unless you intend to run unit tests." - ) - import analytical as package # noqa setup( diff --git a/tox.ini b/tox.ini index 831fc68..b60187e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,11 +10,13 @@ envlist = description = Unit tests deps = coverage + pytest + pytest-django django111: Django>=1.11,<2.0 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 commands = - coverage run setup.py test + coverage run -m pytest coverage xml [gh-actions] From df7ed2d132d4dda645ca2c32c6ef02d1d9c6492d Mon Sep 17 00:00:00 2001 From: David Smith Date: Sun, 29 Nov 2020 21:49:25 +0000 Subject: [PATCH 028/103] Fix flake8 errors --- setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index f03ae69..0607099 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,9 @@ import os try: - from setuptools import setup, Command + from setuptools import setup except ImportError: - from distutils.core import setup, Command - -os.environ['DJANGO_SETTINGS_MODULE'] = 'analytical.tests.settings' + from distutils.core import setup cmdclass = {} From 3eb17007ad17fbc8de49dd2d858d3f5925231fe2 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 30 Nov 2020 21:33:38 +0000 Subject: [PATCH 029/103] Refactored setup.py to remove cmdclass and test packages --- analytical/tests/utils.py | 20 -------------------- setup.cfg | 2 +- setup.py | 18 ++---------------- tox.ini | 1 - 4 files changed, 3 insertions(+), 38 deletions(-) diff --git a/analytical/tests/utils.py b/analytical/tests/utils.py index 11d106d..b7fc835 100644 --- a/analytical/tests/utils.py +++ b/analytical/tests/utils.py @@ -8,26 +8,6 @@ from django.template import Template, Context, RequestContext from django.test.testcases import TestCase -def run_tests(): - """ - Use the Django test runner to run the tests. - - Sets the return code to the number of failed tests. - """ - import sys - import django - try: - django.setup() - except AttributeError: - pass - try: - from django.test.runner import DiscoverRunner as TestRunner - except ImportError: - from django.test.simple import DjangoTestSuiteRunner as TestRunner - runner = TestRunner() - sys.exit(runner.run_tests(["analytical"])) - - class TagTestCase(TestCase): """ Tests for a template tag. diff --git a/setup.cfg b/setup.cfg index d3d35ec..f3c3b37 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,4 +7,4 @@ all_files = 1 upload-dir = build/docs/html [tool:pytest] -DJANGO_SETTINGS_MODULE = analytical.tests.settings \ No newline at end of file +DJANGO_SETTINGS_MODULE = analytical.tests.settings diff --git a/setup.py b/setup.py index 0607099..9d87937 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,13 @@ import os -try: - from setuptools import setup -except ImportError: - from distutils.core import setup +from setuptools import setup -cmdclass = {} - -try: - from sphinx.setup_command import BuildDoc - - cmdclass['build_sphinx'] = BuildDoc -except ImportError: - pass +import analytical as package def read_file(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -import analytical as package # noqa setup( name='django-analytical', @@ -32,8 +21,6 @@ setup( packages=[ 'analytical', 'analytical.templatetags', - 'analytical.tests', - 'analytical.tests.templatetags', ], keywords=['django', 'analytics'], classifiers=[ @@ -61,5 +48,4 @@ setup( platforms=['any'], url='https://github.com/jazzband/django-analytical', download_url='https://github.com/jazzband/django-analytical/archive/master.zip', - cmdclass=cmdclass, ) diff --git a/tox.ini b/tox.ini index b60187e..d0a9f0f 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ envlist = description = Unit tests deps = coverage - pytest pytest-django django111: Django>=1.11,<2.0 django22: Django>=2.2,<3.0 From 9163294603f1d6f7a51024de94021a7817d1de84 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 25 Nov 2020 22:07:00 +0000 Subject: [PATCH 030/103] Updated supported versions of Python and Django Dropped Python2.7/3.5 and Django 1.11 from test matrix Removed Py2 code and updated for Py3 features Replaced assertRaisesRegexp with AsserRaisesRegex Updated setup.py for currently supported versions Removed _timestamp --- .github/workflows/test.yml | 2 +- analytical/templatetags/analytical.py | 5 +---- analytical/templatetags/chartbeat.py | 2 -- analytical/templatetags/clickmap.py | 2 -- analytical/templatetags/clicky.py | 2 -- analytical/templatetags/crazy_egg.py | 2 -- analytical/templatetags/facebook_pixel.py | 1 - analytical/templatetags/gauges.py | 2 -- analytical/templatetags/google_analytics.py | 2 -- analytical/templatetags/google_analytics_gtag.py | 2 -- analytical/templatetags/google_analytics_js.py | 4 +--- analytical/templatetags/gosquared.py | 2 -- analytical/templatetags/hotjar.py | 1 - analytical/templatetags/hubspot.py | 2 -- analytical/templatetags/intercom.py | 14 +------------- analytical/templatetags/kiss_insights.py | 2 -- analytical/templatetags/kiss_metrics.py | 2 -- analytical/templatetags/matomo.py | 2 -- analytical/templatetags/mixpanel.py | 2 -- analytical/templatetags/olark.py | 2 -- analytical/templatetags/optimizely.py | 2 -- analytical/templatetags/performable.py | 2 -- analytical/templatetags/piwik.py | 2 -- analytical/templatetags/rating_mailru.py | 2 -- analytical/templatetags/snapengage.py | 2 -- analytical/templatetags/spring_metrics.py | 2 -- analytical/templatetags/uservoice.py | 2 -- analytical/templatetags/woopra.py | 2 -- analytical/templatetags/yandex_metrica.py | 2 -- analytical/tests/templatetags/dummy.py | 4 +--- analytical/tests/test_tag_analytical.py | 4 ++-- analytical/tests/test_tag_facebook_pixel.py | 12 ++++++------ analytical/tests/test_tag_hotjar.py | 6 +++--- analytical/tests/test_tag_intercom.py | 4 ++-- analytical/tests/test_tag_olark.py | 2 +- analytical/tests/test_utils.py | 13 ++----------- analytical/tests/utils.py | 2 -- docs/conf.py | 9 ++++----- requirements.txt | 2 +- setup.py | 7 ++----- tox.ini | 10 ++++------ 41 files changed, 32 insertions(+), 116 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b217532..82e923f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ['2.7', '3.5', '3.6', '3.7', '3.8'] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index b411cb9..f421cf6 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -2,8 +2,6 @@ Analytical template tags and filters. """ -from __future__ import absolute_import - import logging from django import template @@ -72,8 +70,7 @@ class AnalyticalNode(Node): def _load_template_nodes(): - template_nodes = dict((loc, dict((pos, []) for pos in TAG_POSITIONS)) - for loc in TAG_LOCATIONS) + template_nodes = {loc: {pos: [] for pos in TAG_POSITIONS} for loc in TAG_LOCATIONS} def add_node_cls(location, node, position=None): template_nodes[location][position].append(node) diff --git a/analytical/templatetags/chartbeat.py b/analytical/templatetags/chartbeat.py index 6168c13..1bdb9ab 100644 --- a/analytical/templatetags/chartbeat.py +++ b/analytical/templatetags/chartbeat.py @@ -2,8 +2,6 @@ Chartbeat template tags and filters. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/clickmap.py b/analytical/templatetags/clickmap.py index 8d7f00f..2cdaf80 100644 --- a/analytical/templatetags/clickmap.py +++ b/analytical/templatetags/clickmap.py @@ -2,8 +2,6 @@ Clickmap template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/clicky.py b/analytical/templatetags/clicky.py index f493684..71d5fba 100644 --- a/analytical/templatetags/clicky.py +++ b/analytical/templatetags/clicky.py @@ -2,8 +2,6 @@ Clicky template tags and filters. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/crazy_egg.py b/analytical/templatetags/crazy_egg.py index 99a58c5..5ad8a5a 100644 --- a/analytical/templatetags/crazy_egg.py +++ b/analytical/templatetags/crazy_egg.py @@ -2,8 +2,6 @@ Crazy Egg template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/facebook_pixel.py b/analytical/templatetags/facebook_pixel.py index 33dac0a..9ccc9ad 100644 --- a/analytical/templatetags/facebook_pixel.py +++ b/analytical/templatetags/facebook_pixel.py @@ -1,7 +1,6 @@ """ Facebook Pixel template tags and filters. """ -from __future__ import absolute_import import re diff --git a/analytical/templatetags/gauges.py b/analytical/templatetags/gauges.py index 60b6586..648ba23 100644 --- a/analytical/templatetags/gauges.py +++ b/analytical/templatetags/gauges.py @@ -2,8 +2,6 @@ Gaug.es template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/google_analytics.py b/analytical/templatetags/google_analytics.py index 3ac1deb..96f8808 100644 --- a/analytical/templatetags/google_analytics.py +++ b/analytical/templatetags/google_analytics.py @@ -4,8 +4,6 @@ Google Analytics template tags and filters. DEPRECATED """ -from __future__ import absolute_import - import decimal import re diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index 93154b9..ecb5787 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -2,8 +2,6 @@ Google Analytics template tags and filters, using the new analytics.js library. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/google_analytics_js.py b/analytical/templatetags/google_analytics_js.py index d23467e..ab92b11 100644 --- a/analytical/templatetags/google_analytics_js.py +++ b/analytical/templatetags/google_analytics_js.py @@ -2,8 +2,6 @@ Google Analytics template tags and filters, using the new analytics.js library. """ -from __future__ import absolute_import - import decimal import re from django.conf import settings @@ -134,7 +132,7 @@ class GoogleAnalyticsJsNode(Node): try: float(value) except ValueError: - value = "'{}'".format(value) + value = f"'{value}'" commands.append(CUSTOM_VAR_CODE.format( name=name, value=value, diff --git a/analytical/templatetags/gosquared.py b/analytical/templatetags/gosquared.py index a42267e..5b4458a 100644 --- a/analytical/templatetags/gosquared.py +++ b/analytical/templatetags/gosquared.py @@ -2,8 +2,6 @@ GoSquared template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/hotjar.py b/analytical/templatetags/hotjar.py index b85a126..300518c 100644 --- a/analytical/templatetags/hotjar.py +++ b/analytical/templatetags/hotjar.py @@ -1,7 +1,6 @@ """ Hotjar template tags and filters. """ -from __future__ import absolute_import import re diff --git a/analytical/templatetags/hubspot.py b/analytical/templatetags/hubspot.py index fc011e3..21835ec 100644 --- a/analytical/templatetags/hubspot.py +++ b/analytical/templatetags/hubspot.py @@ -2,8 +2,6 @@ HubSpot template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/intercom.py b/analytical/templatetags/intercom.py index a2d34a3..fa47231 100644 --- a/analytical/templatetags/intercom.py +++ b/analytical/templatetags/intercom.py @@ -2,14 +2,10 @@ intercom.io template tags and filters. """ -from __future__ import absolute_import - import hashlib import hmac import json -import sys import re -import time from django.conf import settings from django.template import Library, Node, TemplateSyntaxError @@ -29,14 +25,6 @@ TRACKING_CODE = """ register = Library() -def _timestamp(when): - """ - Python 2 compatibility for `datetime.timestamp()`. - """ - return (time.mktime(when.timetuple()) if sys.version_info < (3,) else - when.timestamp()) - - def _hashable_bytes(data): """ Coerce strings to hashable bytes. @@ -109,7 +97,7 @@ class IntercomNode(Node): params.setdefault('user_id', user.pk) - params['created_at'] = int(_timestamp(user.date_joined)) + params['created_at'] = int(user.date_joined.timestamp()) else: params['created_at'] = None diff --git a/analytical/templatetags/kiss_insights.py b/analytical/templatetags/kiss_insights.py index 8381eb3..5ec20ce 100644 --- a/analytical/templatetags/kiss_insights.py +++ b/analytical/templatetags/kiss_insights.py @@ -2,8 +2,6 @@ KISSinsights template tags. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/kiss_metrics.py b/analytical/templatetags/kiss_metrics.py index 4706dbd..bf2b4f8 100644 --- a/analytical/templatetags/kiss_metrics.py +++ b/analytical/templatetags/kiss_metrics.py @@ -2,8 +2,6 @@ KISSmetrics template tags. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/matomo.py b/analytical/templatetags/matomo.py index 98bdf8a..c00c5b1 100644 --- a/analytical/templatetags/matomo.py +++ b/analytical/templatetags/matomo.py @@ -2,8 +2,6 @@ Matomo template tags and filters. """ -from __future__ import absolute_import - from collections import namedtuple from itertools import chain import re diff --git a/analytical/templatetags/mixpanel.py b/analytical/templatetags/mixpanel.py index caab030..36af438 100644 --- a/analytical/templatetags/mixpanel.py +++ b/analytical/templatetags/mixpanel.py @@ -2,8 +2,6 @@ Mixpanel template tags and filters. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/olark.py b/analytical/templatetags/olark.py index 2b85648..f3c0364 100644 --- a/analytical/templatetags/olark.py +++ b/analytical/templatetags/olark.py @@ -2,8 +2,6 @@ Olark template tags. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/optimizely.py b/analytical/templatetags/optimizely.py index e5cd185..d99ca07 100644 --- a/analytical/templatetags/optimizely.py +++ b/analytical/templatetags/optimizely.py @@ -2,8 +2,6 @@ Optimizely template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/performable.py b/analytical/templatetags/performable.py index 3364e18..39e6c16 100644 --- a/analytical/templatetags/performable.py +++ b/analytical/templatetags/performable.py @@ -2,8 +2,6 @@ Performable template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/piwik.py b/analytical/templatetags/piwik.py index 97a7250..8855a61 100644 --- a/analytical/templatetags/piwik.py +++ b/analytical/templatetags/piwik.py @@ -2,8 +2,6 @@ Piwik template tags and filters. """ -from __future__ import absolute_import - from collections import namedtuple from itertools import chain import re diff --git a/analytical/templatetags/rating_mailru.py b/analytical/templatetags/rating_mailru.py index ba1da7e..c7b050a 100644 --- a/analytical/templatetags/rating_mailru.py +++ b/analytical/templatetags/rating_mailru.py @@ -2,8 +2,6 @@ Rating@Mail.ru template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/snapengage.py b/analytical/templatetags/snapengage.py index 5881a50..ce54c79 100644 --- a/analytical/templatetags/snapengage.py +++ b/analytical/templatetags/snapengage.py @@ -2,8 +2,6 @@ SnapEngage template tags. """ -from __future__ import absolute_import - import re from django.conf import settings diff --git a/analytical/templatetags/spring_metrics.py b/analytical/templatetags/spring_metrics.py index a3093ea..d402846 100644 --- a/analytical/templatetags/spring_metrics.py +++ b/analytical/templatetags/spring_metrics.py @@ -2,8 +2,6 @@ Spring Metrics template tags and filters. """ -from __future__ import absolute_import - import re from django.template import Library, Node, TemplateSyntaxError diff --git a/analytical/templatetags/uservoice.py b/analytical/templatetags/uservoice.py index 68f59db..1e64e38 100644 --- a/analytical/templatetags/uservoice.py +++ b/analytical/templatetags/uservoice.py @@ -2,8 +2,6 @@ UserVoice template tags. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/woopra.py b/analytical/templatetags/woopra.py index e6c02cc..f5716b3 100644 --- a/analytical/templatetags/woopra.py +++ b/analytical/templatetags/woopra.py @@ -2,8 +2,6 @@ Woopra template tags and filters. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/templatetags/yandex_metrica.py b/analytical/templatetags/yandex_metrica.py index 4e3270f..b269c89 100644 --- a/analytical/templatetags/yandex_metrica.py +++ b/analytical/templatetags/yandex_metrica.py @@ -2,8 +2,6 @@ Yandex.Metrica template tags and filters. """ -from __future__ import absolute_import - import json import re diff --git a/analytical/tests/templatetags/dummy.py b/analytical/tests/templatetags/dummy.py index b7667de..4bca645 100644 --- a/analytical/tests/templatetags/dummy.py +++ b/analytical/tests/templatetags/dummy.py @@ -2,8 +2,6 @@ Dummy testing template tags and filters. """ -from __future__ import absolute_import - from django.template import Library, Node, TemplateSyntaxError from analytical.templatetags.analytical import TAG_LOCATIONS @@ -19,7 +17,7 @@ def _location_node(location): return DummyNode -_location_nodes = dict((loc, _location_node(loc)) for loc in TAG_LOCATIONS) +_location_nodes = {loc: _location_node(loc) for loc in TAG_LOCATIONS} def _location_tag(location): diff --git a/analytical/tests/test_tag_analytical.py b/analytical/tests/test_tag_analytical.py index c12ac48..1dcba80 100644 --- a/analytical/tests/test_tag_analytical.py +++ b/analytical/tests/test_tag_analytical.py @@ -14,7 +14,7 @@ class AnalyticsTagTestCase(TagTestCase): """ def setUp(self): - super(AnalyticsTagTestCase, self).setUp() + super().setUp() self._tag_modules = analytical.TAG_MODULES analytical.TAG_MODULES = ['analytical.tests.dummy'] analytical.template_nodes = analytical._load_template_nodes() @@ -22,7 +22,7 @@ class AnalyticsTagTestCase(TagTestCase): def tearDown(self): analytical.TAG_MODULES = self._tag_modules analytical.template_nodes = analytical._load_template_nodes() - super(AnalyticsTagTestCase, self).tearDown() + super().tearDown() def render_location_tag(self, location, vars=None): if vars is None: diff --git a/analytical/tests/test_tag_facebook_pixel.py b/analytical/tests/test_tag_facebook_pixel.py index 75d52aa..e196ce9 100644 --- a/analytical/tests/test_tag_facebook_pixel.py +++ b/analytical/tests/test_tag_facebook_pixel.py @@ -56,13 +56,13 @@ class FacebookPixelTagTestCase(TagTestCase): self.assertEqual(expected_body_html, html) def test_tags_take_no_args(self): - self.assertRaisesRegexp( + self.assertRaisesRegex( TemplateSyntaxError, r"^'facebook_pixel_head' takes no arguments$", lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_head "arg" %}') .render(Context({}))), ) - self.assertRaisesRegexp( + self.assertRaisesRegex( TemplateSyntaxError, r"^'facebook_pixel_body' takes no arguments$", lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_body "arg" %}') @@ -72,15 +72,15 @@ class FacebookPixelTagTestCase(TagTestCase): @override_settings(FACEBOOK_PIXEL_ID=None) def test_no_id(self): expected_pattern = r'^FACEBOOK_PIXEL_ID setting is not set$' - self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelHeadNode) - self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelBodyNode) + self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelHeadNode) + self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelBodyNode) @override_settings(FACEBOOK_PIXEL_ID='invalid') def test_invalid_id(self): expected_pattern = ( r"^FACEBOOK_PIXEL_ID setting: must be \(a string containing\) a number: 'invalid'$") - self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelHeadNode) - self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelBodyNode) + self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelHeadNode) + self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelBodyNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): diff --git a/analytical/tests/test_tag_hotjar.py b/analytical/tests/test_tag_hotjar.py index c7e656d..e39946d 100644 --- a/analytical/tests/test_tag_hotjar.py +++ b/analytical/tests/test_tag_hotjar.py @@ -39,7 +39,7 @@ class HotjarTagTestCase(TagTestCase): self.assertEqual(expected_html, html) def test_tags_take_no_args(self): - self.assertRaisesRegexp( + self.assertRaisesRegex( TemplateSyntaxError, r"^'hotjar' takes no arguments$", lambda: (Template('{% load hotjar %}{% hotjar "arg" %}') @@ -49,13 +49,13 @@ class HotjarTagTestCase(TagTestCase): @override_settings(HOTJAR_SITE_ID=None) def test_no_id(self): expected_pattern = r'^HOTJAR_SITE_ID setting is not set$' - self.assertRaisesRegexp(AnalyticalException, expected_pattern, HotjarNode) + self.assertRaisesRegex(AnalyticalException, expected_pattern, HotjarNode) @override_settings(HOTJAR_SITE_ID='invalid') def test_invalid_id(self): expected_pattern = ( r"^HOTJAR_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") - self.assertRaisesRegexp(AnalyticalException, expected_pattern, HotjarNode) + self.assertRaisesRegex(AnalyticalException, expected_pattern, HotjarNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): diff --git a/analytical/tests/test_tag_intercom.py b/analytical/tests/test_tag_intercom.py index 2085fcb..03cc47f 100644 --- a/analytical/tests/test_tag_intercom.py +++ b/analytical/tests/test_tag_intercom.py @@ -9,7 +9,7 @@ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings -from analytical.templatetags.intercom import IntercomNode, intercom_user_hash, _timestamp +from analytical.templatetags.intercom import IntercomNode, intercom_user_hash from analytical.tests.utils import TagTestCase from analytical.utils import AnalyticalException @@ -123,7 +123,7 @@ class IntercomTagTestCase(TagTestCase): ) # type: User attrs = IntercomNode()._get_custom_attrs(Context({'user': user})) self.assertEqual({ - 'created_at': int(_timestamp(user.date_joined)), + 'created_at': int(user.date_joined.timestamp()), 'email': 'test@example.com', 'name': '', 'user_hash': intercom_user_hash(str(user.pk)), diff --git a/analytical/tests/test_tag_olark.py b/analytical/tests/test_tag_olark.py index f65514b..378bea7 100644 --- a/analytical/tests/test_tag_olark.py +++ b/analytical/tests/test_tag_olark.py @@ -87,7 +87,7 @@ class OlarkTestCase(TagTestCase): "introduction_messages", "introduction_submit_button_text", ] - vars = dict(('olark_%s' % m, m) for m in messages) + vars = {'olark_%s' % m: m for m in messages} r = OlarkNode().render(Context(vars)) for m in messages: self.assertTrue("olark.configure('locale.%s', \"%s\");" % (m, m) in r, r) diff --git a/analytical/tests/test_utils.py b/analytical/tests/test_utils.py index 83c4cf6..5a7bd1f 100644 --- a/analytical/tests/test_utils.py +++ b/analytical/tests/test_utils.py @@ -27,17 +27,8 @@ class SettingDeletedTestCase(TestCase): Make sure using get_required_setting fails in the right place. """ - # available in python >= 3.2 - if hasattr(self, 'assertRaisesRegex'): - with self.assertRaisesRegex(AnalyticalException, "^USER_ID setting is not set$"): - get_required_setting("USER_ID", r"\d+", "invalid USER_ID") - # available in python >= 2.7, deprecated in 3.2 - elif hasattr(self, 'assertRaisesRegexp'): - with self.assertRaisesRegexp(AnalyticalException, "^USER_ID setting is not set$"): - get_required_setting("USER_ID", r"\d+", "invalid USER_ID") - else: - self.assertRaises(AnalyticalException, - get_required_setting, "USER_ID", r"\d+", "invalid USER_ID") + with self.assertRaisesRegex(AnalyticalException, "^USER_ID setting is not set$"): + get_required_setting("USER_ID", r"\d+", "invalid USER_ID") class MyUser(AbstractBaseUser): diff --git a/analytical/tests/utils.py b/analytical/tests/utils.py index b7fc835..75fc6fa 100644 --- a/analytical/tests/utils.py +++ b/analytical/tests/utils.py @@ -2,8 +2,6 @@ Testing utilities. """ -from __future__ import with_statement - from django.template import Template, Context, RequestContext from django.test.testcases import TestCase diff --git a/docs/conf.py b/docs/conf.py index f84736b..7d86315 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # This file is execfile()d with the current directory set to its containing # directory. @@ -14,8 +13,8 @@ import analytical # noqa # -- General configuration -------------------------------------------------- -project = u'django-analytical' -copyright = u'2011-2016, Joost Cassee ' +project = 'django-analytical' +copyright = '2011-2020, Joost Cassee ' release = analytical.__version__ # The short X.Y version. @@ -45,6 +44,6 @@ htmlhelp_basename = 'analyticaldoc' # -- Options for LaTeX output ----------------------------------------------- latex_documents = [ - ('index', 'django-analytical.tex', u'Documentation for django-analytical', - u'Joost Cassee', 'manual'), + ('index', 'django-analytical.tex', 'Documentation for django-analytical', + 'Joost Cassee', 'manual'), ] diff --git a/requirements.txt b/requirements.txt index fa56c66..4298a2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -Django>=1.7.0 +Django>=2.2.* diff --git a/setup.py b/setup.py index 9d87937..49267c4 100644 --- a/setup.py +++ b/setup.py @@ -27,23 +27,20 @@ setup( 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', - 'Framework :: Django :: 1.11', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', + 'Framework :: Django :: 3.1', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries :: Python Modules', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ], platforms=['any'], url='https://github.com/jazzband/django-analytical', diff --git a/tox.ini b/tox.ini index d0a9f0f..850352f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,7 @@ [tox] envlist = # Python/Django combinations that are officially supported - py{27,35,36,37}-django111 - py{35,36,37,38}-django22 - py{36,37,38}-django30 + py{36,37,38,39}-django{22,30,31} py37-{flake8,bandit,readme,docs} [testenv] @@ -11,20 +9,20 @@ description = Unit tests deps = coverage pytest-django - django111: Django>=1.11,<2.0 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 + django31: Django>=3.1,<3.2 + commands = coverage run -m pytest coverage xml [gh-actions] python = - 2.7: py27 - 3.5: py35 3.6: py36 3.7: py37 3.8: py38 + 3.9: py39 [testenv:py37-bandit] description = PyCQA security linter From 4515fcd1be1fa245359d2a5bd0a9ff4a6dc9d46e Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 1 Dec 2020 08:01:55 +0000 Subject: [PATCH 031/103] Added quotes to Python versions --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82e923f..3b720e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ['3.6', '3.7', '3.8', '3.9'] steps: - uses: actions/checkout@v2 From 00bc123b88173f3753270eec47e51b49509c0f7c Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 5 Dec 2020 16:22:32 +0100 Subject: [PATCH 032/103] Reduce indentation of Hotjar JS code --- analytical/templatetags/hotjar.py | 16 ++++++++-------- analytical/tests/test_tag_hotjar.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/analytical/templatetags/hotjar.py b/analytical/templatetags/hotjar.py index 300518c..5c01200 100644 --- a/analytical/templatetags/hotjar.py +++ b/analytical/templatetags/hotjar.py @@ -11,14 +11,14 @@ from analytical.utils import get_required_setting, is_internal_ip, disable_html HOTJAR_TRACKING_CODE = """\ """ diff --git a/analytical/tests/test_tag_hotjar.py b/analytical/tests/test_tag_hotjar.py index e39946d..6f285c2 100644 --- a/analytical/tests/test_tag_hotjar.py +++ b/analytical/tests/test_tag_hotjar.py @@ -13,14 +13,14 @@ from analytical.utils import AnalyticalException expected_html = """\ """ From ba8982be0a0b93dfe29feb22e27114848a2d8227 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 5 Dec 2020 14:18:18 +0100 Subject: [PATCH 033/103] Add missing Google Analytics instructions to install section --- docs/install.rst | 10 +++++++++- docs/services/google_analytics_js.rst | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index d8af333..a478898 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -133,10 +133,18 @@ settings required to enable each service are listed here: GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef' -* :doc:`Google Analytics `:: +* :doc:`Google Analytics (legacy) `:: GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8' +* :doc:`Google Analytics (gtag.js) `:: + + GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = 'UA-1234567-8' + +* :doc:`Google Analytics (analytics.js) `:: + + GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-12345678-9' + * :doc:`HubSpot `:: HUBSPOT_PORTAL_ID = '1234' diff --git a/docs/services/google_analytics_js.rst b/docs/services/google_analytics_js.rst index fa47f6d..a40e521 100644 --- a/docs/services/google_analytics_js.rst +++ b/docs/services/google_analytics_js.rst @@ -65,7 +65,7 @@ Javascript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_JS_PROPERTY_ID` in the project :file:`settings.py` file:: - GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-XXXXXX-X' + GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-XXXXXXXX-X' If you do not set a property ID, the tracking code will not be rendered. From a9d7d17ce6b04bd8522e6eb06ef6351efaf70d7f Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 12 Apr 2019 01:00:43 +0200 Subject: [PATCH 034/103] Move tests out of analytical module --- analytical/tests/templatetags/__init__.py | 0 {analytical/tests => tests/testproject}/settings.py | 0 {analytical/tests => tests/testproject/templatetags}/__init__.py | 0 {analytical/tests => tests/testproject}/templatetags/dummy.py | 0 {analytical/tests => tests/unit}/test_tag_analytical.py | 0 {analytical/tests => tests/unit}/test_tag_chartbeat.py | 0 {analytical/tests => tests/unit}/test_tag_clickmap.py | 0 {analytical/tests => tests/unit}/test_tag_clicky.py | 0 {analytical/tests => tests/unit}/test_tag_crazy_egg.py | 0 {analytical/tests => tests/unit}/test_tag_facebook_pixel.py | 0 {analytical/tests => tests/unit}/test_tag_gauges.py | 0 {analytical/tests => tests/unit}/test_tag_google_analytics.py | 0 .../tests => tests/unit}/test_tag_google_analytics_gtag.py | 0 {analytical/tests => tests/unit}/test_tag_google_analytics_js.py | 0 {analytical/tests => tests/unit}/test_tag_gosquared.py | 0 {analytical/tests => tests/unit}/test_tag_hotjar.py | 0 {analytical/tests => tests/unit}/test_tag_hubspot.py | 0 {analytical/tests => tests/unit}/test_tag_intercom.py | 0 {analytical/tests => tests/unit}/test_tag_kiss_insights.py | 0 {analytical/tests => tests/unit}/test_tag_kiss_metrics.py | 0 {analytical/tests => tests/unit}/test_tag_matomo.py | 0 {analytical/tests => tests/unit}/test_tag_mixpanel.py | 0 {analytical/tests => tests/unit}/test_tag_olark.py | 0 {analytical/tests => tests/unit}/test_tag_optimizely.py | 0 {analytical/tests => tests/unit}/test_tag_performable.py | 0 {analytical/tests => tests/unit}/test_tag_piwik.py | 0 {analytical/tests => tests/unit}/test_tag_rating_mailru.py | 0 {analytical/tests => tests/unit}/test_tag_snapengage.py | 0 {analytical/tests => tests/unit}/test_tag_spring_metrics.py | 0 {analytical/tests => tests/unit}/test_tag_uservoice.py | 0 {analytical/tests => tests/unit}/test_tag_woopra.py | 0 {analytical/tests => tests/unit}/test_tag_yandex_metrica.py | 0 {analytical/tests => tests/unit}/test_utils.py | 0 {analytical/tests => tests/unit}/utils.py | 0 34 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 analytical/tests/templatetags/__init__.py rename {analytical/tests => tests/testproject}/settings.py (100%) rename {analytical/tests => tests/testproject/templatetags}/__init__.py (100%) rename {analytical/tests => tests/testproject}/templatetags/dummy.py (100%) rename {analytical/tests => tests/unit}/test_tag_analytical.py (100%) rename {analytical/tests => tests/unit}/test_tag_chartbeat.py (100%) rename {analytical/tests => tests/unit}/test_tag_clickmap.py (100%) rename {analytical/tests => tests/unit}/test_tag_clicky.py (100%) rename {analytical/tests => tests/unit}/test_tag_crazy_egg.py (100%) rename {analytical/tests => tests/unit}/test_tag_facebook_pixel.py (100%) rename {analytical/tests => tests/unit}/test_tag_gauges.py (100%) rename {analytical/tests => tests/unit}/test_tag_google_analytics.py (100%) rename {analytical/tests => tests/unit}/test_tag_google_analytics_gtag.py (100%) rename {analytical/tests => tests/unit}/test_tag_google_analytics_js.py (100%) rename {analytical/tests => tests/unit}/test_tag_gosquared.py (100%) rename {analytical/tests => tests/unit}/test_tag_hotjar.py (100%) rename {analytical/tests => tests/unit}/test_tag_hubspot.py (100%) rename {analytical/tests => tests/unit}/test_tag_intercom.py (100%) rename {analytical/tests => tests/unit}/test_tag_kiss_insights.py (100%) rename {analytical/tests => tests/unit}/test_tag_kiss_metrics.py (100%) rename {analytical/tests => tests/unit}/test_tag_matomo.py (100%) rename {analytical/tests => tests/unit}/test_tag_mixpanel.py (100%) rename {analytical/tests => tests/unit}/test_tag_olark.py (100%) rename {analytical/tests => tests/unit}/test_tag_optimizely.py (100%) rename {analytical/tests => tests/unit}/test_tag_performable.py (100%) rename {analytical/tests => tests/unit}/test_tag_piwik.py (100%) rename {analytical/tests => tests/unit}/test_tag_rating_mailru.py (100%) rename {analytical/tests => tests/unit}/test_tag_snapengage.py (100%) rename {analytical/tests => tests/unit}/test_tag_spring_metrics.py (100%) rename {analytical/tests => tests/unit}/test_tag_uservoice.py (100%) rename {analytical/tests => tests/unit}/test_tag_woopra.py (100%) rename {analytical/tests => tests/unit}/test_tag_yandex_metrica.py (100%) rename {analytical/tests => tests/unit}/test_utils.py (100%) rename {analytical/tests => tests/unit}/utils.py (100%) diff --git a/analytical/tests/templatetags/__init__.py b/analytical/tests/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/analytical/tests/settings.py b/tests/testproject/settings.py similarity index 100% rename from analytical/tests/settings.py rename to tests/testproject/settings.py diff --git a/analytical/tests/__init__.py b/tests/testproject/templatetags/__init__.py similarity index 100% rename from analytical/tests/__init__.py rename to tests/testproject/templatetags/__init__.py diff --git a/analytical/tests/templatetags/dummy.py b/tests/testproject/templatetags/dummy.py similarity index 100% rename from analytical/tests/templatetags/dummy.py rename to tests/testproject/templatetags/dummy.py diff --git a/analytical/tests/test_tag_analytical.py b/tests/unit/test_tag_analytical.py similarity index 100% rename from analytical/tests/test_tag_analytical.py rename to tests/unit/test_tag_analytical.py diff --git a/analytical/tests/test_tag_chartbeat.py b/tests/unit/test_tag_chartbeat.py similarity index 100% rename from analytical/tests/test_tag_chartbeat.py rename to tests/unit/test_tag_chartbeat.py diff --git a/analytical/tests/test_tag_clickmap.py b/tests/unit/test_tag_clickmap.py similarity index 100% rename from analytical/tests/test_tag_clickmap.py rename to tests/unit/test_tag_clickmap.py diff --git a/analytical/tests/test_tag_clicky.py b/tests/unit/test_tag_clicky.py similarity index 100% rename from analytical/tests/test_tag_clicky.py rename to tests/unit/test_tag_clicky.py diff --git a/analytical/tests/test_tag_crazy_egg.py b/tests/unit/test_tag_crazy_egg.py similarity index 100% rename from analytical/tests/test_tag_crazy_egg.py rename to tests/unit/test_tag_crazy_egg.py diff --git a/analytical/tests/test_tag_facebook_pixel.py b/tests/unit/test_tag_facebook_pixel.py similarity index 100% rename from analytical/tests/test_tag_facebook_pixel.py rename to tests/unit/test_tag_facebook_pixel.py diff --git a/analytical/tests/test_tag_gauges.py b/tests/unit/test_tag_gauges.py similarity index 100% rename from analytical/tests/test_tag_gauges.py rename to tests/unit/test_tag_gauges.py diff --git a/analytical/tests/test_tag_google_analytics.py b/tests/unit/test_tag_google_analytics.py similarity index 100% rename from analytical/tests/test_tag_google_analytics.py rename to tests/unit/test_tag_google_analytics.py diff --git a/analytical/tests/test_tag_google_analytics_gtag.py b/tests/unit/test_tag_google_analytics_gtag.py similarity index 100% rename from analytical/tests/test_tag_google_analytics_gtag.py rename to tests/unit/test_tag_google_analytics_gtag.py diff --git a/analytical/tests/test_tag_google_analytics_js.py b/tests/unit/test_tag_google_analytics_js.py similarity index 100% rename from analytical/tests/test_tag_google_analytics_js.py rename to tests/unit/test_tag_google_analytics_js.py diff --git a/analytical/tests/test_tag_gosquared.py b/tests/unit/test_tag_gosquared.py similarity index 100% rename from analytical/tests/test_tag_gosquared.py rename to tests/unit/test_tag_gosquared.py diff --git a/analytical/tests/test_tag_hotjar.py b/tests/unit/test_tag_hotjar.py similarity index 100% rename from analytical/tests/test_tag_hotjar.py rename to tests/unit/test_tag_hotjar.py diff --git a/analytical/tests/test_tag_hubspot.py b/tests/unit/test_tag_hubspot.py similarity index 100% rename from analytical/tests/test_tag_hubspot.py rename to tests/unit/test_tag_hubspot.py diff --git a/analytical/tests/test_tag_intercom.py b/tests/unit/test_tag_intercom.py similarity index 100% rename from analytical/tests/test_tag_intercom.py rename to tests/unit/test_tag_intercom.py diff --git a/analytical/tests/test_tag_kiss_insights.py b/tests/unit/test_tag_kiss_insights.py similarity index 100% rename from analytical/tests/test_tag_kiss_insights.py rename to tests/unit/test_tag_kiss_insights.py diff --git a/analytical/tests/test_tag_kiss_metrics.py b/tests/unit/test_tag_kiss_metrics.py similarity index 100% rename from analytical/tests/test_tag_kiss_metrics.py rename to tests/unit/test_tag_kiss_metrics.py diff --git a/analytical/tests/test_tag_matomo.py b/tests/unit/test_tag_matomo.py similarity index 100% rename from analytical/tests/test_tag_matomo.py rename to tests/unit/test_tag_matomo.py diff --git a/analytical/tests/test_tag_mixpanel.py b/tests/unit/test_tag_mixpanel.py similarity index 100% rename from analytical/tests/test_tag_mixpanel.py rename to tests/unit/test_tag_mixpanel.py diff --git a/analytical/tests/test_tag_olark.py b/tests/unit/test_tag_olark.py similarity index 100% rename from analytical/tests/test_tag_olark.py rename to tests/unit/test_tag_olark.py diff --git a/analytical/tests/test_tag_optimizely.py b/tests/unit/test_tag_optimizely.py similarity index 100% rename from analytical/tests/test_tag_optimizely.py rename to tests/unit/test_tag_optimizely.py diff --git a/analytical/tests/test_tag_performable.py b/tests/unit/test_tag_performable.py similarity index 100% rename from analytical/tests/test_tag_performable.py rename to tests/unit/test_tag_performable.py diff --git a/analytical/tests/test_tag_piwik.py b/tests/unit/test_tag_piwik.py similarity index 100% rename from analytical/tests/test_tag_piwik.py rename to tests/unit/test_tag_piwik.py diff --git a/analytical/tests/test_tag_rating_mailru.py b/tests/unit/test_tag_rating_mailru.py similarity index 100% rename from analytical/tests/test_tag_rating_mailru.py rename to tests/unit/test_tag_rating_mailru.py diff --git a/analytical/tests/test_tag_snapengage.py b/tests/unit/test_tag_snapengage.py similarity index 100% rename from analytical/tests/test_tag_snapengage.py rename to tests/unit/test_tag_snapengage.py diff --git a/analytical/tests/test_tag_spring_metrics.py b/tests/unit/test_tag_spring_metrics.py similarity index 100% rename from analytical/tests/test_tag_spring_metrics.py rename to tests/unit/test_tag_spring_metrics.py diff --git a/analytical/tests/test_tag_uservoice.py b/tests/unit/test_tag_uservoice.py similarity index 100% rename from analytical/tests/test_tag_uservoice.py rename to tests/unit/test_tag_uservoice.py diff --git a/analytical/tests/test_tag_woopra.py b/tests/unit/test_tag_woopra.py similarity index 100% rename from analytical/tests/test_tag_woopra.py rename to tests/unit/test_tag_woopra.py diff --git a/analytical/tests/test_tag_yandex_metrica.py b/tests/unit/test_tag_yandex_metrica.py similarity index 100% rename from analytical/tests/test_tag_yandex_metrica.py rename to tests/unit/test_tag_yandex_metrica.py diff --git a/analytical/tests/test_utils.py b/tests/unit/test_utils.py similarity index 100% rename from analytical/tests/test_utils.py rename to tests/unit/test_utils.py diff --git a/analytical/tests/utils.py b/tests/unit/utils.py similarity index 100% rename from analytical/tests/utils.py rename to tests/unit/utils.py From bf471d7dfcef58250ffc7971cca70a673d884c3b Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 12 Apr 2019 01:45:45 +0200 Subject: [PATCH 035/103] Allow test suite to run with pytest --- setup.cfg | 2 +- tests/unit/test_tag_analytical.py | 4 ++-- tests/unit/test_tag_chartbeat.py | 2 +- tests/unit/test_tag_clickmap.py | 2 +- tests/unit/test_tag_clicky.py | 2 +- tests/unit/test_tag_crazy_egg.py | 2 +- tests/unit/test_tag_facebook_pixel.py | 2 +- tests/unit/test_tag_gauges.py | 2 +- tests/unit/test_tag_google_analytics.py | 2 +- tests/unit/test_tag_google_analytics_gtag.py | 2 +- tests/unit/test_tag_google_analytics_js.py | 2 +- tests/unit/test_tag_gosquared.py | 2 +- tests/unit/test_tag_hotjar.py | 2 +- tests/unit/test_tag_hubspot.py | 2 +- tests/unit/test_tag_intercom.py | 2 +- tests/unit/test_tag_kiss_insights.py | 2 +- tests/unit/test_tag_kiss_metrics.py | 2 +- tests/unit/test_tag_matomo.py | 2 +- tests/unit/test_tag_mixpanel.py | 2 +- tests/unit/test_tag_olark.py | 2 +- tests/unit/test_tag_optimizely.py | 2 +- tests/unit/test_tag_performable.py | 2 +- tests/unit/test_tag_piwik.py | 2 +- tests/unit/test_tag_rating_mailru.py | 2 +- tests/unit/test_tag_snapengage.py | 2 +- tests/unit/test_tag_spring_metrics.py | 2 +- tests/unit/test_tag_uservoice.py | 2 +- tests/unit/test_tag_woopra.py | 2 +- tests/unit/test_tag_yandex_metrica.py | 2 +- tests/unit/test_utils.py | 6 +++++- 30 files changed, 35 insertions(+), 31 deletions(-) diff --git a/setup.cfg b/setup.cfg index f3c3b37..72285af 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,4 +7,4 @@ all_files = 1 upload-dir = build/docs/html [tool:pytest] -DJANGO_SETTINGS_MODULE = analytical.tests.settings +DJANGO_SETTINGS_MODULE = tests.testproject.settings diff --git a/tests/unit/test_tag_analytical.py b/tests/unit/test_tag_analytical.py index 1dcba80..81e7936 100644 --- a/tests/unit/test_tag_analytical.py +++ b/tests/unit/test_tag_analytical.py @@ -5,7 +5,7 @@ Tests for the generic template tags and filters. from django.template import Context, Template from analytical.templatetags import analytical -from analytical.tests.utils import TagTestCase +from utils import TagTestCase class AnalyticsTagTestCase(TagTestCase): @@ -16,7 +16,7 @@ class AnalyticsTagTestCase(TagTestCase): def setUp(self): super().setUp() self._tag_modules = analytical.TAG_MODULES - analytical.TAG_MODULES = ['analytical.tests.dummy'] + analytical.TAG_MODULES = ['tests.testproject.dummy'] analytical.template_nodes = analytical._load_template_nodes() def tearDown(self): diff --git a/tests/unit/test_tag_chartbeat.py b/tests/unit/test_tag_chartbeat.py index 67ebd10..c6f420c 100644 --- a/tests/unit/test_tag_chartbeat.py +++ b/tests/unit/test_tag_chartbeat.py @@ -11,7 +11,7 @@ from django.test.utils import override_settings from analytical.templatetags.chartbeat import ChartbeatTopNode, \ ChartbeatBottomNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_clickmap.py b/tests/unit/test_tag_clickmap.py index ed8656f..588a209 100644 --- a/tests/unit/test_tag_clickmap.py +++ b/tests/unit/test_tag_clickmap.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.clickmap import ClickmapNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_clicky.py b/tests/unit/test_tag_clicky.py index b2f2731..b830e57 100644 --- a/tests/unit/test_tag_clicky.py +++ b/tests/unit/test_tag_clicky.py @@ -10,7 +10,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.clicky import ClickyNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_crazy_egg.py b/tests/unit/test_tag_crazy_egg.py index 66e6d56..396591e 100644 --- a/tests/unit/test_tag_crazy_egg.py +++ b/tests/unit/test_tag_crazy_egg.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.crazy_egg import CrazyEggNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_facebook_pixel.py b/tests/unit/test_tag_facebook_pixel.py index e196ce9..1bbdfad 100644 --- a/tests/unit/test_tag_facebook_pixel.py +++ b/tests/unit/test_tag_facebook_pixel.py @@ -7,7 +7,7 @@ from django.test import override_settings from analytical.templatetags.analytical import _load_template_nodes from analytical.templatetags.facebook_pixel import FacebookPixelHeadNode, FacebookPixelBodyNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_gauges.py b/tests/unit/test_tag_gauges.py index 8d80b80..7eab19d 100644 --- a/tests/unit/test_tag_gauges.py +++ b/tests/unit/test_tag_gauges.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.gauges import GaugesNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_google_analytics.py b/tests/unit/test_tag_google_analytics.py index 67c8360..da73996 100644 --- a/tests/unit/test_tag_google_analytics.py +++ b/tests/unit/test_tag_google_analytics.py @@ -9,7 +9,7 @@ from django.test.utils import override_settings from analytical.templatetags.google_analytics import GoogleAnalyticsNode, \ TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS,\ SCOPE_VISITOR, SCOPE_SESSION, SCOPE_PAGE -from analytical.tests.utils import TestCase, TagTestCase +from utils import TestCase, TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_google_analytics_gtag.py b/tests/unit/test_tag_google_analytics_gtag.py index 1e5430c..c81368f 100644 --- a/tests/unit/test_tag_google_analytics_gtag.py +++ b/tests/unit/test_tag_google_analytics_gtag.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.google_analytics_gtag import GoogleAnalyticsGTagNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_google_analytics_js.py b/tests/unit/test_tag_google_analytics_js.py index 517eb8b..1009fbb 100644 --- a/tests/unit/test_tag_google_analytics_js.py +++ b/tests/unit/test_tag_google_analytics_js.py @@ -8,7 +8,7 @@ from django.test.utils import override_settings from analytical.templatetags.google_analytics_js import GoogleAnalyticsJsNode, \ TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS -from analytical.tests.utils import TestCase, TagTestCase +from utils import TestCase, TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_gosquared.py b/tests/unit/test_tag_gosquared.py index 844205c..444420f 100644 --- a/tests/unit/test_tag_gosquared.py +++ b/tests/unit/test_tag_gosquared.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.gosquared import GoSquaredNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_hotjar.py b/tests/unit/test_tag_hotjar.py index 6f285c2..d5978b8 100644 --- a/tests/unit/test_tag_hotjar.py +++ b/tests/unit/test_tag_hotjar.py @@ -7,7 +7,7 @@ from django.test import override_settings from analytical.templatetags.analytical import _load_template_nodes from analytical.templatetags.hotjar import HotjarNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_hubspot.py b/tests/unit/test_tag_hubspot.py index ee9d2ff..5678258 100644 --- a/tests/unit/test_tag_hubspot.py +++ b/tests/unit/test_tag_hubspot.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.hubspot import HubSpotNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_intercom.py b/tests/unit/test_tag_intercom.py index 03cc47f..be99ccc 100644 --- a/tests/unit/test_tag_intercom.py +++ b/tests/unit/test_tag_intercom.py @@ -10,7 +10,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.intercom import IntercomNode, intercom_user_hash -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_kiss_insights.py b/tests/unit/test_tag_kiss_insights.py index ffbba16..dc627d1 100644 --- a/tests/unit/test_tag_kiss_insights.py +++ b/tests/unit/test_tag_kiss_insights.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.kiss_insights import KissInsightsNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_kiss_metrics.py b/tests/unit/test_tag_kiss_metrics.py index 506b635..d3e885f 100644 --- a/tests/unit/test_tag_kiss_metrics.py +++ b/tests/unit/test_tag_kiss_metrics.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.kiss_metrics import KissMetricsNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_matomo.py b/tests/unit/test_tag_matomo.py index d3d6785..fc37614 100644 --- a/tests/unit/test_tag_matomo.py +++ b/tests/unit/test_tag_matomo.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.matomo import MatomoNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_mixpanel.py b/tests/unit/test_tag_mixpanel.py index 033c2c7..e432ea6 100644 --- a/tests/unit/test_tag_mixpanel.py +++ b/tests/unit/test_tag_mixpanel.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.mixpanel import MixpanelNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_olark.py b/tests/unit/test_tag_olark.py index 378bea7..a18fc89 100644 --- a/tests/unit/test_tag_olark.py +++ b/tests/unit/test_tag_olark.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.olark import OlarkNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_optimizely.py b/tests/unit/test_tag_optimizely.py index a1cce26..367f359 100644 --- a/tests/unit/test_tag_optimizely.py +++ b/tests/unit/test_tag_optimizely.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.optimizely import OptimizelyNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_performable.py b/tests/unit/test_tag_performable.py index 2b1e16d..24e20c8 100644 --- a/tests/unit/test_tag_performable.py +++ b/tests/unit/test_tag_performable.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.performable import PerformableNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_piwik.py b/tests/unit/test_tag_piwik.py index 32661ee..38aa317 100644 --- a/tests/unit/test_tag_piwik.py +++ b/tests/unit/test_tag_piwik.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.piwik import PiwikNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_rating_mailru.py b/tests/unit/test_tag_rating_mailru.py index 45d0309..d8f9459 100644 --- a/tests/unit/test_tag_rating_mailru.py +++ b/tests/unit/test_tag_rating_mailru.py @@ -7,7 +7,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.rating_mailru import RatingMailruNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_snapengage.py b/tests/unit/test_tag_snapengage.py index 02b37cc..215ebe2 100644 --- a/tests/unit/test_tag_snapengage.py +++ b/tests/unit/test_tag_snapengage.py @@ -11,7 +11,7 @@ from analytical.templatetags.snapengage import SnapEngageNode, \ BUTTON_STYLE_LIVE, BUTTON_STYLE_DEFAULT, BUTTON_STYLE_NONE, \ BUTTON_LOCATION_LEFT, BUTTON_LOCATION_RIGHT, BUTTON_LOCATION_TOP, \ BUTTON_LOCATION_BOTTOM, FORM_POSITION_TOP_LEFT -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_spring_metrics.py b/tests/unit/test_tag_spring_metrics.py index 14aeb3c..ca00e71 100644 --- a/tests/unit/test_tag_spring_metrics.py +++ b/tests/unit/test_tag_spring_metrics.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.spring_metrics import SpringMetricsNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_uservoice.py b/tests/unit/test_tag_uservoice.py index 9bce78d..c740311 100644 --- a/tests/unit/test_tag_uservoice.py +++ b/tests/unit/test_tag_uservoice.py @@ -6,7 +6,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.uservoice import UserVoiceNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_woopra.py b/tests/unit/test_tag_woopra.py index 671e7de..9dca7e9 100644 --- a/tests/unit/test_tag_woopra.py +++ b/tests/unit/test_tag_woopra.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.woopra import WoopraNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_yandex_metrica.py b/tests/unit/test_tag_yandex_metrica.py index fa287db..3b0619e 100644 --- a/tests/unit/test_tag_yandex_metrica.py +++ b/tests/unit/test_tag_yandex_metrica.py @@ -8,7 +8,7 @@ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.yandex_metrica import YandexMetricaNode -from analytical.tests.utils import TagTestCase +from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 5a7bd1f..22b2a88 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -16,7 +16,7 @@ from analytical.utils import ( get_required_setting, is_internal_ip, ) -from analytical.tests.utils import TestCase +from utils import TestCase class SettingDeletedTestCase(TestCase): @@ -35,6 +35,10 @@ class MyUser(AbstractBaseUser): identity = models.CharField(max_length=50) USERNAME_FIELD = 'identity' + class Meta: + abstract = True + app_label = 'testapp' + class GetIdentityTestCase(TestCase): def test_custom_username_field(self): From 47fa93791097333d750db84b93ec8a7abef37b82 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Sun, 6 Dec 2020 13:50:34 +0200 Subject: [PATCH 036/103] Include tests in source manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 8bded2d..444ac9e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include LICENSE.txt *.rst recursive-include docs *.rst *.py +recursive-include tests *.py From 5ea7d15868bc25505ccaff194394b271b774ce0c Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Sun, 6 Dec 2020 13:51:06 +0200 Subject: [PATCH 037/103] Coverage config: Remove stale `omit` entry --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 99095c1..528aad9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,2 @@ [run] source = analytical -omit = analytical/tests/* From 7f1358dcb6ba5075c1063c3a18a4da73c8b2201e Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 6 Dec 2020 15:06:34 +0100 Subject: [PATCH 038/103] Hard-require Python 3.6+, add link to docs on PyPI --- .gitignore | 1 + setup.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6e8fe88..c702437 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /.idea /.tox /.coverage +/coverage.xml /build /dist diff --git a/setup.py b/setup.py index 49267c4..abff164 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,10 @@ setup( 'analytical', 'analytical.templatetags', ], - keywords=['django', 'analytics'], + keywords=[ + 'django', + 'analytics', + ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', @@ -34,15 +37,19 @@ setup( 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Software Development :: Libraries :: Python Modules', ], + python_requires='>=3.6', platforms=['any'], url='https://github.com/jazzband/django-analytical', download_url='https://github.com/jazzband/django-analytical/archive/master.zip', + project_urls={ + 'Documentation': 'https://django-analytical.readthedocs.io/', + }, ) From 514780aeb2c14e16facdbd8566061f26dd455dde Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Thu, 3 Dec 2020 23:11:27 +0100 Subject: [PATCH 039/103] Add documentation for Lucky Orange --- README.rst | 2 + docs/install.rst | 4 ++ docs/services/luckyorange.rst | 75 +++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 docs/services/luckyorange.rst diff --git a/README.rst b/README.rst index 2c5dc22..3b5fa6e 100644 --- a/README.rst +++ b/README.rst @@ -62,6 +62,7 @@ Currently Supported Services * `Intercom`_ live chat and support * `KISSinsights`_ feedback surveys * `KISSmetrics`_ funnel analysis +* `Lucky Orange`_ analytics and user feedback * `Mixpanel`_ event tracking * `Olark`_ visitor chat * `Optimizely`_ A/B testing @@ -87,6 +88,7 @@ Currently Supported Services .. _`Intercom`: http://www.intercom.io/ .. _`KISSinsights`: http://www.kissinsights.com/ .. _`KISSmetrics`: http://www.kissmetrics.com/ +.. _`Lucky Orange`: http://www.luckyorange.com/ .. _`Mixpanel`: http://www.mixpanel.com/ .. _`Olark`: http://www.olark.com/ .. _`Optimizely`: http://www.optimizely.com/ diff --git a/docs/install.rst b/docs/install.rst index a478898..ba82fbb 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -163,6 +163,10 @@ settings required to enable each service are listed here: KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567' +* :doc:`Lucky Orange `:: + + LUCKYORANGE_SITE_ID = '123456' + * :doc:`Matomo (formerly Piwik) `:: MATOMO_DOMAIN_PATH = 'your.matomo.server/optional/path' diff --git a/docs/services/luckyorange.rst b/docs/services/luckyorange.rst new file mode 100644 index 0000000..659604a --- /dev/null +++ b/docs/services/luckyorange.rst @@ -0,0 +1,75 @@ +================================================== +Lucky Orange -- All-in-one conversion optimization +================================================== + +`Lucky Orange`_ is a website analytics and user feedback tool. + +.. _`Lucky Orange`: https://www.luckyorange.com/ + + +.. luckyorange-installation: + +Installation +============ + +To start using the Lucky Orange 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 Lucky Orange 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:`luckyorange-configuration`. + +The Lucky Orange tracking 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:`luckyorange` template tag library. Then insert the +:ttag:`luckyorange` tag at the bottom of the head section:: + + {% load luckyorange %} + + + ... + {% luckyorange %} + + ... + + + +.. _luckyorange-configuration: + +Configuration +============= + +Before you can use the Lucky Orange integration, you must first set your +Site ID. + + +.. _luckyorange-id: + +Setting the Lucky Orange Site ID +-------------------------------- + +You can find the Lucky Orange Site ID in the "Settings" of your Lucky +Orange account, reachable via the gear icon on the top right corner. +Set :const:`LUCKYORANGE_SITE_ID` in the project :file:`settings.py` file:: + + LUCKYORANGE_SITE_ID = 'XXXXXX' + +If you do not set a Lucky Orange Site ID, the code will not be rendered. + + +.. _luckyorange-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:`LUCKYORANGE_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. From a3fe981f761f775e55c3104ce725d017fe061ed5 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Dec 2020 00:38:50 +0100 Subject: [PATCH 040/103] Add tests for Lucky Orange --- tests/unit/test_tag_luckyorange.py | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/unit/test_tag_luckyorange.py diff --git a/tests/unit/test_tag_luckyorange.py b/tests/unit/test_tag_luckyorange.py new file mode 100644 index 0000000..a4e082b --- /dev/null +++ b/tests/unit/test_tag_luckyorange.py @@ -0,0 +1,82 @@ +""" +Tests for the Lucky Orange template tags. +""" +from django.http import HttpRequest +from django.template import Context, Template, TemplateSyntaxError +from django.test import override_settings + +from analytical.templatetags.analytical import _load_template_nodes +from analytical.templatetags.luckyorange import LuckyOrangeNode +from utils import TagTestCase +from analytical.utils import AnalyticalException + + +expected_html = """\ + +""" + + +@override_settings(LUCKYORANGE_SITE_ID='123456') +class LuckyOrangeTagTestCase(TagTestCase): + + maxDiff = None + + def test_tag(self): + html = self.render_tag('luckyorange', 'luckyorange') + self.assertEqual(expected_html, html) + + def test_node(self): + html = LuckyOrangeNode().render(Context({})) + self.assertEqual(expected_html, html) + + def test_tags_take_no_args(self): + self.assertRaisesRegex( + TemplateSyntaxError, + r"^'luckyorange' takes no arguments$", + lambda: (Template('{% load luckyorange %}{% luckyorange "arg" %}') + .render(Context({}))), + ) + + @override_settings(LUCKYORANGE_SITE_ID=None) + def test_no_id(self): + expected_pattern = r'^LUCKYORANGE_SITE_ID setting is not set$' + self.assertRaisesRegex(AnalyticalException, expected_pattern, LuckyOrangeNode) + + @override_settings(LUCKYORANGE_SITE_ID='invalid') + def test_invalid_id(self): + expected_pattern = ( + r"^LUCKYORANGE_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") + self.assertRaisesRegex(AnalyticalException, expected_pattern, LuckyOrangeNode) + + @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 = LuckyOrangeNode().render(context) + disabled_html = '\n'.join([ + '', + ]) + self.assertEqual(disabled_html, actual_html) + + def test_contribute_to_analytical(self): + """ + `luckyorange.contribute_to_analytical` registers the head and body nodes. + """ + template_nodes = _load_template_nodes() + self.assertEqual({ + 'head_top': [], + 'head_bottom': [LuckyOrangeNode], + 'body_top': [], + 'body_bottom': [], + }, template_nodes) From 8cd75d9e606add110db5a2431bf80c732ccf08cc Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 6 Dec 2020 16:45:34 +0100 Subject: [PATCH 041/103] Add business code for Lucky Orange --- analytical/templatetags/analytical.py | 1 + analytical/templatetags/luckyorange.py | 62 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 analytical/templatetags/luckyorange.py diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index f421cf6..bc9c262 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -29,6 +29,7 @@ TAG_MODULES = [ 'analytical.intercom', 'analytical.kiss_insights', 'analytical.kiss_metrics', + 'analytical.luckyorange', 'analytical.matomo', 'analytical.mixpanel', 'analytical.olark', diff --git a/analytical/templatetags/luckyorange.py b/analytical/templatetags/luckyorange.py new file mode 100644 index 0000000..7eb6ee9 --- /dev/null +++ b/analytical/templatetags/luckyorange.py @@ -0,0 +1,62 @@ +""" +Lucky Orange template tags and filters. +""" + +import re + +from django.template import Library, Node, TemplateSyntaxError + +from analytical.utils import get_required_setting, is_internal_ip, disable_html + + +LUCKYORANGE_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 luckyorange(parser, token): + """ + Lucky Orange template tag. + """ + _validate_no_args(token) + return LuckyOrangeNode() + + +class LuckyOrangeNode(Node): + + def __init__(self): + self.site_id = get_required_setting( + 'LUCKYORANGE_SITE_ID', + re.compile(r'^\d+$'), + "must be (a string containing) a number", + ) + + def render(self, context): + html = LUCKYORANGE_TRACKING_CODE % {'LUCKYORANGE_SITE_ID': self.site_id} + if is_internal_ip(context, 'LUCKYORANGE'): + return disable_html(html, 'Lucky Orange') + else: + return html + + +def contribute_to_analytical(add_node): + # ensure properly configured + LuckyOrangeNode() + add_node('head_bottom', LuckyOrangeNode) From 896a19196f0ecca146e4cb8fb3b26e71e15642c8 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 6 Dec 2020 21:19:16 +0100 Subject: [PATCH 042/103] Release v3.0.0 --- CHANGELOG.rst | 13 ++++++++++++- analytical/__init__.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e1dc085..84ea711 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,14 @@ +Version 3.0.0 +------------- +* Add support for Lucky Orange (Peter Bittner) +* Add missing instructions in Installation chapter of the docs (Peter Bittner) +* Migrate test setup to Pytest (David Smith, Peter Bittner, Pi Delport) +* Support Django 3.1 and Python 3.9, drop Django 1.11 and Python 2.7/3.5 (David Smith) +* Migrate from Travis CI to GitHub Actions (Jannis Leidel) +* Update accepted patterns (regex) for Google Analytics GTag (Taha Rushain) +* Scope Piwik warning to use of Piwik (Hugo Barrera) +* Add ``user_id`` to Google Analytics GTag (Sean Wallace) + Version 2.6.0 ------------- * Support Django 3.0 and Python 3.8, drop Django 2.1 @@ -9,7 +20,7 @@ Version 2.5.0 ------------- * Add support for Google analytics.js (Marc Bourqui) * Add support for Intercom HMAC identity verification (Pi Delport) -* Add support for HotJar (Pi Delport) +* Add support for Hotjar (Pi Delport) * Make sure _trackPageview happens before other settings in Google Analytics (Diederik van der Boor) diff --git a/analytical/__init__.py b/analytical/__init__.py index ee25a09..9253891 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -4,6 +4,6 @@ Analytics service integration for Django projects __author__ = "Joost Cassee" __email__ = "joost@cassee.net" -__version__ = "2.6.0" +__version__ = "3.0.0" __copyright__ = "Copyright (C) 2011-2020 Joost Cassee and contributors" __license__ = "MIT" From baaa1c4a8b7f02ba453d5257f80163bee72f7631 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 8 Dec 2020 22:32:58 +0000 Subject: [PATCH 043/103] Migrate tests to Pytest plain asserts --- tests/unit/test_tag_analytical.py | 2 +- tests/unit/test_tag_chartbeat.py | 23 +++++++++++------------ tests/unit/test_tag_clickmap.py | 9 ++++----- tests/unit/test_tag_clicky.py | 21 +++++++++------------ tests/unit/test_tag_crazy_egg.py | 13 ++++++------- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/tests/unit/test_tag_analytical.py b/tests/unit/test_tag_analytical.py index 81e7936..e037afc 100644 --- a/tests/unit/test_tag_analytical.py +++ b/tests/unit/test_tag_analytical.py @@ -33,4 +33,4 @@ class AnalyticsTagTestCase(TagTestCase): def test_location_tags(self): for loc in ['head_top', 'head_bottom', 'body_top', 'body_bottom']: r = self.render_location_tag(loc) - self.assertTrue('dummy_%s' % loc in r, r) + assert f'dummy_{loc}' in r diff --git a/tests/unit/test_tag_chartbeat.py b/tests/unit/test_tag_chartbeat.py index c6f420c..01363bb 100644 --- a/tests/unit/test_tag_chartbeat.py +++ b/tests/unit/test_tag_chartbeat.py @@ -39,8 +39,8 @@ class ChartbeatTagTestCaseWithSites(TestCase): site = Site.objects.create(domain="test.com", name="test") with override_settings(SITE_ID=site.id): r = ChartbeatBottomNode().render(Context()) - self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r) - self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r) + assert re.search('var _sf_async_config={.*"uid": "12345".*};', r) + assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r) @override_settings(CHARTBEAT_AUTO_DOMAIN=False) def test_auto_domain_false(self): @@ -50,7 +50,7 @@ class ChartbeatTagTestCaseWithSites(TestCase): in _sf_async_config. """ r = ChartbeatBottomNode().render(Context()) - self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r) + assert 'var _sf_async_config={"uid": "12345"};' in r @override_settings(CHARTBEAT_USER_ID='12345') @@ -61,25 +61,25 @@ class ChartbeatTagTestCase(TagTestCase): def test_top_tag(self): r = self.render_tag('chartbeat', 'chartbeat_top', {'chartbeat_domain': "test.com"}) - self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r) + assert 'var _sf_startpt=(new Date()).getTime()' in r def test_bottom_tag(self): r = self.render_tag('chartbeat', 'chartbeat_bottom', {'chartbeat_domain': "test.com"}) - self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r) - self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r) + assert re.search('var _sf_async_config={.*"uid": "12345".*};', r) + assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r) def test_top_node(self): r = ChartbeatTopNode().render(Context({ 'chartbeat_domain': "test.com", })) - self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r) + assert 'var _sf_startpt=(new Date()).getTime()' in r def test_bottom_node(self): r = ChartbeatBottomNode().render(Context({ 'chartbeat_domain': "test.com", })) - self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r) - self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r) + assert re.search('var _sf_async_config={.*"uid": "12345".*};', r) + assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r) @override_settings(CHARTBEAT_USER_ID=None) def test_no_user_id(self): @@ -95,6 +95,5 @@ class ChartbeatTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = ChartbeatBottomNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_clickmap.py b/tests/unit/test_tag_clickmap.py index 588a209..180f0af 100644 --- a/tests/unit/test_tag_clickmap.py +++ b/tests/unit/test_tag_clickmap.py @@ -19,11 +19,11 @@ class ClickmapTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('clickmap', 'clickmap') - self.assertTrue("tracker: '12345ABC', version:'2'};" in r, r) + assert "tracker: '12345ABC', version:'2'};" in r def test_node(self): r = ClickmapNode().render(Context({})) - self.assertTrue("tracker: '12345ABC', version:'2'};" in r, r) + assert "tracker: '12345ABC', version:'2'};" in r @override_settings(CLICKMAP_TRACKER_ID=None) def test_no_site_id(self): @@ -39,6 +39,5 @@ class ClickmapTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = ClickmapNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_clicky.py b/tests/unit/test_tag_clicky.py index b830e57..837b65f 100644 --- a/tests/unit/test_tag_clicky.py +++ b/tests/unit/test_tag_clicky.py @@ -22,13 +22,13 @@ class ClickyTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('clicky', 'clicky') - self.assertTrue('clicky_site_ids.push(12345678);' in r, r) - self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r, r) + assert 'clicky_site_ids.push(12345678);' in r + assert 'src="//in.getclicky.com/12345678ns.gif"' in r def test_node(self): r = ClickyNode().render(Context({})) - self.assertTrue('clicky_site_ids.push(12345678);' in r, r) - self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r, r) + assert 'clicky_site_ids.push(12345678);' in r + assert 'src="//in.getclicky.com/12345678ns.gif"' in r @override_settings(CLICKY_SITE_ID=None) def test_no_site_id(self): @@ -41,21 +41,19 @@ class ClickyTagTestCase(TagTestCase): @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = ClickyNode().render(Context({'user': User(username='test')})) - self.assertTrue('var clicky_custom = {"session": {"username": "test"}};' in r, r) + assert 'var clicky_custom = {"session": {"username": "test"}};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = ClickyNode().render(Context({'user': AnonymousUser()})) - self.assertFalse('var clicky_custom = {"session": {"username":' in r, r) + assert 'var clicky_custom = {"session": {"username":' not in r def test_custom(self): r = ClickyNode().render(Context({ 'clicky_var1': 'val1', 'clicky_var2': 'val2', })) - self.assertTrue( - re.search(r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r), - r) + assert re.search(r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -63,6 +61,5 @@ class ClickyTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = ClickyNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_crazy_egg.py b/tests/unit/test_tag_crazy_egg.py index 396591e..521066a 100644 --- a/tests/unit/test_tag_crazy_egg.py +++ b/tests/unit/test_tag_crazy_egg.py @@ -19,11 +19,11 @@ class CrazyEggTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('crazy_egg', 'crazy_egg') - self.assertTrue('/1234/5678.js' in r, r) + assert '/1234/5678.js' in r def test_node(self): r = CrazyEggNode().render(Context()) - self.assertTrue('/1234/5678.js' in r, r) + assert '/1234/5678.js' in r @override_settings(CRAZY_EGG_ACCOUNT_NUMBER=None) def test_no_account_number(self): @@ -36,8 +36,8 @@ class CrazyEggTagTestCase(TagTestCase): def test_uservars(self): context = Context({'crazy_egg_var1': 'foo', 'crazy_egg_var2': 'bar'}) r = CrazyEggNode().render(context) - self.assertTrue("CE2.set(1, 'foo');" in r, r) - self.assertTrue("CE2.set(2, 'bar');" in r, r) + assert "CE2.set(1, 'foo');" in r + assert "CE2.set(2, 'bar');" in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -45,6 +45,5 @@ class CrazyEggTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = CrazyEggNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') From f0e7ebe7313e96e8bb95383378635263f4ec3a44 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 9 Dec 2020 21:14:39 +0000 Subject: [PATCH 044/103] Converted remaining tests to Pytest style --- tests/unit/test_tag_chartbeat.py | 8 +- tests/unit/test_tag_clickmap.py | 8 +- tests/unit/test_tag_clicky.py | 8 +- tests/unit/test_tag_crazy_egg.py | 7 +- tests/unit/test_tag_facebook_pixel.py | 52 ++++---- tests/unit/test_tag_gauges.py | 19 +-- tests/unit/test_tag_google_analytics.py | 88 +++++++------ tests/unit/test_tag_google_analytics_gtag.py | 64 ++++----- tests/unit/test_tag_google_analytics_js.py | 88 +++++++------ tests/unit/test_tag_gosquared.py | 23 ++-- tests/unit/test_tag_hotjar.py | 26 ++-- tests/unit/test_tag_hubspot.py | 24 ++-- tests/unit/test_tag_intercom.py | 53 ++++---- tests/unit/test_tag_kiss_insights.py | 24 ++-- tests/unit/test_tag_kiss_metrics.py | 35 ++--- tests/unit/test_tag_luckyorange.py | 26 ++-- tests/unit/test_tag_matomo.py | 70 +++++----- tests/unit/test_tag_mixpanel.py | 28 ++-- tests/unit/test_tag_olark.py | 32 ++--- tests/unit/test_tag_optimizely.py | 23 ++-- tests/unit/test_tag_performable.py | 29 ++--- tests/unit/test_tag_piwik.py | 71 +++++----- tests/unit/test_tag_rating_mailru.py | 17 ++- tests/unit/test_tag_snapengage.py | 129 +++++++------------ tests/unit/test_tag_spring_metrics.py | 25 ++-- tests/unit/test_tag_uservoice.py | 32 +++-- tests/unit/test_tag_woopra.py | 34 ++--- tests/unit/test_tag_yandex_metrica.py | 17 ++- tests/unit/test_utils.py | 28 ++-- 29 files changed, 564 insertions(+), 524 deletions(-) diff --git a/tests/unit/test_tag_chartbeat.py b/tests/unit/test_tag_chartbeat.py index 01363bb..c5e6493 100644 --- a/tests/unit/test_tag_chartbeat.py +++ b/tests/unit/test_tag_chartbeat.py @@ -14,6 +14,8 @@ from analytical.templatetags.chartbeat import ChartbeatTopNode, \ from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(CHARTBEAT_USER_ID='12345') class ChartbeatTagTestCaseNoSites(TestCase): @@ -83,11 +85,13 @@ class ChartbeatTagTestCase(TagTestCase): @override_settings(CHARTBEAT_USER_ID=None) def test_no_user_id(self): - self.assertRaises(AnalyticalException, ChartbeatBottomNode) + with pytest.raises(AnalyticalException): + ChartbeatBottomNode() @override_settings(CHARTBEAT_USER_ID='123abc') def test_wrong_user_id(self): - self.assertRaises(AnalyticalException, ChartbeatBottomNode) + with pytest.raises(AnalyticalException): + ChartbeatBottomNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): diff --git a/tests/unit/test_tag_clickmap.py b/tests/unit/test_tag_clickmap.py index 180f0af..b6049eb 100644 --- a/tests/unit/test_tag_clickmap.py +++ b/tests/unit/test_tag_clickmap.py @@ -10,6 +10,8 @@ from analytical.templatetags.clickmap import ClickmapNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(CLICKMAP_TRACKER_ID='12345ABC') class ClickmapTagTestCase(TagTestCase): @@ -27,11 +29,13 @@ class ClickmapTagTestCase(TagTestCase): @override_settings(CLICKMAP_TRACKER_ID=None) def test_no_site_id(self): - self.assertRaises(AnalyticalException, ClickmapNode) + with pytest.raises(AnalyticalException): + ClickmapNode() @override_settings(CLICKMAP_TRACKER_ID='ab#c') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, ClickmapNode) + with pytest.raises(AnalyticalException): + ClickmapNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): diff --git a/tests/unit/test_tag_clicky.py b/tests/unit/test_tag_clicky.py index 837b65f..2be16c8 100644 --- a/tests/unit/test_tag_clicky.py +++ b/tests/unit/test_tag_clicky.py @@ -13,6 +13,8 @@ from analytical.templatetags.clicky import ClickyNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(CLICKY_SITE_ID='12345678') class ClickyTagTestCase(TagTestCase): @@ -32,11 +34,13 @@ class ClickyTagTestCase(TagTestCase): @override_settings(CLICKY_SITE_ID=None) def test_no_site_id(self): - self.assertRaises(AnalyticalException, ClickyNode) + with pytest.raises(AnalyticalException): + ClickyNode() @override_settings(CLICKY_SITE_ID='123abc') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, ClickyNode) + with pytest.raises(AnalyticalException): + ClickyNode() @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): diff --git a/tests/unit/test_tag_crazy_egg.py b/tests/unit/test_tag_crazy_egg.py index 521066a..f2cab47 100644 --- a/tests/unit/test_tag_crazy_egg.py +++ b/tests/unit/test_tag_crazy_egg.py @@ -5,6 +5,7 @@ Tests for the Crazy Egg template tags and filters. from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings +import pytest from analytical.templatetags.crazy_egg import CrazyEggNode from utils import TagTestCase @@ -27,11 +28,13 @@ class CrazyEggTagTestCase(TagTestCase): @override_settings(CRAZY_EGG_ACCOUNT_NUMBER=None) def test_no_account_number(self): - self.assertRaises(AnalyticalException, CrazyEggNode) + with pytest.raises(AnalyticalException): + CrazyEggNode() @override_settings(CRAZY_EGG_ACCOUNT_NUMBER='123abc') def test_wrong_account_number(self): - self.assertRaises(AnalyticalException, CrazyEggNode) + with pytest.raises(AnalyticalException): + CrazyEggNode() def test_uservars(self): context = Context({'crazy_egg_var1': 'foo', 'crazy_egg_var2': 'bar'}) diff --git a/tests/unit/test_tag_facebook_pixel.py b/tests/unit/test_tag_facebook_pixel.py index 1bbdfad..2020257 100644 --- a/tests/unit/test_tag_facebook_pixel.py +++ b/tests/unit/test_tag_facebook_pixel.py @@ -10,6 +10,7 @@ from analytical.templatetags.facebook_pixel import FacebookPixelHeadNode, Facebo from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest expected_head_html = """\ -""", self.render_tag('gauges', 'gauges')) +""" def test_node(self): - self.assertEqual( - """ + assert GaugesNode().render(Context()) == """ -""", GaugesNode().render(Context())) +""" @override_settings(GAUGES_SITE_ID=None) def test_no_account_number(self): - self.assertRaises(AnalyticalException, GaugesNode) + with pytest.raises(AnalyticalException): + GaugesNode() @override_settings(GAUGES_SITE_ID='123abQ') def test_wrong_account_number(self): @@ -66,6 +68,5 @@ class GaugesTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GaugesNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_google_analytics.py b/tests/unit/test_tag_google_analytics.py index da73996..28488c7 100644 --- a/tests/unit/test_tag_google_analytics.py +++ b/tests/unit/test_tag_google_analytics.py @@ -12,6 +12,8 @@ from analytical.templatetags.google_analytics import GoogleAnalyticsNode, \ from utils import TestCase, TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN) @@ -22,36 +24,38 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('google_analytics', 'google_analytics') - self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r) - self.assertTrue("_gaq.push(['_trackPageview']);" in r, r) + assert "_gaq.push(['_setAccount', 'UA-123456-7']);" in r + assert "_gaq.push(['_trackPageview']);" in r def test_node(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r) - self.assertTrue("_gaq.push(['_trackPageview']);" in r, r) + assert "_gaq.push(['_setAccount', 'UA-123456-7']);" in r + assert "_gaq.push(['_trackPageview']);" in r @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID=None) def test_no_property_id(self): - self.assertRaises(AnalyticalException, GoogleAnalyticsNode) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode() @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='wrong') def test_wrong_property_id(self): - self.assertRaises(AnalyticalException, GoogleAnalyticsNode) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode() @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_subdomains(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r) - self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r) + assert "_gaq.push(['_setDomainName', 'example.com']);" in r + assert "_gaq.push(['_setAllowHash', false]);" in r @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_domains(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r) - self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r) - self.assertTrue("_gaq.push(['_setAllowLinker', true]);" in r, r) + assert "_gaq.push(['_setDomainName', 'example.com']);" in r + assert "_gaq.push(['_setAllowHash', false]);" in r + assert "_gaq.push(['_setAllowLinker', true]);" in r def test_custom_vars(self): context = Context({ @@ -61,23 +65,23 @@ class GoogleAnalyticsTagTestCase(TagTestCase): 'google_analytics_var5': ('test5', 'qux', SCOPE_PAGE), }) r = GoogleAnalyticsNode().render(context) - self.assertTrue("_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);" in r, r) - self.assertTrue("_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);" in r, r) - self.assertTrue("_gaq.push(['_setCustomVar', 4, 'test4', 'baz', 2]);" in r, r) - self.assertTrue("_gaq.push(['_setCustomVar', 5, 'test5', 'qux', 3]);" in r, r) + assert "_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);" in r + assert "_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);" in r + assert "_gaq.push(['_setCustomVar', 4, 'test4', 'baz', 2]);" in r + assert "_gaq.push(['_setCustomVar', 5, 'test5', 'qux', 3]);" in r @override_settings(GOOGLE_ANALYTICS_SITE_SPEED=True) def test_track_page_load_time(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_trackPageLoadTime']);" in r, r) + assert "_gaq.push(['_trackPageLoadTime']);" in r def test_display_advertising(self): with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=False): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("google-analytics.com/ga.js" in r, r) + assert "google-analytics.com/ga.js" in r with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("stats.g.doubleclick.net/dc.js" in r, r) + assert "stats.g.doubleclick.net/dc.js" in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -85,90 +89,95 @@ class GoogleAnalyticsTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True) def test_anonymize_ip(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_gat._anonymizeIp']);" in r, r) - self.assertTrue(r.index('_gat._anonymizeIp') < r.index('_trackPageview'), r) + assert "_gaq.push(['_gat._anonymizeIp']);" in r + assert r.index('_gat._anonymizeIp') < r.index('_trackPageview') @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=False) def test_anonymize_ip_not_present(self): r = GoogleAnalyticsNode().render(Context()) - self.assertFalse("_gaq.push(['_gat._anonymizeIp']);" in r, r) + assert "_gaq.push(['_gat._anonymizeIp']);" not in r @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=0.0) def test_set_sample_rate_min(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setSampleRate', '0.00']);" in r, r) + assert "_gaq.push(['_setSampleRate', '0.00']);" in r @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE='100.00') def test_set_sample_rate_max(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setSampleRate', '100.00']);" in r, r) + assert "_gaq.push(['_setSampleRate', '100.00']);" in r @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=-1) def test_exception_whenset_sample_rate_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=101) def test_exception_when_set_sample_rate_too_large(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0) def test_set_site_speed_sample_rate_min(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setSiteSpeedSampleRate', '0.00']);" in r, r) + assert "_gaq.push(['_setSiteSpeedSampleRate', '0.00']);" in r @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00') def test_set_site_speed_sample_rate_max(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setSiteSpeedSampleRate', '100.00']);" in r, r) + assert "_gaq.push(['_setSiteSpeedSampleRate', '100.00']);" in r @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1) def test_exception_whenset_site_speed_sample_rate_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=101) def test_exception_when_set_site_speed_sample_rate_too_large(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT=0) def test_set_session_cookie_timeout_min(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setSessionCookieTimeout', '0']);" in r, r) + assert "_gaq.push(['_setSessionCookieTimeout', '0']);" in r @override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT='10000') def test_set_session_cookie_timeout_as_string(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setSessionCookieTimeout', '10000']);" in r, r) + assert "_gaq.push(['_setSessionCookieTimeout', '10000']);" in r @override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT=-1) def test_exception_when_set_session_cookie_timeout_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) @override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT=0) def test_set_visitor_cookie_timeout_min(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setVisitorCookieTimeout', '0']);" in r, r) + assert "_gaq.push(['_setVisitorCookieTimeout', '0']);" in r @override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT='10000') def test_set_visitor_cookie_timeout_as_string(self): r = GoogleAnalyticsNode().render(Context()) - self.assertTrue("_gaq.push(['_setVisitorCookieTimeout', '10000']);" in r, r) + assert "_gaq.push(['_setVisitorCookieTimeout', '10000']);" in r @override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT=-1) def test_exception_when_set_visitor_cookie_timeout_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', @@ -178,4 +187,5 @@ class GoogleAnalyticsTagTestCase(TagTestCase): class NoDomainTestCase(TestCase): def test_exception_without_domain(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsNode().render(context) diff --git a/tests/unit/test_tag_google_analytics_gtag.py b/tests/unit/test_tag_google_analytics_gtag.py index c81368f..57f4531 100644 --- a/tests/unit/test_tag_google_analytics_gtag.py +++ b/tests/unit/test_tag_google_analytics_gtag.py @@ -11,6 +11,8 @@ from analytical.templatetags.google_analytics_gtag import GoogleAnalyticsGTagNod from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='UA-123456-7') class GoogleAnalyticsTagTestCase(TagTestCase): @@ -20,27 +22,29 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') - self.assertTrue( + assert ( '' - in r, r) - self.assertTrue("gtag('js', new Date());" in r, r) - self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) + ) in r + assert "gtag('js', new Date());" in r + assert "gtag('config', 'UA-123456-7');" in r def test_node(self): r = GoogleAnalyticsGTagNode().render(Context()) - self.assertTrue( + assert ( '' - in r, r) - self.assertTrue("gtag('js', new Date());" in r, r) - self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) + ) in r + assert "gtag('js', new Date());" in r + assert "gtag('config', 'UA-123456-7');" in r @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID=None) def test_no_property_id(self): - self.assertRaises(AnalyticalException, GoogleAnalyticsGTagNode) + with pytest.raises(AnalyticalException): + GoogleAnalyticsGTagNode() @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='wrong') def test_wrong_property_id(self): - self.assertRaises(AnalyticalException, GoogleAnalyticsGTagNode) + with pytest.raises(AnalyticalException): + GoogleAnalyticsGTagNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -48,41 +52,37 @@ class GoogleAnalyticsTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsGTagNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = GoogleAnalyticsGTagNode().render(Context({'user': User(username='test')})) - self.assertTrue("gtag('set', {'user_id': 'test'});" in r, r) + assert "gtag('set', {'user_id': 'test'});" in r @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='G-12345678') def test_tag_with_measurement_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') - self.assertTrue( - ('') - in r, r) - self.assertTrue("gtag('js', new Date());" in r, r) - self.assertTrue("gtag('config', 'G-12345678');" in r, r) + assert ( + '' + ) in r + assert "gtag('js', new Date());" 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): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') - self.assertTrue( - ('') - in r, r) - self.assertTrue("gtag('js', new Date());" in r, r) - self.assertTrue("gtag('config', 'AW-1234567890');" in r, r) + assert ( + '') - in r, r) - self.assertTrue("gtag('js', new Date());" in r, r) - self.assertTrue("gtag('config', 'DC-12345678');" in r, r) + assert ( + '' + ) in r + assert "gtag('js', new Date());" in r + assert "gtag('config', 'DC-12345678');" in r diff --git a/tests/unit/test_tag_google_analytics_js.py b/tests/unit/test_tag_google_analytics_js.py index 1009fbb..1856e4a 100644 --- a/tests/unit/test_tag_google_analytics_js.py +++ b/tests/unit/test_tag_google_analytics_js.py @@ -11,6 +11,8 @@ from analytical.templatetags.google_analytics_js import GoogleAnalyticsJsNode, \ from utils import TestCase, TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN) @@ -21,44 +23,45 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('google_analytics_js', 'google_analytics_js') - self.assertTrue("""(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + assert """(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r, r) - self.assertTrue("ga('create', 'UA-123456-7', 'auto', {});" in r, r) - self.assertTrue("ga('send', 'pageview');" in r, r) +})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r + assert "ga('create', 'UA-123456-7', 'auto', {});" in r + assert "ga('send', 'pageview');" in r def test_node(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("""(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + assert """(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r, r) - self.assertTrue("ga('create', 'UA-123456-7', 'auto', {});" in r, r) - self.assertTrue("ga('send', 'pageview');" in r, r) +})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r + assert "ga('create', 'UA-123456-7', 'auto', {});" in r + assert "ga('send', 'pageview');" in r @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID=None) def test_no_property_id(self): - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode() @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='wrong') def test_wrong_property_id(self): - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode() @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_subdomains(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue( - """ga('create', 'UA-123456-7', 'auto', {"legacyCookieDomain": "example.com"}""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"legacyCookieDomain": "example.com"}""" in r @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_domains(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("ga('create', 'UA-123456-7', 'auto', {" in r, r) - self.assertTrue('"legacyCookieDomain": "example.com"' in r, r) - self.assertTrue('"allowLinker\": true' in r, r) + assert "ga('create', 'UA-123456-7', 'auto', {" in r + assert '"legacyCookieDomain": "example.com"' in r + assert '"allowLinker\": true' in r def test_custom_vars(self): context = Context({ @@ -68,17 +71,17 @@ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 'google_analytics_var5': ('test5', 2.2), }) r = GoogleAnalyticsJsNode().render(context) - self.assertTrue("ga('set', 'test1', 'foo');" in r, r) - self.assertTrue("ga('set', 'test2', 'bar');" in r, r) - self.assertTrue("ga('set', 'test4', 1);" in r, r) - self.assertTrue("ga('set', 'test5', 2.2);" in r, r) + assert "ga('set', 'test1', 'foo');" in r + assert "ga('set', 'test2', 'bar');" in r + assert "ga('set', 'test4', 1);" in r + assert "ga('set', 'test5', 2.2);" in r def test_display_advertising(self): with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {}); + assert """ga('create', 'UA-123456-7', 'auto', {}); ga('require', 'displayfeatures'); -ga('send', 'pageview');""" in r, r) +ga('send', 'pageview');""" in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -86,77 +89,79 @@ ga('send', 'pageview');""" in r, r) req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsJsNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith( + '') @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True) def test_anonymize_ip(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("ga('set', 'anonymizeIp', true);" in r, r) + assert "ga('set', 'anonymizeIp', true);" in r @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=False) def test_anonymize_ip_not_present(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertFalse("ga('set', 'anonymizeIp', true);" in r, r) + assert "ga('set', 'anonymizeIp', true);" not in r @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=0.0) def test_set_sample_rate_min(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"sampleRate": 0});""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"sampleRate": 0});""" in r @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE='100.00') def test_set_sample_rate_max(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"sampleRate": 100});""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"sampleRate": 100});""" in r @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=-1) def test_exception_whenset_sample_rate_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=101) def test_exception_when_set_sample_rate_too_large(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0) def test_set_site_speed_sample_rate_min(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue( - """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 0});""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 0});""" in r @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00') def test_set_site_speed_sample_rate_max(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue( - """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 100});""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 100});""" in r @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1) def test_exception_whenset_site_speed_sample_rate_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode().render(context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=101) def test_exception_when_set_site_speed_sample_rate_too_large(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode().render(context) @override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION=0) def test_set_cookie_expiration_min(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 0});""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 0});""" in r @override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION='10000') def test_set_cookie_expiration_as_string(self): r = GoogleAnalyticsJsNode().render(Context()) - self.assertTrue( - """ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 10000});""" in r, r) + assert """ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 10000});""" in r @override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION=-1) def test_exception_when_set_cookie_expiration_too_small(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode().render(context) @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', @@ -166,4 +171,5 @@ ga('send', 'pageview');""" in r, r) class NoDomainTestCase(TestCase): def test_exception_without_domain(self): context = Context() - self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) + with pytest.raises(AnalyticalException): + GoogleAnalyticsJsNode().render(context) diff --git a/tests/unit/test_tag_gosquared.py b/tests/unit/test_tag_gosquared.py index 444420f..0fa008d 100644 --- a/tests/unit/test_tag_gosquared.py +++ b/tests/unit/test_tag_gosquared.py @@ -11,6 +11,8 @@ from analytical.templatetags.gosquared import GoSquaredNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(GOSQUARED_SITE_TOKEN='ABC-123456-D') class GoSquaredTagTestCase(TagTestCase): @@ -20,26 +22,28 @@ class GoSquaredTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('gosquared', 'gosquared') - self.assertTrue('GoSquared.acct = "ABC-123456-D";' in r, r) + assert 'GoSquared.acct = "ABC-123456-D";' in r def test_node(self): r = GoSquaredNode().render(Context({})) - self.assertTrue('GoSquared.acct = "ABC-123456-D";' in r, r) + assert 'GoSquared.acct = "ABC-123456-D";' in r @override_settings(GOSQUARED_SITE_TOKEN=None) def test_no_token(self): - self.assertRaises(AnalyticalException, GoSquaredNode) + with pytest.raises(AnalyticalException): + GoSquaredNode() @override_settings(GOSQUARED_SITE_TOKEN='this is not a token') def test_wrong_token(self): - self.assertRaises(AnalyticalException, GoSquaredNode) + with pytest.raises(AnalyticalException): + GoSquaredNode() @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_auto_identify(self): r = GoSquaredNode().render(Context({ 'user': User(username='test', first_name='Test', last_name='User'), })) - self.assertTrue('GoSquared.UserName = "Test User";' in r, r) + assert 'GoSquared.UserName = "Test User";' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_manual_identify(self): @@ -47,12 +51,12 @@ class GoSquaredTagTestCase(TagTestCase): 'user': User(username='test', first_name='Test', last_name='User'), 'gosquared_identity': 'test_identity', })) - self.assertTrue('GoSquared.UserName = "test_identity";' in r, r) + assert 'GoSquared.UserName = "test_identity";' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = GoSquaredNode().render(Context({'user': AnonymousUser()})) - self.assertFalse('GoSquared.UserName = ' in r, r) + assert 'GoSquared.UserName = ' not in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -60,6 +64,5 @@ class GoSquaredTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoSquaredNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_hotjar.py b/tests/unit/test_tag_hotjar.py index d5978b8..3c0ff6b 100644 --- a/tests/unit/test_tag_hotjar.py +++ b/tests/unit/test_tag_hotjar.py @@ -10,6 +10,7 @@ from analytical.templatetags.hotjar import HotjarNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest expected_html = """\ -""" % {'user_id': user.pk}, rendered_tag) # noqa +""" % {'user_id': user.pk} # noqa @override_settings(INTERCOM_APP_ID=None) def test_no_account_number(self): - self.assertRaises(AnalyticalException, IntercomNode) + with pytest.raises(AnalyticalException): + IntercomNode() @override_settings(INTERCOM_APP_ID='123abQ') def test_wrong_account_number(self): - self.assertRaises(AnalyticalException, IntercomNode) + with pytest.raises(AnalyticalException): + IntercomNode() def test_identify_name_email_and_created_at(self): now = datetime.datetime(2014, 4, 9, 15, 15, 0) @@ -62,18 +66,17 @@ class IntercomTagTestCase(TagTestCase): r = IntercomNode().render(Context({ 'user': user, })) - self.assertTrue('window.intercomSettings = {' - '"app_id": "abc123xyz", "created_at": 1397074500, ' - '"email": "test@example.com", "name": "Firstname Lastname", ' - '"user_id": %(user_id)s' - '};' % {'user_id': user.pk} in r, msg=r) + assert ( + 'window.intercomSettings = {"app_id": "abc123xyz", "created_at": 1397074500, ' + f'"email": "test@example.com", "name": "Firstname Lastname", "user_id": {user.pk}}};' + ) in r def test_custom(self): r = IntercomNode().render(Context({ 'intercom_var1': 'val1', 'intercom_var2': 'val2' })) - self.assertTrue('var1": "val1", "var2": "val2"' in r) + assert 'var1": "val1", "var2": "val2"' in r def test_identify_name_and_email(self): r = IntercomNode().render(Context({ @@ -83,25 +86,25 @@ class IntercomTagTestCase(TagTestCase): last_name='Lastname', email="test@example.com"), })) - self.assertTrue('"email": "test@example.com", "name": "Firstname Lastname"' in r) + assert '"email": "test@example.com", "name": "Firstname Lastname"' in r def test_identify_username_no_email(self): r = IntercomNode().render(Context({'user': User(username='test')})) - self.assertTrue('"name": "test"' in r, r) + assert '"name": "test"' in r, r def test_no_identify_when_explicit_name(self): r = IntercomNode().render(Context({ 'intercom_name': 'explicit', 'user': User(username='implicit'), })) - self.assertTrue('"name": "explicit"' in r, r) + assert '"name": "explicit"' in r, r def test_no_identify_when_explicit_email(self): r = IntercomNode().render(Context({ 'intercom_email': 'explicit', 'user': User(username='implicit'), })) - self.assertTrue('"email": "explicit"' in r, r) + assert '"email": "explicit"' in r, r @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__without_user_details(self): @@ -109,9 +112,7 @@ class IntercomTagTestCase(TagTestCase): No `user_hash` without `user_id` or `email`. """ attrs = IntercomNode()._get_custom_attrs(Context()) - self.assertEqual({ - 'created_at': None, - }, attrs) + assert {'created_at': None} == attrs @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__with_user(self): @@ -122,13 +123,13 @@ class IntercomTagTestCase(TagTestCase): email='test@example.com', ) # type: User attrs = IntercomNode()._get_custom_attrs(Context({'user': user})) - self.assertEqual({ + assert attrs == { 'created_at': int(user.date_joined.timestamp()), 'email': 'test@example.com', 'name': '', 'user_hash': intercom_user_hash(str(user.pk)), 'user_id': user.pk, - }, attrs) + } @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__with_explicit_user_id(self): @@ -139,13 +140,13 @@ class IntercomTagTestCase(TagTestCase): 'intercom_email': 'test@example.com', 'intercom_user_id': '5', })) - self.assertEqual({ + assert attrs == { 'created_at': None, 'email': 'test@example.com', # HMAC for user_id: 'user_hash': 'd3123a7052b42272d9b520235008c248a5aff3221cc0c530b754702ad91ab102', 'user_id': '5', - }, attrs) + } @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__with_explicit_email(self): @@ -155,12 +156,12 @@ class IntercomTagTestCase(TagTestCase): attrs = IntercomNode()._get_custom_attrs(Context({ 'intercom_email': 'test@example.com', })) - self.assertEqual({ + assert attrs == { 'created_at': None, 'email': 'test@example.com', # HMAC for email: 'user_hash': '49e43229ee99dca2565241719b8341b04e71dd4de0628f991b5bea30a526e153', - }, attrs) + } @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -168,5 +169,5 @@ class IntercomTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = IntercomNode().render(context) - self.assertTrue(r.startswith(''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_kiss_insights.py b/tests/unit/test_tag_kiss_insights.py index dc627d1..a2e6d18 100644 --- a/tests/unit/test_tag_kiss_insights.py +++ b/tests/unit/test_tag_kiss_insights.py @@ -10,6 +10,8 @@ from analytical.templatetags.kiss_insights import KissInsightsNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER='12345', KISS_INSIGHTS_SITE_CODE='abc') class KissInsightsTagTestCase(TagTestCase): @@ -19,38 +21,42 @@ class KissInsightsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('kiss_insights', 'kiss_insights') - self.assertTrue("//s3.amazonaws.com/ki.js/12345/abc.js" in r, r) + assert "//s3.amazonaws.com/ki.js/12345/abc.js" in r def test_node(self): r = KissInsightsNode().render(Context()) - self.assertTrue("//s3.amazonaws.com/ki.js/12345/abc.js" in r, r) + assert "//s3.amazonaws.com/ki.js/12345/abc.js" in r @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER=None) def test_no_account_number(self): - self.assertRaises(AnalyticalException, KissInsightsNode) + with pytest.raises(AnalyticalException): + KissInsightsNode() @override_settings(KISS_INSIGHTS_SITE_CODE=None) def test_no_site_code(self): - self.assertRaises(AnalyticalException, KissInsightsNode) + with pytest.raises(AnalyticalException): + KissInsightsNode() @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER='abcde') def test_wrong_account_number(self): - self.assertRaises(AnalyticalException, KissInsightsNode) + with pytest.raises(AnalyticalException): + KissInsightsNode() @override_settings(KISS_INSIGHTS_SITE_CODE='abc def') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, KissInsightsNode) + with pytest.raises(AnalyticalException): + KissInsightsNode() @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = KissInsightsNode().render(Context({'user': User(username='test')})) - self.assertTrue("_kiq.push(['identify', 'test']);" in r, r) + assert "_kiq.push(['identify', 'test']);" in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = KissInsightsNode().render(Context({'user': AnonymousUser()})) - self.assertFalse("_kiq.push(['identify', " in r, r) + assert "_kiq.push(['identify', " not in r def test_show_survey(self): r = KissInsightsNode().render(Context({'kiss_insights_show_survey': 1234})) - self.assertTrue("_kiq.push(['showSurvey', 1234]);" in r, r) + assert "_kiq.push(['showSurvey', 1234]);" in r diff --git a/tests/unit/test_tag_kiss_metrics.py b/tests/unit/test_tag_kiss_metrics.py index d3e885f..bb30b29 100644 --- a/tests/unit/test_tag_kiss_metrics.py +++ b/tests/unit/test_tag_kiss_metrics.py @@ -11,6 +11,8 @@ from analytical.templatetags.kiss_metrics import KissMetricsNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef01234567') class KissMetricsTagTestCase(TagTestCase): @@ -20,55 +22,55 @@ class KissMetricsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('kiss_metrics', 'kiss_metrics') - self.assertTrue("//doug1izaerwt3.cloudfront.net/" - "0123456789abcdef0123456789abcdef01234567.1.js" in r, r) + assert "//doug1izaerwt3.cloudfront.net/0123456789abcdef0123456789abcdef01234567.1.js" in r def test_node(self): r = KissMetricsNode().render(Context()) - self.assertTrue("//doug1izaerwt3.cloudfront.net/" - "0123456789abcdef0123456789abcdef01234567.1.js" in r, r) + assert "//doug1izaerwt3.cloudfront.net/0123456789abcdef0123456789abcdef01234567.1.js" in r @override_settings(KISS_METRICS_API_KEY=None) def test_no_api_key(self): - self.assertRaises(AnalyticalException, KissMetricsNode) + with pytest.raises(AnalyticalException): + KissMetricsNode() @override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef0123456') def test_api_key_too_short(self): - self.assertRaises(AnalyticalException, KissMetricsNode) + with pytest.raises(AnalyticalException): + KissMetricsNode() @override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef012345678') def test_api_key_too_long(self): - self.assertRaises(AnalyticalException, KissMetricsNode) + with pytest.raises(AnalyticalException): + KissMetricsNode() @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = KissMetricsNode().render(Context({'user': User(username='test')})) - self.assertTrue("_kmq.push(['identify', 'test']);" in r, r) + assert "_kmq.push(['identify', 'test']);" in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = KissMetricsNode().render(Context({'user': AnonymousUser()})) - self.assertFalse("_kmq.push(['identify', " in r, r) + assert "_kmq.push(['identify', " not in r def test_event(self): r = KissMetricsNode().render(Context({ 'kiss_metrics_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}), })) - self.assertTrue("_kmq.push(['record', 'test_event', " - '{"prop1": "val1", "prop2": "val2"}]);' in r, r) + assert "_kmq.push(['record', 'test_event', " + '{"prop1": "val1", "prop2": "val2"}]);' in r def test_property(self): r = KissMetricsNode().render(Context({ 'kiss_metrics_properties': {'prop1': 'val1', 'prop2': 'val2'}, })) - self.assertTrue("_kmq.push([\'set\', " - '{"prop1": "val1", "prop2": "val2"}]);' in r, r) + assert '_kmq.push([\'set\', {"prop1": "val1", "prop2": "val2"}]);' in r def test_alias(self): r = KissMetricsNode().render(Context({ 'kiss_metrics_alias': {'test': 'test_alias'}, })) - self.assertTrue("_kmq.push(['alias', 'test', 'test_alias']);" in r, r) + assert "_kmq.push(['alias', 'test', 'test_alias']);" in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -76,6 +78,5 @@ class KissMetricsTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = KissMetricsNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_luckyorange.py b/tests/unit/test_tag_luckyorange.py index a4e082b..26538fd 100644 --- a/tests/unit/test_tag_luckyorange.py +++ b/tests/unit/test_tag_luckyorange.py @@ -10,6 +10,7 @@ from analytical.templatetags.luckyorange import LuckyOrangeNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest expected_html = """\ ', - self.render_tag('optimizely', 'optimizely')) + expected = '' + assert self.render_tag('optimizely', 'optimizely') == expected def test_node(self): - self.assertEqual( - '', - OptimizelyNode().render(Context())) + expected = '' + assert OptimizelyNode().render(Context()) == expected @override_settings(OPTIMIZELY_ACCOUNT_NUMBER=None) def test_no_account_number(self): - self.assertRaises(AnalyticalException, OptimizelyNode) + with pytest.raises(AnalyticalException): + OptimizelyNode() @override_settings(OPTIMIZELY_ACCOUNT_NUMBER='123abc') def test_wrong_account_number(self): - self.assertRaises(AnalyticalException, OptimizelyNode) + with pytest.raises(AnalyticalException): + OptimizelyNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -41,6 +43,5 @@ class OptimizelyTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = OptimizelyNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_performable.py b/tests/unit/test_tag_performable.py index 24e20c8..c1e83db 100644 --- a/tests/unit/test_tag_performable.py +++ b/tests/unit/test_tag_performable.py @@ -11,6 +11,8 @@ from analytical.templatetags.performable import PerformableNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(PERFORMABLE_API_KEY='123ABC') class PerformableTagTestCase(TagTestCase): @@ -20,19 +22,21 @@ class PerformableTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('performable', 'performable') - self.assertTrue('/performable/pax/123ABC.js' in r, r) + assert '/performable/pax/123ABC.js' in r def test_node(self): r = PerformableNode().render(Context()) - self.assertTrue('/performable/pax/123ABC.js' in r, r) + assert '/performable/pax/123ABC.js' in r @override_settings(PERFORMABLE_API_KEY=None) def test_no_api_key(self): - self.assertRaises(AnalyticalException, PerformableNode) + with pytest.raises(AnalyticalException): + PerformableNode() @override_settings(PERFORMABLE_API_KEY='123 ABC') def test_wrong_account_number(self): - self.assertRaises(AnalyticalException, PerformableNode) + with pytest.raises(AnalyticalException): + PerformableNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -40,19 +44,18 @@ class PerformableTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = PerformableNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = PerformableNode().render(Context({'user': User(username='test')})) - self.assertTrue('_paq.push(["identify", {identity: "test"}]);' in r, r) + assert '_paq.push(["identify", {identity: "test"}]);' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = PerformableNode().render(Context({'user': AnonymousUser()})) - self.assertFalse('_paq.push(["identify", ' in r, r) + assert '_paq.push(["identify", ' not in r class PerformableEmbedTagTestCase(TagTestCase): @@ -63,9 +66,5 @@ class PerformableEmbedTagTestCase(TagTestCase): def test_tag(self): domain = 'example.com' page = 'test' - tag = self.render_tag( - 'performable', 'performable_embed "%s" "%s"' % (domain, page) - ) - self.assertIn( - "$f.initialize({'host': 'example.com', 'page': 'test'});", tag - ) + tag = self.render_tag('performable', f'performable_embed "{domain}" "{page}"') + assert "$f.initialize({'host': 'example.com', 'page': 'test'});" in tag diff --git a/tests/unit/test_tag_piwik.py b/tests/unit/test_tag_piwik.py index 38aa317..2306da2 100644 --- a/tests/unit/test_tag_piwik.py +++ b/tests/unit/test_tag_piwik.py @@ -11,6 +11,8 @@ from analytical.templatetags.piwik import PiwikNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(PIWIK_DOMAIN_PATH='example.com', PIWIK_SITE_ID='345') class PiwikTagTestCase(TagTestCase): @@ -20,71 +22,78 @@ class PiwikTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('piwik', 'piwik') - self.assertTrue('"//example.com/"' in r, r) - self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) - self.assertTrue('img src="//example.com/piwik.php?idsite=345"' - in r, r) + assert '"//example.com/"' in r + assert "_paq.push(['setSiteId', 345]);" in r + assert 'img src="//example.com/piwik.php?idsite=345"' in r def test_node(self): r = PiwikNode().render(Context({})) - self.assertTrue('"//example.com/";' in r, r) - self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) - self.assertTrue('img src="//example.com/piwik.php?idsite=345"' - in r, r) + assert '"//example.com/";' in r + assert "_paq.push(['setSiteId', 345]);" in r + assert 'img src="//example.com/piwik.php?idsite=345"' in r @override_settings(PIWIK_DOMAIN_PATH='example.com/piwik', PIWIK_SITE_ID='345') def test_domain_path_valid(self): r = self.render_tag('piwik', 'piwik') - self.assertTrue('"//example.com/piwik/"' in r, r) + assert '"//example.com/piwik/"' in r @override_settings(PIWIK_DOMAIN_PATH='example.com:1234', PIWIK_SITE_ID='345') def test_domain_port_valid(self): r = self.render_tag('piwik', 'piwik') - self.assertTrue('"//example.com:1234/";' in r, r) + assert '"//example.com:1234/";' in r @override_settings(PIWIK_DOMAIN_PATH='example.com:1234/piwik', PIWIK_SITE_ID='345') def test_domain_port_path_valid(self): r = self.render_tag('piwik', 'piwik') - self.assertTrue('"//example.com:1234/piwik/"' in r, r) + assert '"//example.com:1234/piwik/"' in r @override_settings(PIWIK_DOMAIN_PATH=None) def test_no_domain(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_SITE_ID=None) def test_no_siteid(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_SITE_ID='x') def test_siteid_not_a_number(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_DOMAIN_PATH='http://www.example.com') def test_domain_protocol_invalid(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_DOMAIN_PATH='example.com/') def test_domain_slash_invalid(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_DOMAIN_PATH='example.com:123:456') def test_domain_multi_port(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_DOMAIN_PATH='example.com:') def test_domain_incomplete_port(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_DOMAIN_PATH='example.com:/piwik') def test_domain_uri_incomplete_port(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(PIWIK_DOMAIN_PATH='example.com:12df') def test_domain_port_invalid(self): - self.assertRaises(AnalyticalException, PiwikNode) + with pytest.raises(AnalyticalException): + PiwikNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -92,20 +101,18 @@ class PiwikTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = PiwikNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') def test_uservars(self): context = Context({'piwik_vars': [(1, 'foo', 'foo_val'), (2, 'bar', 'bar_val', 'page'), (3, 'spam', 'spam_val', 'visit')]}) r = PiwikNode().render(context) - msg = 'Incorrect Piwik custom variable rendering. Expected:\n%s\nIn:\n%s' for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', '_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', '_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']: - self.assertIn(var_code, r, msg % (var_code, r)) + assert var_code in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_default_usertrack(self): @@ -113,27 +120,24 @@ class PiwikTagTestCase(TagTestCase): 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum') }) r = PiwikNode().render(context) - msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' - self.assertIn(var_code, r, msg % (var_code, r)) + assert var_code in r def test_piwik_usertrack(self): context = Context({ 'piwik_identity': 'BDFL' }) r = PiwikNode().render(context) - msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' - self.assertIn(var_code, r, msg % (var_code, r)) + assert var_code in r def test_analytical_usertrack(self): context = Context({ 'analytical_identity': 'BDFL' }) r = PiwikNode().render(context) - msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' - self.assertIn(var_code, r, msg % (var_code, r)) + assert var_code in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_disable_usertrack(self): @@ -142,11 +146,10 @@ class PiwikTagTestCase(TagTestCase): 'piwik_identity': None }) r = PiwikNode().render(context) - msg = 'Incorrect Piwik user tracking rendering.\nFound:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' - self.assertNotIn(var_code, r, msg % (var_code, r)) + assert var_code not in r @override_settings(PIWIK_DISABLE_COOKIES=True) def test_disable_cookies(self): r = PiwikNode().render(Context({})) - self.assertTrue("_paq.push(['disableCookies']);" in r, r) + assert "_paq.push(['disableCookies']);" in r diff --git a/tests/unit/test_tag_rating_mailru.py b/tests/unit/test_tag_rating_mailru.py index d8f9459..5f7cf8b 100644 --- a/tests/unit/test_tag_rating_mailru.py +++ b/tests/unit/test_tag_rating_mailru.py @@ -10,6 +10,8 @@ from analytical.templatetags.rating_mailru import RatingMailruNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(RATING_MAILRU_COUNTER_ID='1234567') class RatingMailruTagTestCase(TagTestCase): @@ -19,19 +21,21 @@ class RatingMailruTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('rating_mailru', 'rating_mailru') - self.assertTrue("counter?id=1234567;js=na" in r, r) + assert "counter?id=1234567;js=na" in r def test_node(self): r = RatingMailruNode().render(Context({})) - self.assertTrue("counter?id=1234567;js=na" in r, r) + assert "counter?id=1234567;js=na" in r @override_settings(RATING_MAILRU_COUNTER_ID=None) def test_no_site_id(self): - self.assertRaises(AnalyticalException, RatingMailruNode) + with pytest.raises(AnalyticalException): + RatingMailruNode() @override_settings(RATING_MAILRU_COUNTER_ID='1234abc') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, RatingMailruNode) + with pytest.raises(AnalyticalException): + RatingMailruNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -39,6 +43,5 @@ class RatingMailruTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = RatingMailruNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_snapengage.py b/tests/unit/test_tag_snapengage.py index 215ebe2..a85d812 100644 --- a/tests/unit/test_tag_snapengage.py +++ b/tests/unit/test_tag_snapengage.py @@ -14,6 +14,7 @@ from analytical.templatetags.snapengage import SnapEngageNode, \ from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest WIDGET_ID = 'ec329c69-0bf0-4db8-9b77-3f8150fb977e' @@ -31,246 +32,216 @@ class SnapEngageTestCase(TagTestCase): def test_tag(self): r = self.render_tag('snapengage', 'snapengage') - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r def test_node(self): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r @override_settings(SNAPENGAGE_WIDGET_ID=None) def test_no_site_id(self): - self.assertRaises(AnalyticalException, SnapEngageNode) + with pytest.raises(AnalyticalException): + SnapEngageNode() @override_settings(SNAPENGAGE_WIDGET_ID='abc') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, SnapEngageNode) + with pytest.raises(AnalyticalException): + SnapEngageNode() def test_no_button(self): r = SnapEngageNode().render(Context({ 'snapengage_button': BUTTON_STYLE_NONE, })) - self.assertTrue('SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r, r) + assert 'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_NONE): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r, r) + assert 'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r def test_live_button(self): r = SnapEngageNode().render(Context({ 'snapengage_button': BUTTON_STYLE_LIVE, })) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"55%",true);' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%",true);' in r with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_LIVE): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"55%",true);' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%",true);' in r def test_custom_button(self): r = SnapEngageNode().render(Context({ 'snapengage_button': "http://www.example.com/button.png", })) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"55%");' in r, r) - self.assertTrue( - 'SnapABug.setButton("http://www.example.com/button.png");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + assert 'SnapABug.setButton("http://www.example.com/button.png");' in r with override_settings( SNAPENGAGE_BUTTON="http://www.example.com/button.png"): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"55%");' in r, r) - self.assertTrue( - 'SnapABug.setButton("http://www.example.com/button.png");' in r, - r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + assert 'SnapABug.setButton("http://www.example.com/button.png");' in r def test_button_location_right(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location': BUTTON_LOCATION_RIGHT, })) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1","55%");' in r with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_RIGHT): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1","55%");' in r def test_button_location_top(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location': BUTTON_LOCATION_TOP, })) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2","55%");' in r with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_TOP): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2","55%");' in r def test_button_location_bottom(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location': BUTTON_LOCATION_BOTTOM, })) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3","55%");' in r with override_settings( SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_BOTTOM): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",' - '"55%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3","55%");' in r def test_button_offset(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location_offset': "30%", })) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"30%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","30%");' in r with override_settings(SNAPENGAGE_BUTTON_LOCATION_OFFSET="30%"): r = SnapEngageNode().render(Context()) - self.assertTrue( - 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' - '"30%");' in r, r) + assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","30%");' in r def test_button_effect(self): r = SnapEngageNode().render(Context({ 'snapengage_button_effect': "-4px", })) - self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r) + assert 'SnapABug.setButtonEffect("-4px");' in r with override_settings(SNAPENGAGE_BUTTON_EFFECT="-4px"): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r) + assert 'SnapABug.setButtonEffect("-4px");' in r def test_form_position(self): r = SnapEngageNode().render(Context({ 'snapengage_form_position': FORM_POSITION_TOP_LEFT, })) - self.assertTrue('SnapABug.setChatFormPosition("tl");' in r, r) + assert 'SnapABug.setChatFormPosition("tl");' in r with override_settings(SNAPENGAGE_FORM_POSITION=FORM_POSITION_TOP_LEFT): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setChatFormPosition("tl");' in r, r) + assert 'SnapABug.setChatFormPosition("tl");' in r def test_form_top_position(self): r = SnapEngageNode().render(Context({ 'snapengage_form_top_position': 40, })) - self.assertTrue('SnapABug.setFormTopPosition(40);' in r, r) + assert 'SnapABug.setFormTopPosition(40);' in r with override_settings(SNAPENGAGE_FORM_TOP_POSITION=40): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setFormTopPosition(40);' in r, r) + assert 'SnapABug.setFormTopPosition(40);' in r def test_domain(self): r = SnapEngageNode().render(Context({ 'snapengage_domain': "example.com"})) - self.assertTrue('SnapABug.setDomain("example.com");' in r, r) + assert 'SnapABug.setDomain("example.com");' in r with override_settings(SNAPENGAGE_DOMAIN="example.com"): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setDomain("example.com");' in r, r) + assert 'SnapABug.setDomain("example.com");' in r def test_secure_connection(self): r = SnapEngageNode().render(Context({ 'snapengage_secure_connection': True})) - self.assertTrue('SnapABug.setSecureConnexion();' in r, r) + assert 'SnapABug.setSecureConnexion();' in r with override_settings(SNAPENGAGE_SECURE_CONNECTION=True): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setSecureConnexion();' in r, r) + assert 'SnapABug.setSecureConnexion();' in r def test_show_offline(self): r = SnapEngageNode().render(Context({ 'snapengage_show_offline': False, })) - self.assertTrue('SnapABug.allowOffline(false);' in r, r) + assert 'SnapABug.allowOffline(false);' in r with override_settings(SNAPENGAGE_SHOW_OFFLINE=False): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.allowOffline(false);' in r, r) + assert 'SnapABug.allowOffline(false);' in r def test_proactive_chat(self): r = SnapEngageNode().render(Context({ 'snapengage_proactive_chat': False})) - self.assertTrue('SnapABug.allowProactiveChat(false);' in r, r) + assert 'SnapABug.allowProactiveChat(false);' in r def test_screenshot(self): r = SnapEngageNode().render(Context({ 'snapengage_screenshots': False, })) - self.assertTrue('SnapABug.allowScreenshot(false);' in r, r) + assert 'SnapABug.allowScreenshot(false);' in r with override_settings(SNAPENGAGE_SCREENSHOTS=False): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.allowScreenshot(false);' in r, r) + assert 'SnapABug.allowScreenshot(false);' in r def test_offline_screenshots(self): r = SnapEngageNode().render(Context({ 'snapengage_offline_screenshots': False, })) - self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r) + assert 'SnapABug.showScreenshotOption(false);' in r with override_settings(SNAPENGAGE_OFFLINE_SCREENSHOTS=False): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r) + assert 'SnapABug.showScreenshotOption(false);' in r def test_sounds(self): r = SnapEngageNode().render(Context({'snapengage_sounds': False})) - self.assertTrue('SnapABug.allowChatSound(false);' in r, r) + assert 'SnapABug.allowChatSound(false);' in r with override_settings(SNAPENGAGE_SOUNDS=False): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.allowChatSound(false);' in r, r) + assert 'SnapABug.allowChatSound(false);' in r @override_settings(SNAPENGAGE_READONLY_EMAIL=False) def test_email(self): r = SnapEngageNode().render(Context({ 'snapengage_email': 'test@example.com', })) - self.assertTrue('SnapABug.setUserEmail("test@example.com");' in r, r) + assert 'SnapABug.setUserEmail("test@example.com");' in r def test_email_readonly(self): r = SnapEngageNode().render(Context({ 'snapengage_email': 'test@example.com', 'snapengage_readonly_email': True, })) - self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r, r) + assert 'SnapABug.setUserEmail("test@example.com",true);' in r with override_settings(SNAPENGAGE_READONLY_EMAIL=True): r = SnapEngageNode().render(Context({ 'snapengage_email': 'test@example.com', })) - self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r, r) + assert 'SnapABug.setUserEmail("test@example.com",true);' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = SnapEngageNode().render(Context({ 'user': User(username='test', email='test@example.com'), })) - self.assertTrue('SnapABug.setUserEmail("test@example.com");' in r, r) + assert 'SnapABug.setUserEmail("test@example.com");' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = SnapEngageNode().render(Context({ 'user': AnonymousUser(), })) - self.assertFalse('SnapABug.setUserEmail(' in r, r) + assert 'SnapABug.setUserEmail(' not in r def test_language(self): r = SnapEngageNode().render(Context({'snapengage_locale': 'fr'})) - self.assertTrue('SnapABug.setLocale("fr");' in r, r) + assert 'SnapABug.setLocale("fr");' in r with override_settings(SNAPENGAGE_LOCALE='fr'): r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setLocale("fr");' in r, r) + assert 'SnapABug.setLocale("fr");' in r def test_automatic_language(self): real_get_language = translation.get_language try: translation.get_language = lambda: 'fr-ca' r = SnapEngageNode().render(Context()) - self.assertTrue('SnapABug.setLocale("fr_CA");' in r, r) + assert 'SnapABug.setLocale("fr_CA");' in r finally: translation.get_language = real_get_language diff --git a/tests/unit/test_tag_spring_metrics.py b/tests/unit/test_tag_spring_metrics.py index ca00e71..6eee86d 100644 --- a/tests/unit/test_tag_spring_metrics.py +++ b/tests/unit/test_tag_spring_metrics.py @@ -11,6 +11,8 @@ from analytical.templatetags.spring_metrics import SpringMetricsNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(SPRING_METRICS_TRACKING_ID='12345678') class SpringMetricsTagTestCase(TagTestCase): @@ -20,39 +22,41 @@ class SpringMetricsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('spring_metrics', 'spring_metrics') - self.assertTrue("_springMetq.push(['id', '12345678']);" in r, r) + assert "_springMetq.push(['id', '12345678']);" in r def test_node(self): r = SpringMetricsNode().render(Context({})) - self.assertTrue("_springMetq.push(['id', '12345678']);" in r, r) + assert "_springMetq.push(['id', '12345678']);" in r @override_settings(SPRING_METRICS_TRACKING_ID=None) def test_no_site_id(self): - self.assertRaises(AnalyticalException, SpringMetricsNode) + with pytest.raises(AnalyticalException): + SpringMetricsNode() @override_settings(SPRING_METRICS_TRACKING_ID='123xyz') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, SpringMetricsNode) + with pytest.raises(AnalyticalException): + SpringMetricsNode() @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = SpringMetricsNode().render(Context({ 'user': User(email='test@test.com'), })) - self.assertTrue("_springMetq.push(['setdata', {'email': 'test@test.com'}]);" in r, r) + assert "_springMetq.push(['setdata', {'email': 'test@test.com'}]);" in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = SpringMetricsNode().render(Context({'user': AnonymousUser()})) - self.assertFalse("_springMetq.push(['setdata', {'email':" in r, r) + assert "_springMetq.push(['setdata', {'email':" not in r def test_custom(self): r = SpringMetricsNode().render(Context({ 'spring_metrics_var1': 'val1', 'spring_metrics_var2': 'val2', })) - self.assertTrue("_springMetq.push(['setdata', {'var1': 'val1'}]);" in r, r) - self.assertTrue("_springMetq.push(['setdata', {'var2': 'val2'}]);" in r, r) + assert "_springMetq.push(['setdata', {'var1': 'val1'}]);" in r + assert "_springMetq.push(['setdata', {'var2': 'val2'}]);" in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -60,6 +64,5 @@ class SpringMetricsTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = SpringMetricsNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_uservoice.py b/tests/unit/test_tag_uservoice.py index c740311..fcacbf9 100644 --- a/tests/unit/test_tag_uservoice.py +++ b/tests/unit/test_tag_uservoice.py @@ -4,6 +4,7 @@ Tests for the UserVoice tags and filters. from django.template import Context from django.test.utils import override_settings +import pytest from analytical.templatetags.uservoice import UserVoiceNode from utils import TagTestCase @@ -16,58 +17,55 @@ class UserVoiceTagTestCase(TagTestCase): Tests for the ``uservoice`` template tag. """ - def assertIn(self, element, container): - try: - super(TagTestCase, self).assertIn(element, container) - except AttributeError: - self.assertTrue(element in container) - def test_node(self): r = UserVoiceNode().render(Context()) - self.assertIn("widget.uservoice.com/abcdefghijklmnopqrst.js", r) + assert "widget.uservoice.com/abcdefghijklmnopqrst.js" in r def test_tag(self): r = self.render_tag('uservoice', 'uservoice') - self.assertIn("widget.uservoice.com/abcdefghijklmnopqrst.js", r) + assert "widget.uservoice.com/abcdefghijklmnopqrst.js" in r @override_settings(USERVOICE_WIDGET_KEY=None) def test_no_key(self): - self.assertRaises(AnalyticalException, UserVoiceNode) + with pytest.raises(AnalyticalException): + UserVoiceNode() @override_settings(USERVOICE_WIDGET_KEY='abcdefgh ijklmnopqrst') def test_invalid_key(self): - self.assertRaises(AnalyticalException, UserVoiceNode) + with pytest.raises(AnalyticalException): + UserVoiceNode() @override_settings(USERVOICE_WIDGET_KEY='') def test_empty_key(self): - self.assertRaises(AnalyticalException, UserVoiceNode) + with pytest.raises(AnalyticalException): + UserVoiceNode() def test_overridden_key(self): vars = {'uservoice_widget_key': 'defghijklmnopqrstuvw'} r = UserVoiceNode().render(Context(vars)) - self.assertIn("widget.uservoice.com/defghijklmnopqrstuvw.js", r) + assert "widget.uservoice.com/defghijklmnopqrstuvw.js" in r @override_settings(USERVOICE_WIDGET_OPTIONS={'key1': 'val1'}) def test_options(self): r = UserVoiceNode().render(Context()) - self.assertIn("""UserVoice.push(['set', {"key1": "val1"}]);""", r) + assert """UserVoice.push(['set', {"key1": "val1"}]);""" in r @override_settings(USERVOICE_WIDGET_OPTIONS={'key1': 'val1'}) def test_override_options(self): data = {'uservoice_widget_options': {'key1': 'val2'}} r = UserVoiceNode().render(Context(data)) - self.assertIn("""UserVoice.push(['set', {"key1": "val2"}]);""", r) + assert """UserVoice.push(['set', {"key1": "val2"}]);""" in r def test_auto_trigger_default(self): r = UserVoiceNode().render(Context()) - self.assertTrue("UserVoice.push(['addTrigger', {}]);" in r, r) + assert "UserVoice.push(['addTrigger', {}]);" in r @override_settings(USERVOICE_ADD_TRIGGER=False) def test_auto_trigger(self): r = UserVoiceNode().render(Context()) - self.assertFalse("UserVoice.push(['addTrigger', {}]);" in r, r) + assert "UserVoice.push(['addTrigger', {}]);" not in r @override_settings(USERVOICE_ADD_TRIGGER=False) def test_auto_trigger_custom_win(self): r = UserVoiceNode().render(Context({'uservoice_add_trigger': True})) - self.assertTrue("UserVoice.push(['addTrigger', {}]);" in r, r) + assert "UserVoice.push(['addTrigger', {}]);" in r diff --git a/tests/unit/test_tag_woopra.py b/tests/unit/test_tag_woopra.py index 9dca7e9..0bfbf36 100644 --- a/tests/unit/test_tag_woopra.py +++ b/tests/unit/test_tag_woopra.py @@ -11,6 +11,8 @@ from analytical.templatetags.woopra import WoopraNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(WOOPRA_DOMAIN='example.com') class WoopraTagTestCase(TagTestCase): @@ -20,32 +22,33 @@ class WoopraTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('woopra', 'woopra') - self.assertTrue('var woo_settings = {"domain": "example.com"};' in r, r) + assert 'var woo_settings = {"domain": "example.com"};' in r def test_node(self): r = WoopraNode().render(Context({})) - self.assertTrue('var woo_settings = {"domain": "example.com"};' in r, r) + assert 'var woo_settings = {"domain": "example.com"};' in r @override_settings(WOOPRA_DOMAIN=None) def test_no_domain(self): - self.assertRaises(AnalyticalException, WoopraNode) + with pytest.raises(AnalyticalException): + WoopraNode() @override_settings(WOOPRA_DOMAIN='this is not a domain') def test_wrong_domain(self): - self.assertRaises(AnalyticalException, WoopraNode) + with pytest.raises(AnalyticalException): + WoopraNode() @override_settings(WOOPRA_IDLE_TIMEOUT=1234) def test_idle_timeout(self): r = WoopraNode().render(Context({})) - self.assertTrue('var woo_settings = ' - '{"domain": "example.com", "idle_timeout": "1234"};' in r, r) + assert 'var woo_settings = {"domain": "example.com", "idle_timeout": "1234"};' in r def test_custom(self): r = WoopraNode().render(Context({ 'woopra_var1': 'val1', 'woopra_var2': 'val2', })) - self.assertTrue('var woo_visitor = {"var1": "val1", "var2": "val2"};' in r, r) + assert 'var woo_visitor = {"var1": "val1", "var2": "val2"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_name_and_email(self): @@ -55,13 +58,13 @@ class WoopraTagTestCase(TagTestCase): last_name='Lastname', email="test@example.com"), })) - self.assertTrue('var woo_visitor = ' - '{"email": "test@example.com", "name": "Firstname Lastname"};' in r, r) + assert 'var woo_visitor = ' + '{"email": "test@example.com", "name": "Firstname Lastname"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_username_no_email(self): r = WoopraNode().render(Context({'user': User(username='test')})) - self.assertTrue('var woo_visitor = {"name": "test"};' in r, r) + assert 'var woo_visitor = {"name": "test"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_no_identify_when_explicit_name(self): @@ -69,7 +72,7 @@ class WoopraTagTestCase(TagTestCase): 'woopra_name': 'explicit', 'user': User(username='implicit'), })) - self.assertTrue('var woo_visitor = {"name": "explicit"};' in r, r) + assert 'var woo_visitor = {"name": "explicit"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_no_identify_when_explicit_email(self): @@ -77,12 +80,12 @@ class WoopraTagTestCase(TagTestCase): 'woopra_email': 'explicit', 'user': User(username='implicit'), })) - self.assertTrue('var woo_visitor = {"email": "explicit"};' in r, r) + assert 'var woo_visitor = {"email": "explicit"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = WoopraNode().render(Context({'user': AnonymousUser()})) - self.assertTrue('var woo_visitor = {};' in r, r) + assert 'var woo_visitor = {};' in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -90,6 +93,5 @@ class WoopraTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = WoopraNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_tag_yandex_metrica.py b/tests/unit/test_tag_yandex_metrica.py index 3b0619e..d9956d8 100644 --- a/tests/unit/test_tag_yandex_metrica.py +++ b/tests/unit/test_tag_yandex_metrica.py @@ -11,6 +11,8 @@ from analytical.templatetags.yandex_metrica import YandexMetricaNode from utils import TagTestCase from analytical.utils import AnalyticalException +import pytest + @override_settings(YANDEX_METRICA_COUNTER_ID='12345678') class YandexMetricaTagTestCase(TagTestCase): @@ -20,19 +22,21 @@ class YandexMetricaTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('yandex_metrica', 'yandex_metrica') - self.assertTrue("w.yaCounter12345678 = new Ya.Metrika" in r, r) + assert "w.yaCounter12345678 = new Ya.Metrika" in r def test_node(self): r = YandexMetricaNode().render(Context({})) - self.assertTrue("w.yaCounter12345678 = new Ya.Metrika" in r, r) + assert "w.yaCounter12345678 = new Ya.Metrika" in r @override_settings(YANDEX_METRICA_COUNTER_ID=None) def test_no_site_id(self): - self.assertRaises(AnalyticalException, YandexMetricaNode) + with pytest.raises(AnalyticalException): + YandexMetricaNode() @override_settings(YANDEX_METRICA_COUNTER_ID='1234abcd') def test_wrong_site_id(self): - self.assertRaises(AnalyticalException, YandexMetricaNode) + with pytest.raises(AnalyticalException): + YandexMetricaNode() @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -40,6 +44,5 @@ class YandexMetricaTagTestCase(TagTestCase): req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = YandexMetricaNode().render(context) - self.assertTrue(r.startswith( - ''), r) + assert r.startswith('') diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 22b2a88..4ba8d33 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -18,6 +18,8 @@ from analytical.utils import ( ) from utils import TestCase +import pytest + class SettingDeletedTestCase(TestCase): @@ -27,7 +29,7 @@ class SettingDeletedTestCase(TestCase): Make sure using get_required_setting fails in the right place. """ - with self.assertRaisesRegex(AnalyticalException, "^USER_ID setting is not set$"): + with pytest.raises(AnalyticalException, match="USER_ID setting is not set"): get_required_setting("USER_ID", r"\d+", "invalid USER_ID") @@ -43,27 +45,27 @@ class MyUser(AbstractBaseUser): class GetIdentityTestCase(TestCase): def test_custom_username_field(self): get_id = get_identity(Context({}), user=MyUser(identity='fake_id')) - self.assertEqual(get_id, 'fake_id') + assert get_id == 'fake_id' @override_settings(ANALYTICAL_DOMAIN="example.org") class GetDomainTestCase(TestCase): def test_get_service_domain_from_context(self): context = Context({'test_domain': 'example.com'}) - self.assertEqual(get_domain(context, 'test'), 'example.com') + assert get_domain(context, 'test') == 'example.com' def test_get_analytical_domain_from_context(self): context = Context({'analytical_domain': 'example.com'}) - self.assertEqual(get_domain(context, 'test'), 'example.com') + assert get_domain(context, 'test') == 'example.com' @override_settings(TEST_DOMAIN="example.net") def test_get_service_domain_from_settings(self): context = Context() - self.assertEqual(get_domain(context, 'test'), 'example.net') + assert get_domain(context, 'test') == 'example.net' def test_get_analytical_domain_from_settings(self): context = Context() - self.assertEqual(get_domain(context, 'test'), 'example.org') + assert get_domain(context, 'test') == 'example.org' # FIXME: enable Django apps dynamically and enable test again @@ -82,7 +84,7 @@ class InternalIpTestCase(TestCase): @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_no_internal_ip(self): context = Context() - self.assertFalse(is_internal_ip(context)) + assert not is_internal_ip(context) @override_settings(INTERNAL_IPS=['1.1.1.1']) @override_settings(ANALYTICAL_INTERNAL_IPS=[]) @@ -90,39 +92,39 @@ class InternalIpTestCase(TestCase): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) - self.assertFalse(is_internal_ip(context)) + assert not is_internal_ip(context) @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}) - self.assertTrue(is_internal_ip(context)) + assert is_internal_ip(context) @override_settings(TEST_INTERNAL_IPS=['1.1.1.1']) def test_render_prefix_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) - self.assertTrue(is_internal_ip(context, 'TEST')) + assert is_internal_ip(context, 'TEST') @override_settings(INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip_fallback(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) - self.assertTrue(is_internal_ip(context)) + assert is_internal_ip(context) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip_forwarded_for(self): req = HttpRequest() req.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1' context = Context({'request': req}) - self.assertTrue(is_internal_ip(context)) + assert is_internal_ip(context) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_different_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '2.2.2.2' context = Context({'request': req}) - self.assertFalse(is_internal_ip(context)) + assert not is_internal_ip(context) From 37009f2e08e78d41ddc1bdd1e940f195c3ae4352 Mon Sep 17 00:00:00 2001 From: David Smith Date: Sat, 20 Feb 2021 12:41:17 +0000 Subject: [PATCH 045/103] Implemented isort --- analytical/templatetags/analytical.py | 3 +-- analytical/templatetags/chartbeat.py | 3 +-- analytical/templatetags/clickmap.py | 3 +-- analytical/templatetags/clicky.py | 9 +++++--- analytical/templatetags/crazy_egg.py | 3 +-- analytical/templatetags/facebook_pixel.py | 3 +-- analytical/templatetags/gauges.py | 2 +- .../templatetags/google_analytics_js.py | 1 + analytical/templatetags/gosquared.py | 9 +++++--- analytical/templatetags/hotjar.py | 3 +-- analytical/templatetags/hubspot.py | 3 +-- analytical/templatetags/intercom.py | 11 +++++++--- analytical/templatetags/kiss_insights.py | 1 - analytical/templatetags/kiss_metrics.py | 9 +++++--- analytical/templatetags/luckyorange.py | 3 +-- analytical/templatetags/matomo.py | 11 ++++++---- analytical/templatetags/mixpanel.py | 9 +++++--- analytical/templatetags/olark.py | 1 - analytical/templatetags/optimizely.py | 3 +-- analytical/templatetags/performable.py | 9 +++++--- analytical/templatetags/piwik.py | 13 ++++++----- analytical/templatetags/rating_mailru.py | 4 +--- analytical/templatetags/spring_metrics.py | 9 +++++--- analytical/templatetags/uservoice.py | 2 +- analytical/templatetags/yandex_metrica.py | 4 +--- analytical/utils.py | 1 - setup.cfg | 3 +++ tests/testproject/templatetags/dummy.py | 1 - tests/unit/test_tag_analytical.py | 2 +- tests/unit/test_tag_chartbeat.py | 8 +++---- tests/unit/test_tag_clickmap.py | 5 ++--- tests/unit/test_tag_clicky.py | 7 +++--- tests/unit/test_tag_crazy_egg.py | 4 ++-- tests/unit/test_tag_facebook_pixel.py | 10 +++++---- tests/unit/test_tag_gauges.py | 5 ++--- tests/unit/test_tag_google_analytics.py | 17 +++++++++----- tests/unit/test_tag_google_analytics_gtag.py | 5 ++--- tests/unit/test_tag_google_analytics_js.py | 13 ++++++----- tests/unit/test_tag_gosquared.py | 7 +++--- tests/unit/test_tag_hotjar.py | 5 ++--- tests/unit/test_tag_hubspot.py | 5 ++--- tests/unit/test_tag_intercom.py | 5 ++--- tests/unit/test_tag_kiss_insights.py | 7 +++--- tests/unit/test_tag_kiss_metrics.py | 7 +++--- tests/unit/test_tag_luckyorange.py | 5 ++--- tests/unit/test_tag_matomo.py | 4 ++-- tests/unit/test_tag_mixpanel.py | 7 +++--- tests/unit/test_tag_olark.py | 7 +++--- tests/unit/test_tag_optimizely.py | 5 ++--- tests/unit/test_tag_performable.py | 7 +++--- tests/unit/test_tag_piwik.py | 5 ++--- tests/unit/test_tag_rating_mailru.py | 5 ++--- tests/unit/test_tag_snapengage.py | 22 ++++++++++++------- tests/unit/test_tag_spring_metrics.py | 7 +++--- tests/unit/test_tag_uservoice.py | 4 ++-- tests/unit/test_tag_woopra.py | 7 +++--- tests/unit/test_tag_yandex_metrica.py | 5 ++--- tests/unit/test_utils.py | 5 ++--- tests/unit/utils.py | 2 +- tox.ini | 8 ++++++- 60 files changed, 184 insertions(+), 169 deletions(-) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index bc9c262..f9ca446 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -3,14 +3,13 @@ Analytical template tags and filters. """ import logging +from importlib import import_module from django import template from django.template import Node, TemplateSyntaxError -from importlib import import_module from analytical.utils import AnalyticalException - TAG_LOCATIONS = ['head_top', 'head_bottom', 'body_top', 'body_bottom'] TAG_POSITIONS = ['first', None, 'last'] TAG_MODULES = [ diff --git a/analytical/templatetags/chartbeat.py b/analytical/templatetags/chartbeat.py index 1bdb9ab..6ab7b84 100644 --- a/analytical/templatetags/chartbeat.py +++ b/analytical/templatetags/chartbeat.py @@ -9,8 +9,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import is_internal_ip, disable_html, get_required_setting - +from analytical.utils import disable_html, get_required_setting, is_internal_ip USER_ID_RE = re.compile(r'^\d+$') INIT_CODE = """""" diff --git a/analytical/templatetags/clickmap.py b/analytical/templatetags/clickmap.py index 2cdaf80..16ec133 100644 --- a/analytical/templatetags/clickmap.py +++ b/analytical/templatetags/clickmap.py @@ -6,8 +6,7 @@ import re from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import is_internal_ip, disable_html, get_required_setting - +from analytical.utils import disable_html, get_required_setting, is_internal_ip CLICKMAP_TRACKER_ID_RE = re.compile(r'^\w+$') TRACKING_CODE = """ diff --git a/analytical/templatetags/clicky.py b/analytical/templatetags/clicky.py index 71d5fba..2401a4c 100644 --- a/analytical/templatetags/clicky.py +++ b/analytical/templatetags/clicky.py @@ -7,9 +7,12 @@ import re from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import get_identity, is_internal_ip, disable_html, \ - get_required_setting - +from analytical.utils import ( + disable_html, + get_identity, + get_required_setting, + is_internal_ip, +) SITE_ID_RE = re.compile(r'^\d+$') TRACKING_CODE = """ diff --git a/analytical/templatetags/crazy_egg.py b/analytical/templatetags/crazy_egg.py index 5ad8a5a..49338b3 100644 --- a/analytical/templatetags/crazy_egg.py +++ b/analytical/templatetags/crazy_egg.py @@ -6,8 +6,7 @@ import re from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import is_internal_ip, disable_html, get_required_setting - +from analytical.utils import disable_html, get_required_setting, is_internal_ip ACCOUNT_NUMBER_RE = re.compile(r'^\d+$') SETUP_CODE = '""" diff --git a/analytical/templatetags/performable.py b/analytical/templatetags/performable.py index 39e6c16..de84bcf 100644 --- a/analytical/templatetags/performable.py +++ b/analytical/templatetags/performable.py @@ -7,9 +7,12 @@ import re from django.template import Library, Node, TemplateSyntaxError from django.utils.safestring import mark_safe -from analytical.utils import is_internal_ip, disable_html, get_identity, \ - get_required_setting - +from analytical.utils import ( + disable_html, + get_identity, + get_required_setting, + is_internal_ip, +) API_KEY_RE = re.compile(r'^\w+$') SETUP_CODE = """ diff --git a/analytical/templatetags/piwik.py b/analytical/templatetags/piwik.py index 8855a61..88adb9c 100644 --- a/analytical/templatetags/piwik.py +++ b/analytical/templatetags/piwik.py @@ -2,17 +2,20 @@ Piwik template tags and filters. """ +import re +import warnings from collections import namedtuple from itertools import chain -import re from django.conf import settings from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import (is_internal_ip, disable_html, - get_required_setting, get_identity) - -import warnings +from analytical.utils import ( + disable_html, + get_identity, + get_required_setting, + is_internal_ip, +) # domain name (characters separated by a dot), optional port, optional URI path, no slash DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$') diff --git a/analytical/templatetags/rating_mailru.py b/analytical/templatetags/rating_mailru.py index c7b050a..bdcb444 100644 --- a/analytical/templatetags/rating_mailru.py +++ b/analytical/templatetags/rating_mailru.py @@ -6,9 +6,7 @@ import re from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import is_internal_ip, disable_html, \ - get_required_setting - +from analytical.utils import disable_html, get_required_setting, is_internal_ip COUNTER_ID_RE = re.compile(r'^\d{7}$') COUNTER_CODE = """ diff --git a/analytical/templatetags/spring_metrics.py b/analytical/templatetags/spring_metrics.py index d402846..06814fc 100644 --- a/analytical/templatetags/spring_metrics.py +++ b/analytical/templatetags/spring_metrics.py @@ -6,9 +6,12 @@ import re from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import get_identity, is_internal_ip, disable_html, \ - get_required_setting - +from analytical.utils import ( + disable_html, + get_identity, + get_required_setting, + is_internal_ip, +) TRACKING_ID_RE = re.compile(r'^[0-9a-f]+$') TRACKING_CODE = """ diff --git a/analytical/templatetags/uservoice.py b/analytical/templatetags/uservoice.py index 1e64e38..a7ae920 100644 --- a/analytical/templatetags/uservoice.py +++ b/analytical/templatetags/uservoice.py @@ -7,8 +7,8 @@ import re from django.conf import settings from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import get_required_setting, get_identity +from analytical.utils import get_identity, get_required_setting WIDGET_KEY_RE = re.compile(r'^[a-zA-Z0-9]*$') TRACKING_CODE = """ diff --git a/analytical/templatetags/yandex_metrica.py b/analytical/templatetags/yandex_metrica.py index b269c89..209514b 100644 --- a/analytical/templatetags/yandex_metrica.py +++ b/analytical/templatetags/yandex_metrica.py @@ -8,9 +8,7 @@ import re from django.conf import settings from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import is_internal_ip, disable_html, \ - get_required_setting - +from analytical.utils import disable_html, get_required_setting, is_internal_ip COUNTER_ID_RE = re.compile(r'^\d{8}$') COUNTER_CODE = """ diff --git a/analytical/utils.py b/analytical/utils.py index 45e0fcf..be70b70 100644 --- a/analytical/utils.py +++ b/analytical/utils.py @@ -5,7 +5,6 @@ Utility function for django-analytical. from django.conf import settings from django.core.exceptions import ImproperlyConfigured - HTML_COMMENT = "" diff --git a/setup.cfg b/setup.cfg index 72285af..93945f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,3 +8,6 @@ upload-dir = build/docs/html [tool:pytest] DJANGO_SETTINGS_MODULE = tests.testproject.settings + +[isort] +profile = black diff --git a/tests/testproject/templatetags/dummy.py b/tests/testproject/templatetags/dummy.py index 4bca645..a267c39 100644 --- a/tests/testproject/templatetags/dummy.py +++ b/tests/testproject/templatetags/dummy.py @@ -6,7 +6,6 @@ from django.template import Library, Node, TemplateSyntaxError from analytical.templatetags.analytical import TAG_LOCATIONS - register = Library() diff --git a/tests/unit/test_tag_analytical.py b/tests/unit/test_tag_analytical.py index e037afc..037c08e 100644 --- a/tests/unit/test_tag_analytical.py +++ b/tests/unit/test_tag_analytical.py @@ -3,9 +3,9 @@ Tests for the generic template tags and filters. """ from django.template import Context, Template +from utils import TagTestCase from analytical.templatetags import analytical -from utils import TagTestCase class AnalyticsTagTestCase(TagTestCase): diff --git a/tests/unit/test_tag_chartbeat.py b/tests/unit/test_tag_chartbeat.py index c5e6493..f22f663 100644 --- a/tests/unit/test_tag_chartbeat.py +++ b/tests/unit/test_tag_chartbeat.py @@ -4,17 +4,15 @@ Tests for the Chartbeat template tags and filters. import re +import pytest from django.http import HttpRequest from django.template import Context from django.test import TestCase from django.test.utils import override_settings - -from analytical.templatetags.chartbeat import ChartbeatTopNode, \ - ChartbeatBottomNode from utils import TagTestCase -from analytical.utils import AnalyticalException -import pytest +from analytical.templatetags.chartbeat import ChartbeatBottomNode, ChartbeatTopNode +from analytical.utils import AnalyticalException @override_settings(CHARTBEAT_USER_ID='12345') diff --git a/tests/unit/test_tag_clickmap.py b/tests/unit/test_tag_clickmap.py index b6049eb..8be58cf 100644 --- a/tests/unit/test_tag_clickmap.py +++ b/tests/unit/test_tag_clickmap.py @@ -2,16 +2,15 @@ Tests for the Clickmap template tags and filters. """ +import pytest from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings +from utils import TagTestCase from analytical.templatetags.clickmap import ClickmapNode -from utils import TagTestCase from analytical.utils import AnalyticalException -import pytest - @override_settings(CLICKMAP_TRACKER_ID='12345ABC') class ClickmapTagTestCase(TagTestCase): diff --git a/tests/unit/test_tag_clicky.py b/tests/unit/test_tag_clicky.py index 2be16c8..8b40766 100644 --- a/tests/unit/test_tag_clicky.py +++ b/tests/unit/test_tag_clicky.py @@ -4,17 +4,16 @@ Tests for the Clicky template tags and filters. import re -from django.contrib.auth.models import User, AnonymousUser +import pytest +from django.contrib.auth.models import AnonymousUser, User from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings +from utils import TagTestCase from analytical.templatetags.clicky import ClickyNode -from utils import TagTestCase from analytical.utils import AnalyticalException -import pytest - @override_settings(CLICKY_SITE_ID='12345678') class ClickyTagTestCase(TagTestCase): diff --git a/tests/unit/test_tag_crazy_egg.py b/tests/unit/test_tag_crazy_egg.py index f2cab47..993f74a 100644 --- a/tests/unit/test_tag_crazy_egg.py +++ b/tests/unit/test_tag_crazy_egg.py @@ -2,13 +2,13 @@ Tests for the Crazy Egg template tags and filters. """ +import pytest from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings -import pytest +from utils import TagTestCase from analytical.templatetags.crazy_egg import CrazyEggNode -from utils import TagTestCase from analytical.utils import AnalyticalException diff --git a/tests/unit/test_tag_facebook_pixel.py b/tests/unit/test_tag_facebook_pixel.py index 2020257..374fa95 100644 --- a/tests/unit/test_tag_facebook_pixel.py +++ b/tests/unit/test_tag_facebook_pixel.py @@ -1,17 +1,19 @@ """ Tests for the Facebook Pixel 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.facebook_pixel import FacebookPixelHeadNode, FacebookPixelBodyNode -from utils import TagTestCase +from analytical.templatetags.facebook_pixel import ( + FacebookPixelBodyNode, + FacebookPixelHeadNode, +) from analytical.utils import AnalyticalException -import pytest - expected_head_html = """\ + +""" # noqa + +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 heap(parser, token): + """ + Heap tracker template tag. + + Renders Javascript code to track page visits. You must supply + your heap tracker ID (as a string) in the ``HEAP_TRACKER_ID`` + setting. + """ + _validate_no_args(token) + return HeapNode() + + +class HeapNode(Node): + def __init__(self): + self.tracker_id = get_required_setting('HEAP_TRACKER_ID', + HEAP_TRACKER_ID_RE, + "must be an numeric string") + + def render(self, context): + html = TRACKING_CODE % {'tracker_id': self.tracker_id} + if is_internal_ip(context, 'HEAP'): + html = disable_html(html, 'Heap') + return html + + +def contribute_to_analytical(add_node): + HeapNode() # ensure properly configured + add_node('head_bottom', HeapNode) diff --git a/docs/conf.py b/docs/conf.py index 7d86315..1894cd9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,6 @@ sys.path.append(os.path.dirname(os.path.abspath('.'))) import analytical # noqa - # -- General configuration -------------------------------------------------- project = 'django-analytical' diff --git a/docs/services/heap.rst b/docs/services/heap.rst new file mode 100644 index 0000000..a07ca8a --- /dev/null +++ b/docs/services/heap.rst @@ -0,0 +1,59 @@ +===================================== +Heap -- analytics and events tracking +===================================== + +`Heap`_ automatically captures all user interactions on your site, from the moment of installation forward. + +.. _`Heap`: https://heap.io/ + + +.. heap-installation: + +Installation +============ + +To start using the Heap 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. + +.. _heap-configuration: + +Configuration +============= + +Before you can use the Heap integration, you must first get your +Heap Tracker ID. If you don't have a Heap account yet, +`sign up`_ to get your Tracker ID. + +.. _`sign up`: https://heap.io/ + + +.. _heap-tracker-id: + +Setting the Tracker ID +---------------------- + +Heap gives you a unique ID. You can find this ID on the Projects page +of your Heap account. Set :const:`HEAP_TRACKER_ID` in the project +:file:`settings.py` file:: + + HEAP_TRACKER_ID = 'XXXXXXXX' + +If you do not set an Tracker ID, the tracking code will not be +rendered. + +The tracking code will be added just before the closing head tag. + + +.. _heap-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:`ANALYTICAL_INTERNAL_IPS` setting +(which is :const:`INTERNAL_IPS` by default,) the tracking code is +commented out. See :ref:`identifying-visitors` for important information +about detecting the visitor IP address. diff --git a/tests/unit/test_tag_heap.py b/tests/unit/test_tag_heap.py new file mode 100644 index 0000000..37ff046 --- /dev/null +++ b/tests/unit/test_tag_heap.py @@ -0,0 +1,50 @@ +""" +Tests for the Heap template tags and filters. +""" + +import pytest +from django.http import HttpRequest +from django.template import Context, Template, TemplateSyntaxError +from django.test.utils import override_settings +from utils import TagTestCase + +from analytical.templatetags.heap import HeapNode +from analytical.utils import AnalyticalException + + +@override_settings(HEAP_TRACKER_ID='123456789') +class HeapTagTestCase(TagTestCase): + """ + Tests for the ``heap`` template tag. + """ + + def test_tag(self): + r = self.render_tag('heap', 'heap') + assert "123456789" in r + + def test_node(self): + r = HeapNode().render(Context({})) + assert "123456789" in r + + def test_tags_take_no_args(self): + with pytest.raises(TemplateSyntaxError, match="'heap' takes no arguments"): + Template('{% load heap %}{% heap "arg" %}').render(Context({})) + + @override_settings(HEAP_TRACKER_ID=None) + def test_no_site_id(self): + with pytest.raises(AnalyticalException): + HeapNode() + + @override_settings(HEAP_TRACKER_ID='abcdefg') + def test_wrong_site_id(self): + with pytest.raises(AnalyticalException): + HeapNode() + + @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 = HeapNode().render(context) + assert r.startswith('') From 0b162449c53100ac241aaa5a6aec8e952e876129 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 26 Nov 2021 14:17:50 +0100 Subject: [PATCH 051/103] Fix pre-commit-ci complaint about missing config file --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4e6a92c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1 @@ +repos: [] From 5daa0c53294b6b956ceb1b0c7b6204806de788b2 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Thu, 3 Dec 2020 23:22:58 +0100 Subject: [PATCH 052/103] Split up test matrix into Python-Django combinations --- .github/workflows/test.yml | 22 +++++++++++++++++----- tox.ini | 7 ++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96ca50f..2b946a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,8 @@ name: Test -on: [push, pull_request] +on: +- push +- pull_request jobs: build: @@ -8,7 +10,16 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python-version: + - '3.6' + - '3.7' + - '3.8' + - '3.9' + - '3.10' + django-version: + - '2.2' + - '3.2' + - '4.0' steps: - uses: actions/checkout@v2 @@ -37,9 +48,10 @@ jobs: python -m pip install --upgrade pip python -m pip install --upgrade tox tox-gh-actions - - name: Tox tests - run: | - tox -v + - name: "Unit tests for py${{ matrix.python-version }}-django${{ matrix.django-version }}" + run: tox + env: + DJANGO: ${{ matrix.django-version }} - name: Upload coverage uses: codecov/codecov-action@v1 diff --git a/tox.ini b/tox.ini index 1810e0e..260e4ab 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,6 @@ deps = coverage pytest-django django22: Django>=2.2,<3.0 - django31: Django>=3.1,<3.2 django32: Django>=3.2,<4.0 django40: Django>=4.0a1,<4.1 @@ -28,6 +27,12 @@ python = 3.9: py39 3.10: py310 +[gh-actions:env] +DJANGO = + 2.2: django22 + 3.2: django32 + 4.0: django40 + [testenv:py37-bandit] description = PyCQA security linter deps = bandit<1.6 From 41b8c243d4397ecb9365f7a68e1c3643f6834162 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Thu, 3 Dec 2020 23:53:57 +0100 Subject: [PATCH 053/103] Add checks as a separate workflow Run test suite only for PRs and master --- .github/workflows/check.yml | 35 +++++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 10 +++++++--- tox.ini | 16 ++++++++++------ 3 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/check.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..d773cf2 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,35 @@ +name: Check + +on: + pull_request: + branches: + - master + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + env: + - isort + - flake8 + - bandit + - readme + - docs + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install prerequisites + run: python -m pip install --upgrade setuptools pip wheel tox + + - name: Run ${{ matrix.env }} + run: tox -e ${{ matrix.env }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b946a5..94491a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,12 @@ name: Test on: -- push -- pull_request + pull_request: + branches: + - master + push: + branches: + - master jobs: build: @@ -48,7 +52,7 @@ jobs: python -m pip install --upgrade pip python -m pip install --upgrade tox tox-gh-actions - - name: "Unit tests for py${{ matrix.python-version }}-django${{ matrix.django-version }}" + - name: Unit tests for py${{ matrix.python-version }}-django${{ matrix.django-version }} run: tox env: DJANGO: ${{ matrix.django-version }} diff --git a/tox.ini b/tox.ini index 260e4ab..d8c12f8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,14 @@ [tox] envlist = + isort + flake8 + bandit # Python/Django combinations that are officially supported py{36,37,38,39}-django{22} py{36,37,38,39,310}-django{32} py{38,39,310}-django{40} - py37-{flake8,bandit,readme,docs,isort} + readme + docs [testenv] description = Unit tests @@ -33,30 +37,30 @@ DJANGO = 3.2: django32 4.0: django40 -[testenv:py37-bandit] +[testenv:bandit] description = PyCQA security linter deps = bandit<1.6 commands = - bandit -r --ini tox.ini ignore_errors = true -[testenv:py37-docs] +[testenv:docs] description = Build the HTML documentation deps = sphinx commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html whitelist_externals = make -[testenv:py37-flake8] +[testenv:flake8] description = Static code analysis and code style deps = flake8 commands = flake8 -[testenv:py37-isort] +[testenv:isort] description = Ensure imports are ordered skip_install = True deps = isort commands = isort tests setup.py analytical --check --diff -[testenv:py37-readme] +[testenv:readme] description = Ensure README renders on PyPI deps = twine commands = From 44e642c34b034f3f3615a0a6c3be9fd5f8aac6b7 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Dec 2020 00:18:09 +0100 Subject: [PATCH 054/103] Add clean Tox target (run `tox -e clean`) --- tox.ini | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tox.ini b/tox.ini index d8c12f8..9387fdd 100644 --- a/tox.ini +++ b/tox.ini @@ -67,6 +67,15 @@ commands = {envpython} setup.py -q sdist bdist_wheel twine check dist/* +[testenv:clean] +description = Clean up bytecode and build artifacts +deps = pyclean +commands = + pyclean -v {toxinidir} + rm -rf .tox/ build/ dist/ django_analytical.egg-info/ .coverage coverage.xml +whitelist_externals = + rm + [bandit] exclude = .cache,.git,.tox,build,dist,docs,tests targets = . From 8d449868da5b824c4028a5f34e9222bbbb4a0090 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 5 Dec 2020 00:10:04 +0100 Subject: [PATCH 055/103] Use job name suggested by @jezdez Co-authored-by: Jannis Leidel --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94491a4..28c6eaf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,7 +52,7 @@ jobs: python -m pip install --upgrade pip python -m pip install --upgrade tox tox-gh-actions - - name: Unit tests for py${{ matrix.python-version }}-django${{ matrix.django-version }} + - name: Tox tests (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) run: tox env: DJANGO: ${{ matrix.django-version }} From 051d46ceda930b03bfd7f6cad0222f1a694b48e8 Mon Sep 17 00:00:00 2001 From: merc1er Date: Wed, 2 Feb 2022 17:42:08 +0700 Subject: [PATCH 056/103] Fix typo --- docs/services/google_analytics_gtag.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 29b4941..8e55e3e 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -69,7 +69,7 @@ project :file:`settings.py` file:: If you do not set a property ID, the tracking code will not be rendered. -Please node that the accepted Property IDs should be one of the following formats: +Please note that the accepted Property IDs should be one of the following formats: - 'UA-XXXXXX-Y' - 'AW-XXXXXXXXXX' From a021e07e152b8deab913c8c1eaf16d8178e79de3 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Feb 2022 19:04:03 +0100 Subject: [PATCH 057/103] Improve GHA test jobs, move tool config to pyproject.toml (#202) * Move Tox tool config settings to pyproject.toml * Restore older Bandit version for predictive setup Bandit UX is seriously broken. Unfortunately, only <1.6 works predictably. * Exclude unsupported Python/Django combinations Use a more self-explanatory build job name for tests * Help pytest find the Django test project settings * Use clean range for Django 4.0 * Keep load on the GHA runners low, use newer Python for checks --- .bandit | 1 - .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 6 ++++- .gitignore | 1 + pyproject.toml | 19 ++++++++++++++ tox.ini | 49 ++++++++++++++++------------------- 6 files changed, 49 insertions(+), 29 deletions(-) delete mode 120000 .bandit create mode 100644 pyproject.toml diff --git a/.bandit b/.bandit deleted file mode 120000 index bf39a01..0000000 --- a/.bandit +++ /dev/null @@ -1 +0,0 @@ -tox.ini \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 59142fd..4784861 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: '3.8' - name: Get pip cache dir id: pip-cache diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28c6eaf..1f9df91 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: - master jobs: - build: + python-django: runs-on: ubuntu-latest strategy: max-parallel: 5 @@ -24,6 +24,10 @@ jobs: - '2.2' - '3.2' - '4.0' + exclude: + - { django-version: '2.2', python-version: '3.10' } + - { django-version: '4.0', python-version: '3.6' } + - { django-version: '4.0', python-version: '3.7' } steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index c702437..4f17f7d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.tox /.coverage /coverage.xml +/tests/reports /build /dist diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c51c02e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.bandit] +# Exclude/ignore of files is currently broken in Bandit. + +[tool.black] +color = true + +[tool.coverage.xml] +output = "tests/reports/coverage.xml" + +[tool.isort] +color_output = true +profile = "black" + +[tool.pylint.master] +output-format = "colorized" + +[tool.pytest.ini_options] +addopts = "--junitxml=tests/reports/unittests.xml --color=yes --verbose" +DJANGO_SETTINGS_MODULE = "tests.testproject.settings" diff --git a/tox.ini b/tox.ini index 9387fdd..cf70de9 100644 --- a/tox.ini +++ b/tox.ini @@ -17,48 +17,32 @@ deps = pytest-django django22: Django>=2.2,<3.0 django32: Django>=3.2,<4.0 - django40: Django>=4.0a1,<4.1 - + django40: Django>=4.0,<4.1 commands = coverage run -m pytest coverage xml -[gh-actions] -python = - 3.6: py36 - 3.7: py37 - 3.8: py38 - 3.9: py39 - 3.10: py310 - -[gh-actions:env] -DJANGO = - 2.2: django22 - 3.2: django32 - 4.0: django40 - [testenv:bandit] description = PyCQA security linter deps = bandit<1.6 -commands = - bandit -r --ini tox.ini -ignore_errors = true +commands = -bandit --ini tox.ini {posargs:-r .} [testenv:docs] description = Build the HTML documentation deps = sphinx commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html -whitelist_externals = make +allowlist_externals = make [testenv:flake8] description = Static code analysis and code style deps = flake8 -commands = flake8 +commands = flake8 {posargs} [testenv:isort] -description = Ensure imports are ordered +description = Ensure imports are ordered consistently +deps = isort[colors] +commands = isort --check-only --diff {posargs:analytical setup.py tests} skip_install = True -deps = isort -commands = isort tests setup.py analytical --check --diff [testenv:readme] description = Ensure README renders on PyPI @@ -73,13 +57,26 @@ deps = pyclean commands = pyclean -v {toxinidir} rm -rf .tox/ build/ dist/ django_analytical.egg-info/ .coverage coverage.xml -whitelist_externals = +allowlist_externals = rm [bandit] -exclude = .cache,.git,.tox,build,dist,docs,tests -targets = . +exclude = .git,.github,.tox,build,dist,docs,tests [flake8] exclude = .cache,.git,.tox,build,dist max-line-length = 100 + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 + 3.10: py310 + +[gh-actions:env] +DJANGO = + 2.2: django22 + 3.2: django32 + 4.0: django40 From 2fd2279ecb7ac8e3298a91081c66ab33d884cc8b Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sat, 5 Feb 2022 18:41:47 +0100 Subject: [PATCH 058/103] =?UTF-8?q?Rename=20default=20branch=20(master=20?= =?UTF-8?q?=E2=9E=9C=20main)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following the example of other popular free software projects, we also rename our default branch to remove terminology stemming from colonialism and slavery. --- .github/workflows/check.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- README.rst | 4 ++-- setup.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d773cf2..2836be2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -3,10 +3,10 @@ name: Check on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: build: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f9df91..9948809 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,10 +3,10 @@ name: Test on: pull_request: branches: - - master + - main push: branches: - - master + - main jobs: python-django: diff --git a/README.rst b/README.rst index c73a268..8cab4b7 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ an asynchronous version of the Javascript code if possible. .. |build-status| image:: https://github.com/jazzband/django-analytical/workflows/Test/badge.svg :target: https://github.com/jazzband/django-analytical/actions :alt: GitHub Actions -.. |coverage| image:: https://codecov.io/gh/jazzband/django-analytical/branch/master/graph/badge.svg +.. |coverage| image:: https://codecov.io/gh/jazzband/django-analytical/branch/main/graph/badge.svg :alt: Test coverage :target: https://codecov.io/gh/jazzband/django-analytical .. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg @@ -37,7 +37,7 @@ an asynchronous version of the Javascript code if possible. :alt: Python versions .. |license| image:: https://img.shields.io/pypi/l/django-analytical.svg :alt: Software license - :target: https://github.com/jazzband/django-analytical/blob/master/LICENSE.txt + :target: https://github.com/jazzband/django-analytical/blob/main/LICENSE.txt .. |gitter| image:: https://img.shields.io/gitter/room/jazzband/django-analytical.svg :alt: Gitter chat room :target: https://gitter.im/jazzband/django-analytical diff --git a/setup.py b/setup.py index 7e10d2c..4695f43 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ setup( python_requires='>=3.6', platforms=['any'], url='https://github.com/jazzband/django-analytical', - download_url='https://github.com/jazzband/django-analytical/archive/master.zip', + download_url='https://github.com/jazzband/django-analytical/archive/main.zip', project_urls={ 'Documentation': 'https://django-analytical.readthedocs.io/', }, From 023702fa75d1ae1cae68220fc42345254b708e0b Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 6 Mar 2022 18:01:13 +0100 Subject: [PATCH 059/103] Consolidate tox.ini and pyproject.toml --- .gitignore | 21 +++++----- pyproject.toml | 7 ++-- tox.ini | 111 ++++++++++++++++++++++++------------------------- 3 files changed, 69 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index 4f17f7d..c671177 100644 --- a/.gitignore +++ b/.gitignore @@ -2,18 +2,19 @@ /*.geany /.idea /.tox -/.coverage -/coverage.xml -/tests/reports - -/build -/dist -/docs/_build -/MANIFEST - -/docs/_templates/layout.html +/.vscode *.pyc *.pyo +/.coverage +/coverage.xml +/tests/*-report.json +/tests/*-report.xml + +/build +/dist +/docs/_build +/docs/_templates/layout.html +/MANIFEST *.egg-info diff --git a/pyproject.toml b/pyproject.toml index c51c02e..99679b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,12 @@ [tool.bandit] -# Exclude/ignore of files is currently broken in Bandit. +# Exclude/ignore of files and TOML reading is currently broken in Bandit +exclude = [".cache",".git",".github",".tox","build","dist","docs","tests"] [tool.black] color = true [tool.coverage.xml] -output = "tests/reports/coverage.xml" +output = "tests/coverage-report.xml" [tool.isort] color_output = true @@ -15,5 +16,5 @@ profile = "black" output-format = "colorized" [tool.pytest.ini_options] -addopts = "--junitxml=tests/reports/unittests.xml --color=yes --verbose" +addopts = "--junitxml=tests/unittests-report.xml --color=yes --verbose" DJANGO_SETTINGS_MODULE = "tests.testproject.settings" diff --git a/tox.ini b/tox.ini index cf70de9..e286479 100644 --- a/tox.ini +++ b/tox.ini @@ -10,63 +10,6 @@ envlist = readme docs -[testenv] -description = Unit tests -deps = - coverage - pytest-django - django22: Django>=2.2,<3.0 - django32: Django>=3.2,<4.0 - django40: Django>=4.0,<4.1 -commands = - coverage run -m pytest - coverage xml - -[testenv:bandit] -description = PyCQA security linter -deps = bandit<1.6 -commands = -bandit --ini tox.ini {posargs:-r .} - -[testenv:docs] -description = Build the HTML documentation -deps = sphinx -commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html -allowlist_externals = make - -[testenv:flake8] -description = Static code analysis and code style -deps = flake8 -commands = flake8 {posargs} - -[testenv:isort] -description = Ensure imports are ordered consistently -deps = isort[colors] -commands = isort --check-only --diff {posargs:analytical setup.py tests} -skip_install = True - -[testenv:readme] -description = Ensure README renders on PyPI -deps = twine -commands = - {envpython} setup.py -q sdist bdist_wheel - twine check dist/* - -[testenv:clean] -description = Clean up bytecode and build artifacts -deps = pyclean -commands = - pyclean -v {toxinidir} - rm -rf .tox/ build/ dist/ django_analytical.egg-info/ .coverage coverage.xml -allowlist_externals = - rm - -[bandit] -exclude = .git,.github,.tox,build,dist,docs,tests - -[flake8] -exclude = .cache,.git,.tox,build,dist -max-line-length = 100 - [gh-actions] python = 3.6: py36 @@ -80,3 +23,57 @@ DJANGO = 2.2: django22 3.2: django32 4.0: django40 + +[testenv] +description = Unit tests +deps = + coverage[toml] + pytest-django + django22: Django>=2.2,<3.0 + django32: Django>=3.2,<4.0 + django40: Django>=4.0,<4.1 +commands = + coverage run -m pytest {posargs} + coverage xml + +[testenv:bandit] +description = PyCQA security linter +deps = bandit +commands = -bandit {posargs:-r analytical} -v + +[testenv:docs] +description = Build the HTML documentation +deps = sphinx +commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html + +[testenv:flake8] +description = Static code analysis and code style +deps = flake8 +commands = flake8 {posargs} + +[testenv:isort] +description = Ensure imports are ordered consistently +deps = isort[colors] +commands = isort --check-only --diff {posargs:analytical setup.py tests} + +[testenv:readme] +description = Ensure README renders on PyPI +deps = + build + twine +commands = + python -m build + twine check dist/* + +[testenv:clean] +description = Clean up bytecode and build artifacts +deps = pyclean +commands = + pyclean {posargs:.} + rm -rf .tox/ build/ dist/ django_analytical.egg-info/ .coverage coverage.xml tests/coverage-report.xml tests/unittests-report.xml +allowlist_externals = + rm + +[flake8] +exclude = .cache,.git,.tox,build,dist +max-line-length = 100 From 52aa88f08ad166c5c9f18742ce203d2f4d69a1fc Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 11 Mar 2022 09:48:08 +0100 Subject: [PATCH 060/103] Use pathlib instead of os.path (we only support Python 3.6+) --- setup.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4695f43..ad95ec1 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,8 @@ -import os +""" +Packaging setup for django-analytical. +""" + +from pathlib import Path from setuptools import setup @@ -6,7 +10,7 @@ import analytical as package def read_file(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() + return (Path(__file__).resolve().parent / fname).open().read() setup( From d82ca0aed52ff55311f361e274e4efb28fc0a77e Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 11 Mar 2022 10:22:51 +0100 Subject: [PATCH 061/103] Use context manager for file reading --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ad95ec1..c6892cc 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,9 @@ import analytical as package def read_file(fname): - return (Path(__file__).resolve().parent / fname).open().read() + """Read content of a file in project folder.""" + with (Path(__file__).resolve().parent / fname).open() as file: + return file.read() setup( From a8be4ea814a8a2c8112ed6dea59b47587aa46e54 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Sun, 13 Mar 2022 16:42:41 +0100 Subject: [PATCH 062/103] Readability counts, code must be self-explanatory --- setup.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index c6892cc..2f35e30 100644 --- a/setup.py +++ b/setup.py @@ -9,10 +9,11 @@ from setuptools import setup import analytical as package -def read_file(fname): - """Read content of a file in project folder.""" - with (Path(__file__).resolve().parent / fname).open() as file: - return file.read() +def read_file(filename): + """Read a text file and return its contents.""" + project_home = Path(__file__).parent.resolve() + file_path = project_home / filename + return file_path.read_text(encoding="utf-8") setup( From ce90933ab1fd67a25f0e75b7bdb54ed44c20b720 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Tue, 15 Mar 2022 00:27:34 +0100 Subject: [PATCH 063/103] Release v3.1.0 Removed optional license field from metadata, following recommendations from packaging guide. See https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#license --- CHANGELOG.rst | 9 +++++++++ analytical/__init__.py | 5 ++--- setup.py | 1 - 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 84ea711..d104fae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,12 @@ +Version 3.1.0 +------------- +* Rename default branch from master to main (Peter Bittner, Jannis Leidel) +* Modernize packaging setup, add pyproject.toml (Peter Bittner) +* Integrate isort, reorganize imports (David Smith) +* Refactor test suite from Python unit tests to Pytest (David Smith) +* Add Heap integration (Garrett Coakley) +* Drop Django 3.1, cover Django 4.0 and Python 3.10 in test suite (David Smith) + Version 3.0.0 ------------- * Add support for Lucky Orange (Peter Bittner) diff --git a/analytical/__init__.py b/analytical/__init__.py index 9253891..bb1e232 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -4,6 +4,5 @@ Analytics service integration for Django projects __author__ = "Joost Cassee" __email__ = "joost@cassee.net" -__version__ = "3.0.0" -__copyright__ = "Copyright (C) 2011-2020 Joost Cassee and contributors" -__license__ = "MIT" +__version__ = "3.1.0" +__copyright__ = "Copyright (C) 2011-2022 Joost Cassee and contributors" diff --git a/setup.py b/setup.py index 2f35e30..4bf7a51 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,6 @@ def read_file(filename): setup( name='django-analytical', version=package.__version__, - license=package.__license__, description=package.__doc__.strip(), long_description=read_file('README.rst'), long_description_content_type='text/x-rst', From 844a178c0480bf1e7ce6b6447dd80daa96039659 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Tue, 15 Mar 2022 09:27:31 +0100 Subject: [PATCH 064/103] Simplify copyright notice Co-authored-by: David Smith <39445562+smithdc1@users.noreply.github.com> --- analytical/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytical/__init__.py b/analytical/__init__.py index bb1e232..7541c72 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -5,4 +5,4 @@ Analytics service integration for Django projects __author__ = "Joost Cassee" __email__ = "joost@cassee.net" __version__ = "3.1.0" -__copyright__ = "Copyright (C) 2011-2022 Joost Cassee and contributors" +__copyright__ = "Copyright (C) 2011 Joost Cassee and contributors" From ba59f396df0d740bae86c8bea7fad406b3d2af23 Mon Sep 17 00:00:00 2001 From: Kevin Olbrich Date: Tue, 5 Jul 2022 14:09:37 +0200 Subject: [PATCH 065/103] add GOOGLE_ANALYTICS_JS_SOURCE setting to allow custom source URL fixes https://github.com/jazzband/django-analytical/issues/212 --- analytical/templatetags/google_analytics_js.py | 9 ++++++++- docs/services/google_analytics_js.rst | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/analytical/templatetags/google_analytics_js.py b/analytical/templatetags/google_analytics_js.py index 59bdcfa..6f133bb 100644 --- a/analytical/templatetags/google_analytics_js.py +++ b/analytical/templatetags/google_analytics_js.py @@ -26,7 +26,7 @@ SETUP_CODE = """ (function(i,s,o,g,r,a,m){{i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){{ (i[r].q=i[r].q||[]).push(arguments)}},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -}})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); +}})(window,document,'script','{js_source}','ga'); ga('create', '{property_id}', 'auto', {create_fields}); {commands}ga('send', 'pageview'); @@ -70,10 +70,17 @@ class GoogleAnalyticsJsNode(Node): if display_features: commands.insert(0, REQUIRE_DISPLAY_FEATURES) + js_source = getattr( + settings, + 'GOOGLE_ANALYTICS_JS_SOURCE', + 'https://www.google-analytics.com/analytics.js' + ) + html = SETUP_CODE.format( property_id=self.property_id, create_fields=json.dumps(create_fields), commands="".join(commands), + js_source=js_source, ) if is_internal_ip(context, 'GOOGLE_ANALYTICS'): html = disable_html(html, 'Google Analytics') diff --git a/docs/services/google_analytics_js.rst b/docs/services/google_analytics_js.rst index a40e521..b064d2e 100644 --- a/docs/services/google_analytics_js.rst +++ b/docs/services/google_analytics_js.rst @@ -228,3 +228,11 @@ You can configure the `Cookie Expiration`_ feature by setting the The value is the cookie expiration in seconds or 0 to delete the cookie when the browser is closed. .. _`Cookie Expiration`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout + +Custom Javascript Source +---------------------- + +You can configure a custom URL for the javascript file by setting the +:const:`GOOGLE_ANALYTICS_JS_SOURCE` configuration setting:: + + GOOGLE_ANALYTICS_JS_SOURCE = 'https://www.example.com/analytics.js' From 688524305f842d6b75c6c27893749cfd7511dd0d Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 1 Jul 2022 00:50:12 +0200 Subject: [PATCH 066/103] Remove deprecated Piwik integration Please use our Matomo integration instead --- CHANGELOG.rst | 4 + analytical/templatetags/analytical.py | 1 - analytical/templatetags/piwik.py | 121 ----------------- docs/install.rst | 5 - docs/services/piwik.rst | 179 -------------------------- tests/unit/test_tag_piwik.py | 154 ---------------------- 6 files changed, 4 insertions(+), 460 deletions(-) delete mode 100644 analytical/templatetags/piwik.py delete mode 100644 docs/services/piwik.rst delete mode 100644 tests/unit/test_tag_piwik.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d104fae..1f990c9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,7 @@ +Unreleased +---------- +* Remove deprecated Piwik integration. Use Matomo instead! (Peter Bittner) + Version 3.1.0 ------------- * Rename default branch from master to main (Peter Bittner, Jannis Leidel) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index a7c364b..ee6aa42 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -35,7 +35,6 @@ TAG_MODULES = [ 'analytical.olark', 'analytical.optimizely', 'analytical.performable', - 'analytical.piwik', 'analytical.rating_mailru', 'analytical.snapengage', 'analytical.spring_metrics', diff --git a/analytical/templatetags/piwik.py b/analytical/templatetags/piwik.py deleted file mode 100644 index 88adb9c..0000000 --- a/analytical/templatetags/piwik.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Piwik template tags and filters. -""" - -import re -import warnings -from collections import namedtuple -from itertools import chain - -from django.conf import settings -from django.template import Library, Node, TemplateSyntaxError - -from analytical.utils import ( - disable_html, - get_identity, - get_required_setting, - is_internal_ip, -) - -# domain name (characters separated by a dot), optional port, optional URI path, no slash -DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$') - -# numeric ID -SITEID_RE = re.compile(r'^\d+$') - -TRACKING_CODE = """ - - -""" # noqa - -VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa -IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);' -DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);' - -DEFAULT_SCOPE = 'page' - -PiwikVar = namedtuple('PiwikVar', ('index', 'name', 'value', 'scope')) - - -register = Library() - - -@register.tag -def piwik(parser, token): - """ - Piwik tracking template tag. - - Renders Javascript code to track page visits. You must supply - your Piwik domain (plus optional URI path), and tracked site ID - in the ``PIWIK_DOMAIN_PATH`` and the ``PIWIK_SITE_ID`` setting. - - Custom variables can be passed in the ``piwik_vars`` context - variable. It is an iterable of custom variables as tuples like: - ``(index, name, value[, scope])`` where scope may be ``'page'`` - (default) or ``'visit'``. Index should be an integer and the - other parameters should be strings. - """ - warnings.warn('The Piwik module is deprecated; use the Matomo module.', DeprecationWarning) - bits = token.split_contents() - if len(bits) > 1: - raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) - return PiwikNode() - - -class PiwikNode(Node): - def __init__(self): - self.domain_path = \ - get_required_setting('PIWIK_DOMAIN_PATH', DOMAINPATH_RE, - "must be a domain name, optionally followed " - "by an URI path, no trailing slash (e.g. " - "piwik.example.com or my.piwik.server/path)") - self.site_id = \ - get_required_setting('PIWIK_SITE_ID', SITEID_RE, - "must be a (string containing a) number") - - def render(self, context): - custom_variables = context.get('piwik_vars', ()) - - complete_variables = (var if len(var) >= 4 else var + (DEFAULT_SCOPE,) - for var in custom_variables) - - variables_code = (VARIABLE_CODE % PiwikVar(*var)._asdict() - for var in complete_variables) - - commands = [] - if getattr(settings, 'PIWIK_DISABLE_COOKIES', False): - commands.append(DISABLE_COOKIES_CODE) - - userid = get_identity(context, 'piwik') - if userid is not None: - variables_code = chain(variables_code, ( - IDENTITY_CODE % {'userid': userid}, - )) - - html = TRACKING_CODE % { - 'url': self.domain_path, - 'siteid': self.site_id, - 'variables': '\n '.join(variables_code), - 'commands': '\n '.join(commands) - } - if is_internal_ip(context, 'PIWIK'): - html = disable_html(html, 'Piwik') - return html - - -def contribute_to_analytical(add_node): - PiwikNode() # ensure properly configured - add_node('body_bottom', PiwikNode) diff --git a/docs/install.rst b/docs/install.rst index ba82fbb..927e238 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -188,11 +188,6 @@ settings required to enable each service are listed here: PERFORMABLE_API_KEY = '123abc' -* :doc:`Piwik (deprecated, see Matomo) `:: - - PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path' - PIWIK_SITE_ID = '123' - * :doc:`Rating\@Mail.ru `:: RATING_MAILRU_COUNTER_ID = '1234567' diff --git a/docs/services/piwik.rst b/docs/services/piwik.rst deleted file mode 100644 index 30f6d9a..0000000 --- a/docs/services/piwik.rst +++ /dev/null @@ -1,179 +0,0 @@ -================================== -Piwik (deprecated) -- open source web analytics -================================== - -Piwik_ is an open analytics platform currently used by individuals, -companies and governments all over the world. With Piwik, your data -will always be yours, because you run your own analytics server. - -.. _Piwik: http://www.piwik.org/ - - -Deprecated -========== - -Note that Piwik is now known as Matomo. New projects should use the -Matomo integration. The Piwik integration in django-analytical is -deprecated and eventually will be removed. - - -Installation -============ - -To start using the Piwik 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 Piwik 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:`piwik-configuration`. - -The Piwik tracking code is inserted into templates using a template -tag. Load the :mod:`piwik` template tag library and insert the -:ttag:`piwik` 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 bottom of the HTML body as recommended by the -`Piwik best practice for Integration Plugins`_:: - - {% load piwik %} - ... - {% piwik %} - - - -.. _`Piwik best practice for Integration Plugins`: http://piwik.org/integrate/how-to/ - - - -.. _piwik-configuration: - -Configuration -============= - -Before you can use the Piwik integration, you must first define -domain name and optional URI path to your Piwik server, as well as -the Piwik ID of the website you're tracking with your Piwik server, -in your project settings. - - -Setting the domain ------------------- - -Your Django project needs to know where your Piwik server is located. -Typically, you'll have Piwik installed on a subdomain of its own -(e.g. ``piwik.example.com``), otherwise it runs in a subdirectory of -a website of yours (e.g. ``www.example.com/piwik``). Set -:const:`PIWIK_DOMAIN_PATH` in the project :file:`settings.py` file -accordingly:: - - PIWIK_DOMAIN_PATH = 'piwik.example.com' - -If you do not set a domain the tracking code will not be rendered. - - -Setting the site ID -------------------- - -Your Piwik server can track several websites. Each website has its -site ID (this is the ``idSite`` parameter in the query string of your -browser's address bar when you visit the Piwik Dashboard). Set -:const:`PIWIK_SITE_ID` in the project :file:`settings.py` file to -the value corresponding to the website you're tracking:: - - PIWIK_SITE_ID = '4' - -If you do not set the site ID the tracking code will not be rendered. - - -.. _piwik-uservars: - -User variables --------------- - -Piwik supports sending `custom variables`_ along with default statistics. If -you want to set a custom variable, use the context variable ``piwik_vars`` when -you render your template. It should be an iterable of custom variables -represented by tuples like: ``(index, name, value[, scope])``, where scope may -be ``'page'`` (default) or ``'visit'``. ``index`` should be an integer and the -other parameters should be strings. :: - - context = Context({ - 'piwik_vars': [(1, 'foo', 'Sir Lancelot of Camelot'), - (2, 'bar', 'To seek the Holy Grail', 'page'), - (3, 'spam', 'Blue', 'visit')] - }) - return some_template.render(context) - -Piwik default settings allow up to 5 custom variables for both scope. Variable -mapping between index and name must stay constant, or the latest name -override the previous one. - -If you use the same user variables in different views and its value can -be computed from the HTTP request, you can also set them in a context -processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list -in :file:`settings.py`. - -.. _`custom variables`: http://developer.piwik.org/guides/tracking-javascript-guide#custom-variables - - -.. _piwik-user-tracking: - -User tracking -------------- - -If you use the standard Django authentication system, you can allow Piwik to -`track individual users`_ by setting the :data:`ANALYTICAL_AUTO_IDENTIFY` -setting to :const:`True`. This is enabled by default. Piwik will identify -users based on their ``username``. - -If you disable this settings, or want to customize what user id to use, you can -set the context variable ``analytical_identity`` (for global configuration) or -``piwik_identity`` (for Piwik specific configuration). Setting one to -:const:`None` will disable the user tracking feature:: - - # Piwik will identify this user as 'BDFL' if ANALYTICAL_AUTO_IDENTIFY is True or unset - request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') - - # Piwik will identify this user as 'Guido van Rossum' - request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') - context = Context({ - 'piwik_identity': request.user.get_full_name() - }) - - # Piwik will not identify this user (but will still collect statistics) - request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') - context = Context({ - 'piwik_identity': None - }) - -.. _`track individual users`: http://developer.piwik.org/guides/tracking-javascript-guide#user-id - -Disabling cookies ------------------ - -If you want to `disable cookies`_, set :data:`PIWIKI_DISABLE_COOKIES` to -:const:`True`. This is disabled by default. - -.. _`disable cookies`: https://matomo.org/faq/general/faq_157/ - -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:`ANALYTICAL_INTERNAL_IPS` (which -takes the value of :const:`INTERNAL_IPS` by default) the tracking code -is commented out. See :ref:`identifying-visitors` for important -information about detecting the visitor IP address. - - ----- - -Thanks go to Piwik for providing an excellent web analytics platform -entirely for free! Consider donating_ to ensure that they continue -their development efforts in the spirit of open source and freedom -for our personal data. - -.. _donating: http://piwik.org/donate/ diff --git a/tests/unit/test_tag_piwik.py b/tests/unit/test_tag_piwik.py deleted file mode 100644 index c8c01e5..0000000 --- a/tests/unit/test_tag_piwik.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -Tests for the Piwik template tags and filters. -""" - -import pytest -from django.contrib.auth.models import User -from django.http import HttpRequest -from django.template import Context -from django.test.utils import override_settings -from utils import TagTestCase - -from analytical.templatetags.piwik import PiwikNode -from analytical.utils import AnalyticalException - - -@override_settings(PIWIK_DOMAIN_PATH='example.com', PIWIK_SITE_ID='345') -class PiwikTagTestCase(TagTestCase): - """ - Tests for the ``piwik`` template tag. - """ - - def test_tag(self): - r = self.render_tag('piwik', 'piwik') - assert '"//example.com/"' in r - assert "_paq.push(['setSiteId', 345]);" in r - assert 'img src="//example.com/piwik.php?idsite=345"' in r - - def test_node(self): - r = PiwikNode().render(Context({})) - assert '"//example.com/";' in r - assert "_paq.push(['setSiteId', 345]);" in r - assert 'img src="//example.com/piwik.php?idsite=345"' in r - - @override_settings(PIWIK_DOMAIN_PATH='example.com/piwik', - PIWIK_SITE_ID='345') - def test_domain_path_valid(self): - r = self.render_tag('piwik', 'piwik') - assert '"//example.com/piwik/"' in r - - @override_settings(PIWIK_DOMAIN_PATH='example.com:1234', - PIWIK_SITE_ID='345') - def test_domain_port_valid(self): - r = self.render_tag('piwik', 'piwik') - assert '"//example.com:1234/";' in r - - @override_settings(PIWIK_DOMAIN_PATH='example.com:1234/piwik', - PIWIK_SITE_ID='345') - def test_domain_port_path_valid(self): - r = self.render_tag('piwik', 'piwik') - assert '"//example.com:1234/piwik/"' in r - - @override_settings(PIWIK_DOMAIN_PATH=None) - def test_no_domain(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_SITE_ID=None) - def test_no_siteid(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_SITE_ID='x') - def test_siteid_not_a_number(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_DOMAIN_PATH='http://www.example.com') - def test_domain_protocol_invalid(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_DOMAIN_PATH='example.com/') - def test_domain_slash_invalid(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_DOMAIN_PATH='example.com:123:456') - def test_domain_multi_port(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_DOMAIN_PATH='example.com:') - def test_domain_incomplete_port(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_DOMAIN_PATH='example.com:/piwik') - def test_domain_uri_incomplete_port(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @override_settings(PIWIK_DOMAIN_PATH='example.com:12df') - def test_domain_port_invalid(self): - with pytest.raises(AnalyticalException): - PiwikNode() - - @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 = PiwikNode().render(context) - assert r.startswith('') - - def test_uservars(self): - context = Context({'piwik_vars': [(1, 'foo', 'foo_val'), - (2, 'bar', 'bar_val', 'page'), - (3, 'spam', 'spam_val', 'visit')]}) - r = PiwikNode().render(context) - for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', - '_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', - '_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']: - assert var_code in r - - @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) - def test_default_usertrack(self): - context = Context({ - 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum') - }) - r = PiwikNode().render(context) - var_code = '_paq.push(["setUserId", "BDFL"]);' - assert var_code in r - - def test_piwik_usertrack(self): - context = Context({ - 'piwik_identity': 'BDFL' - }) - r = PiwikNode().render(context) - var_code = '_paq.push(["setUserId", "BDFL"]);' - assert var_code in r - - def test_analytical_usertrack(self): - context = Context({ - 'analytical_identity': 'BDFL' - }) - r = PiwikNode().render(context) - var_code = '_paq.push(["setUserId", "BDFL"]);' - assert var_code in r - - @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) - def test_disable_usertrack(self): - context = Context({ - 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'), - 'piwik_identity': None - }) - r = PiwikNode().render(context) - var_code = '_paq.push(["setUserId", "BDFL"]);' - assert var_code not in r - - @override_settings(PIWIK_DISABLE_COOKIES=True) - def test_disable_cookies(self): - r = PiwikNode().render(Context({})) - assert "_paq.push(['disableCookies']);" in r From f487cb895c1fd6d10c075d8203e2ad22d9f644d0 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 1 Jul 2022 00:57:51 +0200 Subject: [PATCH 067/103] Fix Matomo noscript tag Reference: https://matomo.org/faq/how-to/faq_176/ --- analytical/templatetags/matomo.py | 2 +- tests/unit/test_tag_matomo.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/analytical/templatetags/matomo.py b/analytical/templatetags/matomo.py index 15334fb..c5b1c1c 100644 --- a/analytical/templatetags/matomo.py +++ b/analytical/templatetags/matomo.py @@ -37,7 +37,7 @@ TRACKING_CODE = """ g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); - + """ # noqa VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa diff --git a/tests/unit/test_tag_matomo.py b/tests/unit/test_tag_matomo.py index f842d29..6e9a850 100644 --- a/tests/unit/test_tag_matomo.py +++ b/tests/unit/test_tag_matomo.py @@ -23,13 +23,13 @@ class MatomoTagTestCase(TagTestCase): r = self.render_tag('matomo', 'matomo') assert '"//example.com/"' in r assert "_paq.push(['setSiteId', 345]);" in r - assert 'img src="//example.com/piwik.php?idsite=345"' in r + assert 'img src="//example.com/matomo.php?idsite=345"' in r def test_node(self): r = MatomoNode().render(Context({})) assert '"//example.com/";' in r assert "_paq.push(['setSiteId', 345]);" in r - assert 'img src="//example.com/piwik.php?idsite=345"' in r + assert 'img src="//example.com/matomo.php?idsite=345"' in r @override_settings(MATOMO_DOMAIN_PATH='example.com/matomo', MATOMO_SITE_ID='345') From 248e04aef526899bd73764b11a78629e585f07d0 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Thu, 7 Jul 2022 00:24:51 +0200 Subject: [PATCH 068/103] Fix outdated URLs that point to the Clickmap service Fixes #182 --- README.rst | 2 +- docs/services/clickmap.rst | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 8cab4b7..2fd9f12 100644 --- a/README.rst +++ b/README.rst @@ -77,7 +77,7 @@ Currently Supported Services * `Yandex.Metrica`_ web analytics .. _`Chartbeat`: http://www.chartbeat.com/ -.. _`Clickmap`: http://getclickmap.com/ +.. _`Clickmap`: http://clickmap.ch/ .. _`Clicky`: http://getclicky.com/ .. _`Crazy Egg`: http://www.crazyegg.com/ .. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/ diff --git a/docs/services/clickmap.rst b/docs/services/clickmap.rst index 5ee0192..d3d9b9f 100644 --- a/docs/services/clickmap.rst +++ b/docs/services/clickmap.rst @@ -2,9 +2,10 @@ Clickmap -- visual click tracking ================================== -`Clickmap`_ is a real-time heatmap tool to track mouse clicks and scroll paths of your website visitors. Gain intelligence about what's hot and what's not, and optimize your conversion with Clickmap. +`Clickmap`_ is a real-time heatmap tool to track mouse clicks and scroll paths of your website visitors. +Gain intelligence about what's hot and what's not, and optimize your conversion with Clickmap. -.. _`Clickmap`: http://www.getclickmap.com/ +.. _`Clickmap`: http://www.clickmap.ch/ .. clickmap-installation: @@ -44,7 +45,7 @@ Before you can use the Clickmap integration, you must first set your Clickmap Tracker ID. If you don't have a Clickmap account yet, `sign up`_ to get your Tracker ID. -.. _`sign up`: http://www.getclickmap.com/ +.. _`sign up`: http://www.clickmap.ch/ .. _clickmap-tracker-id: @@ -72,6 +73,6 @@ 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:`ANALYTICAL_INTERNAL_IPS` setting -(which is :const:`INTERNAL_IPS` by default,) the tracking code is +(which is :const:`INTERNAL_IPS` by default,) the tracking code is commented out. See :ref:`identifying-visitors` for important information about detecting the visitor IP address. From e596e20183ff58da57ab4d5c05bdb241fb57dcc1 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Thu, 14 Jul 2022 14:17:22 +0200 Subject: [PATCH 069/103] Remove apparently obsolete setup.cfg --- setup.cfg | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 93945f8..0000000 --- a/setup.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[build_sphinx] -source-dir = docs -build-dir = build/docs -all_files = 1 - -[upload_sphinx] -upload-dir = build/docs/html - -[tool:pytest] -DJANGO_SETTINGS_MODULE = tests.testproject.settings - -[isort] -profile = black From 7e28ee31f156bfa5b027d004af47805a51a3b55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Thu, 14 Jul 2022 11:56:27 +0200 Subject: [PATCH 070/103] add custom identity test --- tests/unit/test_utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 8a9bd13..816acda 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -46,6 +46,19 @@ class GetIdentityTestCase(TestCase): get_id = get_identity(Context({}), user=MyUser(identity='fake_id')) assert get_id == 'fake_id' + def test_custom_identity_specific_provider(self): + get_id = get_identity(Context({ + 'foo_provider_identity': 'bar', + 'analytical_identity': 'baz', + }), prefix='foo_provider') + assert get_id == 'bar' + + def test_custom_identity_general(self): + get_id = get_identity(Context({ + 'analytical_identity': 'baz', + }), prefix='foo_provider') + assert get_id == 'baz' + @override_settings(ANALYTICAL_DOMAIN="example.org") class GetDomainTestCase(TestCase): From 96f1ba98b1a6867eeff8f6862ae4e9aa2efde049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Thu, 14 Jul 2022 12:08:41 +0200 Subject: [PATCH 071/103] add more information about overriding identity to documentation --- docs/features.rst | 20 ++++++++++++++++++++ docs/services/google_analytics_gtag.rst | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/docs/features.rst b/docs/features.rst index 4485b92..6382bd5 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -68,3 +68,23 @@ and is enabled by default. To disable: Alternatively, add one of the variables to the context yourself when you render the template. + + +Changing the identity +********************* + +If you want to change identity of the user, that different providers are +sending, you can do it by setting the `analyitcal_identity` context variable:: + + context = RequestContext({'analytical_identity': user.uuid}) + return some_template.render(context) + +or in the template:: + + {% with analytical_identity=request.user.uuid|default:None %} + {% analytical_head_top %} + {% endwith %} + +If you want to change the identity only for specific provider, use the +`*_identity` context variable, where `*` is prefix specified on the provider +page. diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 8e55e3e..ae94625 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -95,3 +95,19 @@ Identifying authenticated users The username of an authenticated user is passed to Google Analytics automatically as the `user_id`. See :ref:`identifying-visitors`. + +According to `Google Analytics conditions `_ +you should avoid sending Personally Identifiable Information. +Using `username` as `user_id` might not be the best option. +To avoid that, you can change the identity +by setting `google_analytics_gtag_identity` (or `analytical_identity` to +affect all providers) context variable:: + + context = RequestContext({'google_analytics_gtag_identity': user.uuid}) + return some_template.render(context) + +or in the template:: + + {% with google_analytics_gtag_identity=request.user.uuid|default:None %} + {% analytical_head_top %} + {% endwith %} From 25979fd0c2d7541111f803f18203259759a32002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Fri, 15 Jul 2022 12:14:23 +0200 Subject: [PATCH 072/103] Update docs/features.rst Co-authored-by: Peter Bittner --- docs/features.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/features.rst b/docs/features.rst index 6382bd5..ae6d075 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -79,7 +79,9 @@ sending, you can do it by setting the `analyitcal_identity` context variable:: context = RequestContext({'analytical_identity': user.uuid}) return some_template.render(context) -or in the template:: +or in the template: + +.. code-block:: django {% with analytical_identity=request.user.uuid|default:None %} {% analytical_head_top %} From 83e4361d2a80d986f3332253245c7dedb1d55a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Fri, 15 Jul 2022 12:14:34 +0200 Subject: [PATCH 073/103] Update docs/features.rst Co-authored-by: Peter Bittner --- docs/features.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index ae6d075..3bf6a44 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -73,9 +73,11 @@ and is enabled by default. To disable: Changing the identity ********************* -If you want to change identity of the user, that different providers are -sending, you can do it by setting the `analyitcal_identity` context variable:: +If you want to override the identity of the logged-in user that the various +providers send you can do it by setting the ``analytical_identity`` context +variable in your view code: +.. code-block:: python context = RequestContext({'analytical_identity': user.uuid}) return some_template.render(context) From a82bde708a919bbf87deb021386c9f337cd12b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Fri, 15 Jul 2022 12:14:50 +0200 Subject: [PATCH 074/103] Update docs/services/google_analytics_gtag.rst Co-authored-by: Peter Bittner --- docs/services/google_analytics_gtag.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index ae94625..c9ff31d 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -96,7 +96,9 @@ Identifying authenticated users The username of an authenticated user is passed to Google Analytics automatically as the `user_id`. See :ref:`identifying-visitors`. -According to `Google Analytics conditions `_ +According to `Google Analytics conditions`_ you should avoid ... + +.. _`Google Analytics conditions`: https://developers.google.com/analytics/solutions/crm-integration#user_id you should avoid sending Personally Identifiable Information. Using `username` as `user_id` might not be the best option. To avoid that, you can change the identity From f56a779b7ad2721030d96b7318599bb9021575cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Fri, 15 Jul 2022 12:18:14 +0200 Subject: [PATCH 075/103] Update docs/features.rst Co-authored-by: Peter Bittner --- docs/features.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index 3bf6a44..609730d 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -89,6 +89,6 @@ or in the template: {% analytical_head_top %} {% endwith %} -If you want to change the identity only for specific provider, use the -`*_identity` context variable, where `*` is prefix specified on the provider -page. +If you want to change the identity only for specific provider use the +``*_identity`` context variable, where ``*``-prefix is the module name +of the specific provider. From 47a5a4d6a996e5f58b670b61fdd86325bfec7ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Fri, 15 Jul 2022 12:29:23 +0200 Subject: [PATCH 076/103] more fixes to the docs --- docs/features.rst | 27 +++++++++++++++++++++++++ docs/services/google_analytics_gtag.rst | 22 +++++++++++--------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index 609730d..1472859 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -78,6 +78,7 @@ providers send you can do it by setting the ``analytical_identity`` context variable in your view code: .. code-block:: python + context = RequestContext({'analytical_identity': user.uuid}) return some_template.render(context) @@ -89,6 +90,32 @@ or in the template: {% analytical_head_top %} {% endwith %} +or in context proessor: + +.. code-block:: python + + # FILE: myproject/context_processors.py + from django.conf import settings + + def get_identity(request): + return { + 'analytical_identity': 'some-value-here', + } + + # FILE: myproject/settings.py + TEMPLATES = [ + { + 'OPTIONS': { + 'context_processors': [ + 'myproject.context_processors.get_identity', + ], + }, + }, + ] + +That allows you as a developer to leave your view code untouched and +make sure that the variable is injected for all templates. + If you want to change the identity only for specific provider use the ``*_identity`` context variable, where ``*``-prefix is the module name of the specific provider. diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index c9ff31d..1a97cf5 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -94,22 +94,26 @@ Identifying authenticated users ------------------------------- The username of an authenticated user is passed to Google Analytics -automatically as the `user_id`. See :ref:`identifying-visitors`. +automatically as the ``user_id``. See :ref:`identifying-visitors`. -According to `Google Analytics conditions`_ you should avoid ... - -.. _`Google Analytics conditions`: https://developers.google.com/analytics/solutions/crm-integration#user_id -you should avoid sending Personally Identifiable Information. -Using `username` as `user_id` might not be the best option. +According to `Google Analytics conditions`_ you should avoid +sending Personally Identifiable Information. +Using ``username`` as ``user_id`` might not be the best option. To avoid that, you can change the identity -by setting `google_analytics_gtag_identity` (or `analytical_identity` to -affect all providers) context variable:: +by setting ``google_analytics_gtag_identity`` (or ``analytical_identity`` to +affect all providers) context variable: + +.. code-block:: python context = RequestContext({'google_analytics_gtag_identity': user.uuid}) return some_template.render(context) -or in the template:: +or in the template: + +.. code-block:: django {% with google_analytics_gtag_identity=request.user.uuid|default:None %} {% analytical_head_top %} {% endwith %} + +.. _`Google Analytics conditions`: https://developers.google.com/analytics/solutions/crm-integration#user_id From 5ad082df48e8c0256f7f93b42961ac0efe73f2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Mon, 18 Jul 2022 15:05:53 +0200 Subject: [PATCH 077/103] Update docs/features.rst Co-authored-by: Peter Bittner --- docs/features.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index 1472859..e804d78 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -68,8 +68,6 @@ and is enabled by default. To disable: Alternatively, add one of the variables to the context yourself when you render the template. - - Changing the identity ********************* From 19c43bef4a1756c892b7141f8479c7f7cf8d3f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Mon, 18 Jul 2022 15:06:04 +0200 Subject: [PATCH 078/103] Update docs/features.rst Co-authored-by: Peter Bittner --- docs/features.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features.rst b/docs/features.rst index e804d78..fc80172 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -88,7 +88,7 @@ or in the template: {% analytical_head_top %} {% endwith %} -or in context proessor: +or by implementing a context processor, e.g. .. code-block:: python From 858a05307b83425a364bff91f629b3022ca14a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Mon, 18 Jul 2022 15:06:25 +0200 Subject: [PATCH 079/103] Update docs/features.rst Co-authored-by: Peter Bittner --- docs/features.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features.rst b/docs/features.rst index fc80172..3a2566e 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -115,5 +115,5 @@ That allows you as a developer to leave your view code untouched and make sure that the variable is injected for all templates. If you want to change the identity only for specific provider use the -``*_identity`` context variable, where ``*``-prefix is the module name +``*_identity`` context variable, where the ``*`` prefix is the module name of the specific provider. From 2163c77684b3e09ff1c12abb54dd4150b27fac55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Tue, 19 Jul 2022 08:58:28 +0200 Subject: [PATCH 080/103] Add the missing get_identity prefix to google_analytics_gtag, test it (#217) --- .../templatetags/google_analytics_gtag.py | 2 +- tests/unit/test_tag_google_analytics_gtag.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index ecb5787..19bcc57 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -57,7 +57,7 @@ class GoogleAnalyticsGTagNode(Node): def render(self, context): other_fields = {} - identity = get_identity(context) + identity = get_identity(context, 'google_analytics_gtag') if identity is not None: other_fields['user_id'] = identity diff --git a/tests/unit/test_tag_google_analytics_gtag.py b/tests/unit/test_tag_google_analytics_gtag.py index 39e2460..6b5cd87 100644 --- a/tests/unit/test_tag_google_analytics_gtag.py +++ b/tests/unit/test_tag_google_analytics_gtag.py @@ -59,6 +59,28 @@ class GoogleAnalyticsTagTestCase(TagTestCase): r = GoogleAnalyticsGTagNode().render(Context({'user': User(username='test')})) assert "gtag('set', {'user_id': 'test'});" in r + def test_identity_context_specific_provider(self): + """ + The user_id variable must be set according to + google_analytics_gtag_identity variable in the context. + """ + r = GoogleAnalyticsGTagNode().render(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 + + def test_identity_context_general(self): + """ + The user_id variable must be set according to analytical_identity variable in the context. + """ + r = GoogleAnalyticsGTagNode().render(Context({ + 'analytical_identity': 'bar_analytical_identity', + 'user': User(username='test'), + })) + assert "gtag('set', {'user_id': 'bar_analytical_identity'});" in r + @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='G-12345678') def test_tag_with_measurement_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') From 6e367cdd2d52d7e0252abce1eaf21d9659498065 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Tue, 2 Aug 2022 22:56:06 +1000 Subject: [PATCH 081/103] docs: fix simple typo, convesions -> conversions There is a small typo in docs/services/spring_metrics.rst. Should read `conversions` rather than `convesions`. Signed-off-by: Tim Gates --- docs/services/spring_metrics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/services/spring_metrics.rst b/docs/services/spring_metrics.rst index 627625e..e80ebb9 100644 --- a/docs/services/spring_metrics.rst +++ b/docs/services/spring_metrics.rst @@ -2,7 +2,7 @@ Spring Metrics -- conversion tracking ===================================== -`Spring Metrics`_ is a convesions analysis tool. It shows you the top +`Spring Metrics`_ is a conversions analysis tool. It shows you the top converting sources, search keywords and landing pages. The real-time dashboard shows you how customers interact with your website and how to increase conversion. From 82fbbeb6b22dd6ea30355b62b2f3a45ec86ba15d Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Apr 2025 13:23:06 +0200 Subject: [PATCH 082/103] Migrate packaging from setup.py to pyproject.toml --- .github/workflows/check.yml | 16 ++--- .github/workflows/release.yml | 19 +++--- .github/workflows/test.yml | 37 +++++----- .gitignore | 3 + CHANGELOG.rst | 2 + analytical/__init__.py | 7 +- docs/conf.py | 6 +- pyproject.toml | 123 ++++++++++++++++++++++++++++++---- requirements.txt | 1 - setup.py | 62 ----------------- tox.ini | 99 ++++++++++++++++----------- 11 files changed, 216 insertions(+), 159 deletions(-) delete mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2836be2..9a7923e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -15,21 +15,21 @@ jobs: fail-fast: false matrix: env: - - isort - - flake8 - - bandit - - readme + - lint + - format + - audit + - package - docs steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.12' - name: Install prerequisites - run: python -m pip install --upgrade setuptools pip wheel tox + run: python -m pip install tox - name: Run ${{ matrix.env }} run: tox -e ${{ matrix.env }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4784861..2ae375f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,20 +5,24 @@ on: tags: - '*' +env: + PIP_DISABLE_PIP_VERSION_CHECK: '1' + PY_COLORS: '1' + jobs: build: if: github.repository == 'jazzband/django-analytical' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.12' - name: Get pip cache dir id: pip-cache @@ -26,7 +30,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: release-${{ hashFiles('**/setup.py') }} @@ -35,14 +39,11 @@ jobs: - name: Install dependencies run: | - python -m pip install -U pip - python -m pip install -U setuptools twine wheel + python -m pip install tox - name: Build package run: | - python setup.py --version - python setup.py sdist --format=gztar bdist_wheel - twine check dist/* + tox -e package - name: Upload packages to Jazzband if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9948809..3cab676 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,10 @@ on: branches: - main +env: + PIP_DISABLE_PIP_VERSION_CHECK: '1' + PY_COLORS: '1' + jobs: python-django: runs-on: ubuntu-latest @@ -15,25 +19,25 @@ jobs: max-parallel: 5 matrix: python-version: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - '3.10' + - '3.11' + - '3.12' + - '3.13' django-version: - - '2.2' - - '3.2' - - '4.0' + - '4.2' + - '5.1' + - '5.2' + include: + - { django-version: '4.2', python-version: '3.8' } + - { django-version: '4.2', python-version: '3.9' } exclude: - - { django-version: '2.2', python-version: '3.10' } - - { django-version: '4.0', python-version: '3.6' } - - { django-version: '4.0', python-version: '3.7' } + - { django-version: '4.2', python-version: '3.13' } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -43,18 +47,17 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: - ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} + ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }} restore-keys: | ${{ matrix.python-version }}-v1- - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install --upgrade tox tox-gh-actions + python -m pip install tox tox-gh-actions - name: Tox tests (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }}) run: tox @@ -62,6 +65,6 @@ jobs: DJANGO: ${{ matrix.django-version }} - name: Upload coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 with: name: Python ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index c671177..5275f3c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ /docs/_templates/layout.html /MANIFEST *.egg-info + +/requirements.txt +/uv.lock diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1f990c9..f3e8561 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,8 @@ Unreleased ---------- * Remove deprecated Piwik integration. Use Matomo instead! (Peter Bittner) +* Migrate packaging from setup.py to pyproject.toml with Ruff for linting + and formatting (Peter Bittner) Version 3.1.0 ------------- diff --git a/analytical/__init__.py b/analytical/__init__.py index 7541c72..cbdca74 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -1,8 +1,5 @@ """ -Analytics service integration for Django projects +Analytics service integration for Django projects. """ -__author__ = "Joost Cassee" -__email__ = "joost@cassee.net" -__version__ = "3.1.0" -__copyright__ = "Copyright (C) 2011 Joost Cassee and contributors" +__version__ = '3.2.0.dev0' diff --git a/docs/conf.py b/docs/conf.py index 1894cd9..215350a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,9 +28,9 @@ add_function_parentheses = True pygments_style = 'sphinx' intersphinx_mapping = { - 'http://docs.python.org/2.7': None, - 'http://docs.djangoproject.com/en/1.9': - 'http://docs.djangoproject.com/en/1.9/_objects/', + 'python': ('http://docs.python.org/3.13', None), + 'django': ('http://docs.djangoproject.com/en/stable', + 'http://docs.djangoproject.com/en/stable/_objects/'), } diff --git a/pyproject.toml b/pyproject.toml index 99679b2..b99677a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,115 @@ -[tool.bandit] -# Exclude/ignore of files and TOML reading is currently broken in Bandit -exclude = [".cache",".git",".github",".tox","build","dist","docs","tests"] +[build-system] +build-backend = "setuptools.build_meta" +requires = ["setuptools>=64"] -[tool.black] -color = true +[project] +name = "django-analytical" +dynamic = ["version"] +description = "Analytics service integration for Django projects" +readme = "README.rst" +authors = [ + {name = "Joost Cassee", email = "joost@cassee.net"}, + {name = "Joshua Krall", email = "joshuakrall@pobox.com"}, + {name = "Aleck Landgraf", email = "aleck.landgraf@buildingenergy.com"}, + {name = "Alexandre Pocquet", email = "apocquet@lecko.fr"}, + {name = "Bateau Knowledge", email = "info@bateauknowledge.nl"}, + {name = "Bogdan Bodnar", email = "bogdanbodnar@mail.com"}, + {name = "Brad Pitcher", email = "bradpitcher@gmail.com"}, + {name = "Corentin Mercier", email = "corentin@mercier.link"}, + {name = "Craig Bruce", email = "craig@eyesopen.com"}, + {name = "Daniel Vitiello", email = "ezdismissal@gmail.com"}, + {name = "David Smith", email = "smithdc@gmail.com"}, + {name = "Diederik van der Boor", email = "vdboor@edoburu.nl"}, + {name = "Eric Amador", email = "eric.amador14@gmail.com"}, + {name = "Eric Davis", email = "eric@davislv.com"}, + {name = "Eric Wang", email = "gnawrice@gmail.com"}, + {name = "Garrett Coakley", email = "garrettc@users.noreply.github.com"}, + {name = "Garrett Robinson", email = "garrett.f.robinson@gmail.com"}, + {name = "GreenKahuna", email = "info@greenkahuna.com"}, + {name = "Hugo Osvaldo Barrera", email = "hugo@barrera.io"}, + {name = "Ian Ramsay", email = "ianalexr@yahoo.com"}, + {name = "Iván Raskovsky", email = "raskovsky+git@gmail.com"}, + {name = "James Paden", email = "james@xemion.com"}, + {name = "Jannis Leidel", email = "jannis@leidel.info"}, + {name = "Julien Grenier", email = "julien.grenier42@gmail.com"}, + {name = "Kevin Olbrich", email = "ko@sv01.de"}, + {name = "Marc Bourqui", email = "m.bourqui@edsi-tech.com"}, + {name = "Martey Dodoo", email = "martey@mobolic.com"}, + {name = "Martín Gaitán", email = "gaitan@gmail.com"}, + {name = "Matthäus G. Chajdas", email = "dev@anteru.net"}, + {name = "Max Arnold", email = "arnold.maxim@gmail.com"}, + {name = "Nikolay Korotkiy", email = "sikmir@gmail.com"}, + {name = "Paul Oswald", email = "pauloswald@gmail.com"}, + {name = "Peter Bittner", email = "django@bittner.it"}, + {name = "Petr Dlouhý", email = "petr.dlouhy@email.cz"}, + {name = "Philippe O. Wagner", email = "admin@arteria.ch"}, + {name = "Pi Delport", email = "pjdelport@gmail.com"}, + {name = "Sandra Mau", email = "sandra.mau@gmail.com"}, + {name = "Scott Adams", email = "scottadams80@gmail.com"}, + {name = "Scott Karlin", email = "gitlab@karlin-online.com"}, + {name = "Sean Wallace", email = "sean@lowpro.ca"}, + {name = "Sid Mitra", email = "sidmitra.del@gmail.com"}, + {name = "Simon Ye", email = "sye737@gmail.com"}, + {name = "Steve Schwarz", email = "steve@agilitynerd.com"}, + {name = "Steven Skoczen", email = "steven.skoczen@wk.com"}, + {name = "Tim Gates", email = "tim.gates@iress.com"}, + {name = "Tinnet Coronam", email = "tinnet@coronam.net"}, + {name = "Uros Trebec", email = "uros@trebec.org"}, + {name = "Walter Renner", email = "walter.renner@me.com"}, +] +maintainers = [ + {name = "Jazzband community", email = "jazzband-bot@users.noreply.github.com"}, + {name = "Peter Bittner", email = "django@bittner.it"}, +] +keywords=[ + "django", + "analytics", +] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.1", + "Framework :: Django :: 5.2", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Software Development :: Libraries :: Python Modules", +] +requires-python = ">=3.8" +dependencies = [ + "django>=4.2", +] -[tool.coverage.xml] -output = "tests/coverage-report.xml" - -[tool.isort] -color_output = true -profile = "black" - -[tool.pylint.master] -output-format = "colorized" +[project.urls] +Homepage = "https://github.com/jazzband/django-analytical" +Documentation = "https://django-analytical.readthedocs.io/" [tool.pytest.ini_options] addopts = "--junitxml=tests/unittests-report.xml --color=yes --verbose" DJANGO_SETTINGS_MODULE = "tests.testproject.settings" + +[tool.ruff.format] +quote-style = "single" + +[tool.ruff.lint.flake8-quotes] +inline-quotes = "single" + +[tool.setuptools] +packages = [ + "analytical", + "analytical.templatetags", +] + +[tool.setuptools.dynamic] +version = {attr = "analytical.__version__"} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4298a2d..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Django>=2.2.* diff --git a/setup.py b/setup.py deleted file mode 100644 index 4bf7a51..0000000 --- a/setup.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Packaging setup for django-analytical. -""" - -from pathlib import Path - -from setuptools import setup - -import analytical as package - - -def read_file(filename): - """Read a text file and return its contents.""" - project_home = Path(__file__).parent.resolve() - file_path = project_home / filename - return file_path.read_text(encoding="utf-8") - - -setup( - name='django-analytical', - version=package.__version__, - description=package.__doc__.strip(), - long_description=read_file('README.rst'), - long_description_content_type='text/x-rst', - author=package.__author__, - author_email=package.__email__, - packages=[ - 'analytical', - 'analytical.templatetags', - ], - keywords=[ - 'django', - 'analytics', - ], - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: 2.2', - 'Framework :: Django :: 3.2', - 'Framework :: Django :: 4.0', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - python_requires='>=3.6', - platforms=['any'], - url='https://github.com/jazzband/django-analytical', - download_url='https://github.com/jazzband/django-analytical/archive/main.zip', - project_urls={ - 'Documentation': 'https://django-analytical.readthedocs.io/', - }, -) diff --git a/tox.ini b/tox.ini index e286479..9cc4be0 100644 --- a/tox.ini +++ b/tox.ini @@ -1,79 +1,98 @@ [tox] envlist = - isort - flake8 - bandit + lint + format + audit # Python/Django combinations that are officially supported - py{36,37,38,39}-django{22} - py{36,37,38,39,310}-django{32} - py{38,39,310}-django{40} - readme + py{38,39,310,311,312}-django{42} + py{310,311,312,313}-django{51} + py{310,311,312,313}-django{52} + package docs + clean [gh-actions] python = - 3.6: py36 - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 + 3.10: py310 + 3.11: py311 + 3.12: py312 + 3.13: py313 [gh-actions:env] DJANGO = - 2.2: django22 - 3.2: django32 - 4.0: django40 + 4.2: django42 + 5.1: django51 + 5.2: django52 [testenv] description = Unit tests deps = coverage[toml] pytest-django - django22: Django>=2.2,<3.0 - django32: Django>=3.2,<4.0 - django40: Django>=4.0,<4.1 + django42: Django>=4.2,<5.0 + django51: Django>=5.1,<5.2 + django52: Django>=5.2,<6.0 commands = coverage run -m pytest {posargs} coverage xml +[testenv:audit] +description = Scan for vulnerable dependencies +skip_install = true +deps = + pip-audit + uv +commands = + uv export --no-emit-project --no-hashes -o requirements.txt -q + pip-audit {posargs:-r requirements.txt --progress-spinner off} + [testenv:bandit] description = PyCQA security linter +skip_install = true deps = bandit -commands = -bandit {posargs:-r analytical} -v +commands = bandit {posargs:-r analytical} -v + +[testenv:clean] +description = Clean up bytecode and build artifacts +skip_install = true +deps = pyclean +commands = pyclean {posargs:. --debris --erase requirements.txt tests/unittests-report.xml --yes} [testenv:docs] description = Build the HTML documentation deps = sphinx commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html -[testenv:flake8] -description = Static code analysis and code style -deps = flake8 -commands = flake8 {posargs} +[testenv:format] +description = Ensure consistent code style (Ruff) +skip_install = true +deps = ruff +commands = ruff format {posargs:--check --diff .} -[testenv:isort] -description = Ensure imports are ordered consistently -deps = isort[colors] -commands = isort --check-only --diff {posargs:analytical setup.py tests} +[testenv:lint] +description = Lightening-fast linting (Ruff) +skip_install = true +deps = ruff +commands = ruff check {posargs:--output-format=full .} -[testenv:readme] -description = Ensure README renders on PyPI +[testenv:mypy] +description = Perform static type checking +deps = mypy +commands = mypy {posargs:.} + +[testenv:package] +description = Build package and check metadata (or upload package) +skip_install = true deps = build twine commands = python -m build - twine check dist/* - -[testenv:clean] -description = Clean up bytecode and build artifacts -deps = pyclean -commands = - pyclean {posargs:.} - rm -rf .tox/ build/ dist/ django_analytical.egg-info/ .coverage coverage.xml tests/coverage-report.xml tests/unittests-report.xml -allowlist_externals = - rm - -[flake8] -exclude = .cache,.git,.tox,build,dist -max-line-length = 100 + twine {posargs:check --strict} dist/* +passenv = + TWINE_USERNAME + TWINE_PASSWORD + TWINE_REPOSITORY_URL From a112ec445f5794656743301fbeaa62892f59a146 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Apr 2025 13:25:00 +0200 Subject: [PATCH 083/103] Reformat code using Ruff --- analytical/templatetags/analytical.py | 9 +- analytical/templatetags/chartbeat.py | 12 +- analytical/templatetags/clickmap.py | 8 +- analytical/templatetags/clicky.py | 4 +- analytical/templatetags/crazy_egg.py | 26 +- analytical/templatetags/facebook_pixel.py | 3 +- analytical/templatetags/gauges.py | 4 +- analytical/templatetags/google_analytics.py | 72 +++-- .../templatetags/google_analytics_gtag.py | 21 +- .../templatetags/google_analytics_js.py | 51 ++-- analytical/templatetags/gosquared.py | 6 +- analytical/templatetags/heap.py | 8 +- analytical/templatetags/hotjar.py | 3 +- analytical/templatetags/hubspot.py | 5 +- analytical/templatetags/intercom.py | 13 +- analytical/templatetags/kiss_insights.py | 14 +- analytical/templatetags/kiss_metrics.py | 28 +- analytical/templatetags/luckyorange.py | 3 +- analytical/templatetags/matomo.py | 42 +-- analytical/templatetags/mixpanel.py | 29 +- analytical/templatetags/olark.py | 46 ++- analytical/templatetags/optimizely.py | 6 +- analytical/templatetags/performable.py | 17 +- analytical/templatetags/rating_mailru.py | 6 +- analytical/templatetags/snapengage.py | 107 ++++--- analytical/templatetags/spring_metrics.py | 15 +- analytical/templatetags/uservoice.py | 18 +- analytical/templatetags/woopra.py | 7 +- analytical/templatetags/yandex_metrica.py | 8 +- analytical/utils.py | 14 +- docs/_ext/local.py | 24 +- docs/conf.py | 15 +- tests/testproject/templatetags/dummy.py | 4 +- tests/unit/test_tag_analytical.py | 2 +- tests/unit/test_tag_chartbeat.py | 48 ++-- tests/unit/test_tag_clicky.py | 16 +- tests/unit/test_tag_facebook_pixel.py | 30 +- tests/unit/test_tag_gauges.py | 10 +- tests/unit/test_tag_google_analytics.py | 46 +-- tests/unit/test_tag_google_analytics_gtag.py | 26 +- tests/unit/test_tag_google_analytics_js.py | 82 ++++-- tests/unit/test_tag_gosquared.py | 22 +- tests/unit/test_tag_heap.py | 4 +- tests/unit/test_tag_hotjar.py | 15 +- tests/unit/test_tag_intercom.py | 100 ++++--- tests/unit/test_tag_kiss_insights.py | 4 +- tests/unit/test_tag_kiss_metrics.py | 43 ++- tests/unit/test_tag_luckyorange.py | 23 +- tests/unit/test_tag_matomo.py | 57 ++-- tests/unit/test_tag_mixpanel.py | 15 +- tests/unit/test_tag_olark.py | 69 +++-- tests/unit/test_tag_rating_mailru.py | 4 +- tests/unit/test_tag_snapengage.py | 268 ++++++++++++------ tests/unit/test_tag_spring_metrics.py | 22 +- tests/unit/test_tag_uservoice.py | 6 +- tests/unit/test_tag_woopra.py | 58 ++-- tests/unit/test_tag_yandex_metrica.py | 5 +- tests/unit/test_utils.py | 34 ++- tests/unit/utils.py | 2 +- 59 files changed, 1055 insertions(+), 604 deletions(-) diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index ee6aa42..7ae606b 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -66,7 +66,7 @@ class AnalyticalNode(Node): self.nodes = [node_cls() for node_cls in template_nodes[location]] def render(self, context): - return "".join([node.render(context) for node in self.nodes]) + return ''.join([node.render(context) for node in self.nodes]) def _load_template_nodes(): @@ -82,14 +82,15 @@ def _load_template_nodes(): except AnalyticalException as e: logger.debug("not loading tags from '%s': %s", path, e) for location in TAG_LOCATIONS: - template_nodes[location] = sum((template_nodes[location][p] - for p in TAG_POSITIONS), []) + template_nodes[location] = sum( + (template_nodes[location][p] for p in TAG_POSITIONS), [] + ) return template_nodes def _import_tag_module(path): app_name, lib_name = path.rsplit('.', 1) - return import_module("%s.templatetags.%s" % (app_name, lib_name)) + return import_module('%s.templatetags.%s' % (app_name, lib_name)) template_nodes = _load_template_nodes() diff --git a/analytical/templatetags/chartbeat.py b/analytical/templatetags/chartbeat.py index 6ab7b84..c499935 100644 --- a/analytical/templatetags/chartbeat.py +++ b/analytical/templatetags/chartbeat.py @@ -12,7 +12,9 @@ from django.template import Library, Node, TemplateSyntaxError from analytical.utils import disable_html, get_required_setting, is_internal_ip USER_ID_RE = re.compile(r'^\d+$') -INIT_CODE = """""" +INIT_CODE = ( + """""" +) SETUP_CODE = """ '.\ - format(placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/' - '%(account_nr_1)s/%(account_nr_2)s.js') +SETUP_CODE = ''.format( + placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/' + '%(account_nr_1)s/%(account_nr_2)s.js' +) USERVAR_CODE = "CE2.set(%(varnr)d, '%(value)s');" @@ -38,7 +38,8 @@ class CrazyEggNode(Node): def __init__(self): self.account_nr = get_required_setting( 'CRAZY_EGG_ACCOUNT_NUMBER', - ACCOUNT_NUMBER_RE, "must be (a string containing) a number" + ACCOUNT_NUMBER_RE, + 'must be (a string containing) a number', ) def render(self, context): @@ -49,12 +50,15 @@ class CrazyEggNode(Node): values = (context.get('crazy_egg_var%d' % i) for i in range(1, 6)) params = [(i, v) for i, v in enumerate(values, 1) if v is not None] if params: - js = " ".join(USERVAR_CODE % { - 'varnr': varnr, - 'value': value, - } for (varnr, value) in params) - html = '%s\n' \ - '' % (html, js) + js = ' '.join( + USERVAR_CODE + % { + 'varnr': varnr, + 'value': value, + } + for (varnr, value) in params + ) + html = '%s\n' % (html, js) if is_internal_ip(context, 'CRAZY_EGG'): html = disable_html(html, 'Crazy Egg') return html diff --git a/analytical/templatetags/facebook_pixel.py b/analytical/templatetags/facebook_pixel.py index 6634686..e1b5e43 100644 --- a/analytical/templatetags/facebook_pixel.py +++ b/analytical/templatetags/facebook_pixel.py @@ -60,11 +60,12 @@ class _FacebookPixelNode(Node): """ Base class: override and provide code_template. """ + def __init__(self): self.pixel_id = get_required_setting( 'FACEBOOK_PIXEL_ID', re.compile(r'^\d+$'), - "must be (a string containing) a number", + 'must be (a string containing) a number', ) def render(self, context): diff --git a/analytical/templatetags/gauges.py b/analytical/templatetags/gauges.py index d4f9307..ee9c714 100644 --- a/analytical/templatetags/gauges.py +++ b/analytical/templatetags/gauges.py @@ -46,8 +46,8 @@ def gauges(parser, token): class GaugesNode(Node): def __init__(self): self.site_id = get_required_setting( - 'GAUGES_SITE_ID', SITE_ID_RE, - "must be a string looking like 'XXXXXXX'") + 'GAUGES_SITE_ID', SITE_ID_RE, "must be a string looking like 'XXXXXXX'" + ) def render(self, context): html = TRACKING_CODE % {'site_id': self.site_id} diff --git a/analytical/templatetags/google_analytics.py b/analytical/templatetags/google_analytics.py index 96f8808..a7640dc 100644 --- a/analytical/templatetags/google_analytics.py +++ b/analytical/templatetags/google_analytics.py @@ -45,8 +45,9 @@ DOMAIN_CODE = "_gaq.push(['_setDomainName', '%s']);" NO_ALLOW_HASH_CODE = "_gaq.push(['_setAllowHash', false]);" TRACK_PAGE_VIEW = "_gaq.push(['_trackPageview']);" ALLOW_LINKER_CODE = "_gaq.push(['_setAllowLinker', true]);" -CUSTOM_VAR_CODE = "_gaq.push(['_setCustomVar', %(index)s, '%(name)s', " \ - "'%(value)s', %(scope)s]);" +CUSTOM_VAR_CODE = ( + "_gaq.push(['_setCustomVar', %(index)s, '%(name)s', '%(value)s', %(scope)s]);" +) SITE_SPEED_CODE = "_gaq.push(['_trackPageLoadTime']);" ANONYMIZE_IP_CODE = "_gaq.push(['_gat._anonymizeIp']);" SAMPLE_RATE_CODE = "_gaq.push(['_setSampleRate', '%s']);" @@ -54,7 +55,10 @@ SITE_SPEED_SAMPLE_RATE_CODE = "_gaq.push(['_setSiteSpeedSampleRate', '%s']);" SESSION_COOKIE_TIMEOUT_CODE = "_gaq.push(['_setSessionCookieTimeout', '%s']);" VISITOR_COOKIE_TIMEOUT_CODE = "_gaq.push(['_setVisitorCookieTimeout', '%s']);" DEFAULT_SOURCE = ("'https://ssl' : 'http://www'", "'.google-analytics.com/ga.js'") -DISPLAY_ADVERTISING_SOURCE = ("'https://' : 'http://'", "'stats.g.doubleclick.net/dc.js'") +DISPLAY_ADVERTISING_SOURCE = ( + "'https://' : 'http://'", + "'stats.g.doubleclick.net/dc.js'", +) ZEROPLACES = decimal.Decimal('0') TWOPLACES = decimal.Decimal('0.01') @@ -80,8 +84,10 @@ def google_analytics(parser, token): class GoogleAnalyticsNode(Node): def __init__(self): self.property_id = get_required_setting( - 'GOOGLE_ANALYTICS_PROPERTY_ID', PROPERTY_ID_RE, - "must be a string looking like 'UA-XXXXXX-Y'") + 'GOOGLE_ANALYTICS_PROPERTY_ID', + PROPERTY_ID_RE, + "must be a string looking like 'UA-XXXXXX-Y'", + ) def render(self, context): commands = self._get_domain_commands(context) @@ -94,7 +100,7 @@ class GoogleAnalyticsNode(Node): source = DEFAULT_SOURCE html = SETUP_CODE % { 'property_id': self.property_id, - 'commands': " ".join(commands), + 'commands': ' '.join(commands), 'source_scheme': source[0], 'source_url': source[1], } @@ -104,15 +110,17 @@ class GoogleAnalyticsNode(Node): def _get_domain_commands(self, context): commands = [] - tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', - TRACK_SINGLE_DOMAIN) + tracking_type = getattr( + settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', TRACK_SINGLE_DOMAIN + ) if tracking_type == TRACK_SINGLE_DOMAIN: pass else: domain = get_domain(context, 'google_analytics') if domain is None: raise AnalyticalException( - "tracking multiple domains with Google Analytics requires a domain name") + 'tracking multiple domains with Google Analytics requires a domain name' + ) commands.append(DOMAIN_CODE % domain) commands.append(NO_ALLOW_HASH_CODE) if tracking_type == TRACK_MULTIPLE_DOMAINS: @@ -120,9 +128,7 @@ class GoogleAnalyticsNode(Node): return commands def _get_custom_var_commands(self, context): - values = ( - context.get('google_analytics_var%s' % i) for i in range(1, 6) - ) + values = (context.get('google_analytics_var%s' % i) for i in range(1, 6)) params = [(i, v) for i, v in enumerate(values, 1) if v is not None] commands = [] for index, var in params: @@ -132,12 +138,15 @@ class GoogleAnalyticsNode(Node): scope = var[2] except IndexError: scope = SCOPE_PAGE - commands.append(CUSTOM_VAR_CODE % { - 'index': index, - 'name': name, - 'value': value, - 'scope': scope, - }) + commands.append( + CUSTOM_VAR_CODE + % { + 'index': index, + 'name': name, + 'value': value, + 'scope': scope, + } + ) return commands def _get_other_commands(self, context): @@ -152,29 +161,42 @@ class GoogleAnalyticsNode(Node): if sampleRate is not False: value = decimal.Decimal(sampleRate) if not 0 <= value <= 100: - raise AnalyticalException("'GOOGLE_ANALYTICS_SAMPLE_RATE' must be >= 0 and <= 100") + raise AnalyticalException( + "'GOOGLE_ANALYTICS_SAMPLE_RATE' must be >= 0 and <= 100" + ) commands.append(SAMPLE_RATE_CODE % value.quantize(TWOPLACES)) - siteSpeedSampleRate = getattr(settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False) + siteSpeedSampleRate = getattr( + settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False + ) if siteSpeedSampleRate is not False: value = decimal.Decimal(siteSpeedSampleRate) if not 0 <= value <= 100: raise AnalyticalException( - "'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100") + "'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100" + ) commands.append(SITE_SPEED_SAMPLE_RATE_CODE % value.quantize(TWOPLACES)) - sessionCookieTimeout = getattr(settings, 'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT', False) + sessionCookieTimeout = getattr( + settings, 'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT', False + ) if sessionCookieTimeout is not False: value = decimal.Decimal(sessionCookieTimeout) if value < 0: - raise AnalyticalException("'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT' must be >= 0") + raise AnalyticalException( + "'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT' must be >= 0" + ) commands.append(SESSION_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES)) - visitorCookieTimeout = getattr(settings, 'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT', False) + visitorCookieTimeout = getattr( + settings, 'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT', False + ) if visitorCookieTimeout is not False: value = decimal.Decimal(visitorCookieTimeout) if value < 0: - raise AnalyticalException("'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT' must be >= 0") + raise AnalyticalException( + "'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT' must be >= 0" + ) commands.append(VISITOR_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES)) return commands diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index 19bcc57..f83f835 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -13,7 +13,9 @@ from analytical.utils import ( is_internal_ip, ) -PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$|^G-[a-zA-Z0-9]+$|^AW-[a-zA-Z0-9]+$|^DC-[a-zA-Z0-9]+$') +PROPERTY_ID_RE = re.compile( + r'^UA-\d+-\d+$|^G-[a-zA-Z0-9]+$|^AW-[a-zA-Z0-9]+$|^DC-[a-zA-Z0-9]+$' +) SETUP_CODE = """ -""" # noqa +""" # noqa register = Library() @@ -41,9 +41,9 @@ def heap(parser, token): class HeapNode(Node): def __init__(self): - self.tracker_id = get_required_setting('HEAP_TRACKER_ID', - HEAP_TRACKER_ID_RE, - "must be an numeric string") + self.tracker_id = get_required_setting( + 'HEAP_TRACKER_ID', HEAP_TRACKER_ID_RE, 'must be an numeric string' + ) def render(self, context): html = TRACKING_CODE % {'tracker_id': self.tracker_id} diff --git a/analytical/templatetags/hotjar.py b/analytical/templatetags/hotjar.py index 6ad2063..9c5bf38 100644 --- a/analytical/templatetags/hotjar.py +++ b/analytical/templatetags/hotjar.py @@ -41,12 +41,11 @@ def hotjar(parser, token): class HotjarNode(Node): - def __init__(self): self.site_id = get_required_setting( 'HOTJAR_SITE_ID', re.compile(r'^\d+$'), - "must be (a string containing) a number", + 'must be (a string containing) a number', ) def render(self, context): diff --git a/analytical/templatetags/hubspot.py b/analytical/templatetags/hubspot.py index 6aeab9f..cff51b8 100644 --- a/analytical/templatetags/hubspot.py +++ b/analytical/templatetags/hubspot.py @@ -41,8 +41,9 @@ def hubspot(parser, token): class HubSpotNode(Node): def __init__(self): - self.portal_id = get_required_setting('HUBSPOT_PORTAL_ID', PORTAL_ID_RE, - "must be a (string containing a) number") + self.portal_id = get_required_setting( + 'HUBSPOT_PORTAL_ID', PORTAL_ID_RE, 'must be a (string containing a) number' + ) def render(self, context): html = TRACKING_CODE % {'portal_id': self.portal_id} diff --git a/analytical/templatetags/intercom.py b/analytical/templatetags/intercom.py index fc47376..69ae77e 100644 --- a/analytical/templatetags/intercom.py +++ b/analytical/templatetags/intercom.py @@ -76,8 +76,8 @@ def intercom(parser, token): class IntercomNode(Node): def __init__(self): self.app_id = get_required_setting( - 'INTERCOM_APP_ID', APP_ID_RE, - "must be a string looking like 'XXXXXXX'") + 'INTERCOM_APP_ID', APP_ID_RE, "must be a string looking like 'XXXXXXX'" + ) def _identify(self, user): name = user.get_full_name() @@ -95,8 +95,7 @@ class IntercomNode(Node): user = get_user_from_context(context) if user is not None and get_user_is_authenticated(user): if 'name' not in params: - params['name'] = get_identity( - context, 'intercom', self._identify, user) + params['name'] = get_identity(context, 'intercom', self._identify, user) if 'email' not in params and user.email: params['email'] = user.email @@ -120,10 +119,8 @@ class IntercomNode(Node): def render(self, context): params = self._get_custom_attrs(context) - params["app_id"] = self.app_id - html = TRACKING_CODE % { - "settings_json": json.dumps(params, sort_keys=True) - } + params['app_id'] = self.app_id + html = TRACKING_CODE % {'settings_json': json.dumps(params, sort_keys=True)} if is_internal_ip(context, 'INTERCOM'): html = disable_html(html, 'Intercom') diff --git a/analytical/templatetags/kiss_insights.py b/analytical/templatetags/kiss_insights.py index f009d8f..4aa11e8 100644 --- a/analytical/templatetags/kiss_insights.py +++ b/analytical/templatetags/kiss_insights.py @@ -41,11 +41,15 @@ def kiss_insights(parser, token): class KissInsightsNode(Node): def __init__(self): self.account_number = get_required_setting( - 'KISS_INSIGHTS_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE, - "must be (a string containing) a number") + 'KISS_INSIGHTS_ACCOUNT_NUMBER', + ACCOUNT_NUMBER_RE, + 'must be (a string containing) a number', + ) self.site_code = get_required_setting( - 'KISS_INSIGHTS_SITE_CODE', SITE_CODE_RE, - "must be a string containing three characters") + 'KISS_INSIGHTS_SITE_CODE', + SITE_CODE_RE, + 'must be a string containing three characters', + ) def render(self, context): commands = [] @@ -59,7 +63,7 @@ class KissInsightsNode(Node): html = SETUP_CODE % { 'account_number': self.account_number, 'site_code': self.site_code, - 'commands': " ".join(commands), + 'commands': ' '.join(commands), } return html diff --git a/analytical/templatetags/kiss_metrics.py b/analytical/templatetags/kiss_metrics.py index dc91e44..73d09d5 100644 --- a/analytical/templatetags/kiss_metrics.py +++ b/analytical/templatetags/kiss_metrics.py @@ -63,8 +63,10 @@ def kiss_metrics(parser, token): class KissMetricsNode(Node): def __init__(self): self.api_key = get_required_setting( - 'KISS_METRICS_API_KEY', API_KEY_RE, - "must be a string containing a 40-digit hexadecimal number") + 'KISS_METRICS_API_KEY', + API_KEY_RE, + 'must be a string containing a 40-digit hexadecimal number', + ) def render(self, context): commands = [] @@ -79,22 +81,28 @@ class KissMetricsNode(Node): pass try: name, properties = context[EVENT_CONTEXT_KEY] - commands.append(EVENT_CODE % { - 'name': name, - 'properties': json.dumps(properties, sort_keys=True), - }) + commands.append( + EVENT_CODE + % { + 'name': name, + 'properties': json.dumps(properties, sort_keys=True), + } + ) except KeyError: pass try: properties = context[PROPERTY_CONTEXT_KEY] - commands.append(PROPERTY_CODE % { - 'properties': json.dumps(properties, sort_keys=True), - }) + commands.append( + PROPERTY_CODE + % { + 'properties': json.dumps(properties, sort_keys=True), + } + ) except KeyError: pass html = TRACKING_CODE % { 'api_key': self.api_key, - 'commands': " ".join(commands), + 'commands': ' '.join(commands), } if is_internal_ip(context, 'KISS_METRICS'): html = disable_html(html, 'KISSmetrics') diff --git a/analytical/templatetags/luckyorange.py b/analytical/templatetags/luckyorange.py index e9bba8e..0c22a81 100644 --- a/analytical/templatetags/luckyorange.py +++ b/analytical/templatetags/luckyorange.py @@ -39,12 +39,11 @@ def luckyorange(parser, token): class LuckyOrangeNode(Node): - def __init__(self): self.site_id = get_required_setting( 'LUCKYORANGE_SITE_ID', re.compile(r'^\d+$'), - "must be (a string containing) a number", + 'must be (a string containing) a number', ) def render(self, context): diff --git a/analytical/templatetags/matomo.py b/analytical/templatetags/matomo.py index c5b1c1c..a984841 100644 --- a/analytical/templatetags/matomo.py +++ b/analytical/templatetags/matomo.py @@ -40,9 +40,11 @@ TRACKING_CODE = """ """ # noqa -VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa +VARIABLE_CODE = ( + '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa +) IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);' -DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);' +DISABLE_COOKIES_CODE = "_paq.push(['disableCookies']);" DEFAULT_SCOPE = 'page' @@ -75,23 +77,27 @@ def matomo(parser, token): class MatomoNode(Node): def __init__(self): - self.domain_path = \ - get_required_setting('MATOMO_DOMAIN_PATH', DOMAINPATH_RE, - "must be a domain name, optionally followed " - "by an URI path, no trailing slash (e.g. " - "matomo.example.com or my.matomo.server/path)") - self.site_id = \ - get_required_setting('MATOMO_SITE_ID', SITEID_RE, - "must be a (string containing a) number") + self.domain_path = get_required_setting( + 'MATOMO_DOMAIN_PATH', + DOMAINPATH_RE, + 'must be a domain name, optionally followed ' + 'by an URI path, no trailing slash (e.g. ' + 'matomo.example.com or my.matomo.server/path)', + ) + self.site_id = get_required_setting( + 'MATOMO_SITE_ID', SITEID_RE, 'must be a (string containing a) number' + ) def render(self, context): custom_variables = context.get('matomo_vars', ()) - complete_variables = (var if len(var) >= 4 else var + (DEFAULT_SCOPE,) - for var in custom_variables) + complete_variables = ( + var if len(var) >= 4 else var + (DEFAULT_SCOPE,) for var in custom_variables + ) - variables_code = (VARIABLE_CODE % MatomoVar(*var)._asdict() - for var in complete_variables) + variables_code = ( + VARIABLE_CODE % MatomoVar(*var)._asdict() for var in complete_variables + ) commands = [] if getattr(settings, 'MATOMO_DISABLE_COOKIES', False): @@ -99,15 +105,15 @@ class MatomoNode(Node): userid = get_identity(context, 'matomo') if userid is not None: - variables_code = chain(variables_code, ( - IDENTITY_CODE % {'userid': userid}, - )) + variables_code = chain( + variables_code, (IDENTITY_CODE % {'userid': userid},) + ) html = TRACKING_CODE % { 'url': self.domain_path, 'siteid': self.site_id, 'variables': '\n '.join(variables_code), - 'commands': '\n '.join(commands) + 'commands': '\n '.join(commands), } if is_internal_ip(context, 'MATOMO'): html = disable_html(html, 'Matomo') diff --git a/analytical/templatetags/mixpanel.py b/analytical/templatetags/mixpanel.py index e9b95b7..996d772 100644 --- a/analytical/templatetags/mixpanel.py +++ b/analytical/templatetags/mixpanel.py @@ -25,7 +25,7 @@ e,d])};b.__SV=1.2}})(document,window.mixpanel||[]); """ # noqa IDENTIFY_CODE = "mixpanel.identify('%s');" -IDENTIFY_PROPERTIES = "mixpanel.people.set(%s);" +IDENTIFY_PROPERTIES = 'mixpanel.people.set(%s);' EVENT_CODE = "mixpanel.track('%(name)s', %(properties)s);" EVENT_CONTEXT_KEY = 'mixpanel_event' @@ -49,29 +49,38 @@ def mixpanel(parser, token): class MixpanelNode(Node): def __init__(self): self._token = get_required_setting( - 'MIXPANEL_API_TOKEN', MIXPANEL_API_TOKEN_RE, - "must be a string containing a 32-digit hexadecimal number") + 'MIXPANEL_API_TOKEN', + MIXPANEL_API_TOKEN_RE, + 'must be a string containing a 32-digit hexadecimal number', + ) def render(self, context): commands = [] identity = get_identity(context, 'mixpanel') if identity is not None: if isinstance(identity, dict): - commands.append(IDENTIFY_CODE % identity.get('id', identity.get('username'))) - commands.append(IDENTIFY_PROPERTIES % json.dumps(identity, sort_keys=True)) + commands.append( + IDENTIFY_CODE % identity.get('id', identity.get('username')) + ) + commands.append( + IDENTIFY_PROPERTIES % json.dumps(identity, sort_keys=True) + ) else: commands.append(IDENTIFY_CODE % identity) try: name, properties = context[EVENT_CONTEXT_KEY] - commands.append(EVENT_CODE % { - 'name': name, - 'properties': json.dumps(properties, sort_keys=True), - }) + commands.append( + EVENT_CODE + % { + 'name': name, + 'properties': json.dumps(properties, sort_keys=True), + } + ) except KeyError: pass html = TRACKING_CODE % { 'token': self._token, - 'commands': " ".join(commands), + 'commands': ' '.join(commands), } if is_internal_ip(context, 'MIXPANEL'): html = disable_html(html, 'Mixpanel') diff --git a/analytical/templatetags/olark.py b/analytical/templatetags/olark.py index 833a01f..34e5c4f 100644 --- a/analytical/templatetags/olark.py +++ b/analytical/templatetags/olark.py @@ -24,16 +24,29 @@ EMAIL_CODE = "olark('api.visitor.updateEmailAddress', {{emailAddress: '{0}'}});" EMAIL_CONTEXT_KEY = 'olark_email' STATUS_CODE = "olark('api.chat.updateVisitorStatus', {snippet: %s});" STATUS_CONTEXT_KEY = 'olark_status' -MESSAGE_CODE = "olark.configure('locale.%(key)s', \"%(msg)s\");" +MESSAGE_CODE = 'olark.configure(\'locale.%(key)s\', "%(msg)s");' MESSAGE_KEYS = { - "welcome_title", "chatting_title", "unavailable_title", - "busy_title", "away_message", "loading_title", "welcome_message", - "busy_message", "chat_input_text", "name_input_text", - "email_input_text", "offline_note_message", "send_button_text", - "offline_note_thankyou_text", "offline_note_error_text", - "offline_note_sending_text", "operator_is_typing_text", - "operator_has_stopped_typing_text", "introduction_error_text", - "introduction_messages", "introduction_submit_button_text", + 'welcome_title', + 'chatting_title', + 'unavailable_title', + 'busy_title', + 'away_message', + 'loading_title', + 'welcome_message', + 'busy_message', + 'chat_input_text', + 'name_input_text', + 'email_input_text', + 'offline_note_message', + 'send_button_text', + 'offline_note_thankyou_text', + 'offline_note_error_text', + 'offline_note_sending_text', + 'operator_is_typing_text', + 'operator_has_stopped_typing_text', + 'introduction_error_text', + 'introduction_messages', + 'introduction_submit_button_text', } register = Library() @@ -56,8 +69,10 @@ def olark(parser, token): class OlarkNode(Node): def __init__(self): self.site_id = get_required_setting( - 'OLARK_SITE_ID', SITE_ID_RE, - "must be a string looking like 'XXXX-XXX-XX-XXXX'") + 'OLARK_SITE_ID', + SITE_ID_RE, + "must be a string looking like 'XXXX-XXX-XX-XXXX'", + ) def render(self, context): extra_code = [] @@ -76,21 +91,22 @@ class OlarkNode(Node): except KeyError: pass try: - extra_code.append(STATUS_CODE % json.dumps(context[STATUS_CONTEXT_KEY], - sort_keys=True)) + extra_code.append( + STATUS_CODE % json.dumps(context[STATUS_CONTEXT_KEY], sort_keys=True) + ) except KeyError: pass extra_code.extend(self._get_configuration(context)) html = SETUP_CODE % { 'site_id': self.site_id, - 'extra_code': " ".join(extra_code), + 'extra_code': ' '.join(extra_code), } return html def _get_nickname(self, user): name = user.get_full_name() if name: - return "%s (%s)" % (name, user.username) + return '%s (%s)' % (name, user.username) else: return user.username diff --git a/analytical/templatetags/optimizely.py b/analytical/templatetags/optimizely.py index 83e72b3..91a8efc 100644 --- a/analytical/templatetags/optimizely.py +++ b/analytical/templatetags/optimizely.py @@ -33,8 +33,10 @@ def optimizely(parser, token): class OptimizelyNode(Node): def __init__(self): self.account_number = get_required_setting( - 'OPTIMIZELY_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE, - "must be a string looking like 'XXXXXXX'") + 'OPTIMIZELY_ACCOUNT_NUMBER', + ACCOUNT_NUMBER_RE, + "must be a string looking like 'XXXXXXX'", + ) def render(self, context): html = SETUP_CODE % {'account_number': self.account_number} diff --git a/analytical/templatetags/performable.py b/analytical/templatetags/performable.py index de84bcf..302a025 100644 --- a/analytical/templatetags/performable.py +++ b/analytical/templatetags/performable.py @@ -56,14 +56,14 @@ def performable(parser, token): class PerformableNode(Node): def __init__(self): self.api_key = get_required_setting( - 'PERFORMABLE_API_KEY', API_KEY_RE, - "must be a string looking like 'XXXXX'") + 'PERFORMABLE_API_KEY', API_KEY_RE, "must be a string looking like 'XXXXX'" + ) def render(self, context): html = SETUP_CODE % {'api_key': self.api_key} identity = get_identity(context, 'performable') if identity is not None: - html = "%s%s" % (IDENTIFY_CODE % identity, html) + html = '%s%s' % (IDENTIFY_CODE % identity, html) if is_internal_ip(context, 'PERFORMABLE'): html = disable_html(html, 'Performable') return html @@ -74,10 +74,13 @@ def performable_embed(hostname, page_id): """ Include a Performable landing page. """ - return mark_safe(EMBED_CODE % { - 'hostname': hostname, - 'page_id': page_id, - }) + return mark_safe( + EMBED_CODE + % { + 'hostname': hostname, + 'page_id': page_id, + } + ) def contribute_to_analytical(add_node): diff --git a/analytical/templatetags/rating_mailru.py b/analytical/templatetags/rating_mailru.py index bdcb444..0eaa5d8 100644 --- a/analytical/templatetags/rating_mailru.py +++ b/analytical/templatetags/rating_mailru.py @@ -48,8 +48,10 @@ def rating_mailru(parser, token): class RatingMailruNode(Node): def __init__(self): self.counter_id = get_required_setting( - 'RATING_MAILRU_COUNTER_ID', COUNTER_ID_RE, - "must be (a string containing) a number'") + 'RATING_MAILRU_COUNTER_ID', + COUNTER_ID_RE, + "must be (a string containing) a number'", + ) def render(self, context): html = COUNTER_CODE % { diff --git a/analytical/templatetags/snapengage.py b/analytical/templatetags/snapengage.py index ce54c79..6c13001 100644 --- a/analytical/templatetags/snapengage.py +++ b/analytical/templatetags/snapengage.py @@ -24,7 +24,9 @@ FORM_POSITION_TOP_RIGHT = 'tr' FORM_POSITION_BOTTOM_LEFT = 'bl' FORM_POSITION_BOTTOM_RIGHT = 'br' -WIDGET_ID_RE = re.compile(r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$') +WIDGET_ID_RE = re.compile( + r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$' +) SETUP_CODE = """ """ + ) def test_node(self): - assert GaugesNode().render(Context()) == """ + assert ( + GaugesNode().render(Context()) + == """ """ + ) @override_settings(GAUGES_SITE_ID=None) def test_no_account_number(self): diff --git a/tests/unit/test_tag_google_analytics.py b/tests/unit/test_tag_google_analytics.py index 35220dd..14aaeb2 100644 --- a/tests/unit/test_tag_google_analytics.py +++ b/tests/unit/test_tag_google_analytics.py @@ -20,8 +20,10 @@ from analytical.templatetags.google_analytics import ( from analytical.utils import AnalyticalException -@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', - GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN) +@override_settings( + GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN, +) class GoogleAnalyticsTagTestCase(TagTestCase): """ Tests for the ``google_analytics`` template tag. @@ -47,15 +49,19 @@ class GoogleAnalyticsTagTestCase(TagTestCase): with pytest.raises(AnalyticalException): GoogleAnalyticsNode() - @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, - GOOGLE_ANALYTICS_DOMAIN='example.com') + @override_settings( + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, + GOOGLE_ANALYTICS_DOMAIN='example.com', + ) def test_track_multiple_subdomains(self): r = GoogleAnalyticsNode().render(Context()) assert "_gaq.push(['_setDomainName', 'example.com']);" in r assert "_gaq.push(['_setAllowHash', false]);" in r - @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, - GOOGLE_ANALYTICS_DOMAIN='example.com') + @override_settings( + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, + GOOGLE_ANALYTICS_DOMAIN='example.com', + ) def test_track_multiple_domains(self): r = GoogleAnalyticsNode().render(Context()) assert "_gaq.push(['_setDomainName', 'example.com']);" in r @@ -63,12 +69,14 @@ class GoogleAnalyticsTagTestCase(TagTestCase): assert "_gaq.push(['_setAllowLinker', true]);" in r def test_custom_vars(self): - context = Context({ - 'google_analytics_var1': ('test1', 'foo'), - 'google_analytics_var2': ('test2', 'bar', SCOPE_VISITOR), - 'google_analytics_var4': ('test4', 'baz', SCOPE_SESSION), - 'google_analytics_var5': ('test5', 'qux', SCOPE_PAGE), - }) + context = Context( + { + 'google_analytics_var1': ('test1', 'foo'), + 'google_analytics_var2': ('test2', 'bar', SCOPE_VISITOR), + 'google_analytics_var4': ('test4', 'baz', SCOPE_SESSION), + 'google_analytics_var5': ('test5', 'qux', SCOPE_PAGE), + } + ) r = GoogleAnalyticsNode().render(context) assert "_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);" in r assert "_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);" in r @@ -83,10 +91,10 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_display_advertising(self): with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=False): r = GoogleAnalyticsNode().render(Context()) - assert "google-analytics.com/ga.js" in r + assert 'google-analytics.com/ga.js' in r with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True): r = GoogleAnalyticsNode().render(Context()) - assert "stats.g.doubleclick.net/dc.js" in r + assert 'stats.g.doubleclick.net/dc.js' in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -185,10 +193,12 @@ class GoogleAnalyticsTagTestCase(TagTestCase): GoogleAnalyticsNode().render(context) -@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', - GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, - GOOGLE_ANALYTICS_DOMAIN=None, - ANALYTICAL_DOMAIN=None) +@override_settings( + GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, + GOOGLE_ANALYTICS_DOMAIN=None, + ANALYTICAL_DOMAIN=None, +) class NoDomainTestCase(TestCase): def test_exception_without_domain(self): context = Context() diff --git a/tests/unit/test_tag_google_analytics_gtag.py b/tests/unit/test_tag_google_analytics_gtag.py index 6b5cd87..16c356f 100644 --- a/tests/unit/test_tag_google_analytics_gtag.py +++ b/tests/unit/test_tag_google_analytics_gtag.py @@ -64,21 +64,29 @@ class GoogleAnalyticsTagTestCase(TagTestCase): The user_id variable must be set according to google_analytics_gtag_identity variable in the context. """ - r = GoogleAnalyticsGTagNode().render(Context({ - 'google_analytics_gtag_identity': 'foo_gtag_identity', - 'analytical_identity': 'bar_analytical_identity', - 'user': User(username='test'), - })) + r = GoogleAnalyticsGTagNode().render( + 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 def test_identity_context_general(self): """ The user_id variable must be set according to analytical_identity variable in the context. """ - r = GoogleAnalyticsGTagNode().render(Context({ - 'analytical_identity': 'bar_analytical_identity', - 'user': User(username='test'), - })) + r = GoogleAnalyticsGTagNode().render( + Context( + { + 'analytical_identity': 'bar_analytical_identity', + 'user': User(username='test'), + } + ) + ) assert "gtag('set', {'user_id': 'bar_analytical_identity'});" in r @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='G-12345678') diff --git a/tests/unit/test_tag_google_analytics_js.py b/tests/unit/test_tag_google_analytics_js.py index ed4b216..d0e53ab 100644 --- a/tests/unit/test_tag_google_analytics_js.py +++ b/tests/unit/test_tag_google_analytics_js.py @@ -17,8 +17,10 @@ from analytical.templatetags.google_analytics_js import ( from analytical.utils import AnalyticalException -@override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', - GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN) +@override_settings( + GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN, +) class GoogleAnalyticsTagTestCase(TagTestCase): """ Tests for the ``google_analytics_js`` template tag. @@ -26,19 +28,25 @@ class GoogleAnalyticsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('google_analytics_js', 'google_analytics_js') - assert """(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + assert ( + """(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r +})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" + in r + ) assert "ga('create', 'UA-123456-7', 'auto', {});" in r assert "ga('send', 'pageview');" in r def test_node(self): r = GoogleAnalyticsJsNode().render(Context()) - assert """(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + assert ( + """(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) -})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r +})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" + in r + ) assert "ga('create', 'UA-123456-7', 'auto', {});" in r assert "ga('send', 'pageview');" in r @@ -52,27 +60,36 @@ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) with pytest.raises(AnalyticalException): GoogleAnalyticsJsNode() - @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, - GOOGLE_ANALYTICS_DOMAIN='example.com') + @override_settings( + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, + GOOGLE_ANALYTICS_DOMAIN='example.com', + ) def test_track_multiple_subdomains(self): r = GoogleAnalyticsJsNode().render(Context()) - assert """ga('create', 'UA-123456-7', 'auto', {"legacyCookieDomain": "example.com"}""" in r + assert ( + """ga('create', 'UA-123456-7', 'auto', {"legacyCookieDomain": "example.com"}""" + in r + ) - @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, - GOOGLE_ANALYTICS_DOMAIN='example.com') + @override_settings( + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, + GOOGLE_ANALYTICS_DOMAIN='example.com', + ) def test_track_multiple_domains(self): r = GoogleAnalyticsJsNode().render(Context()) assert "ga('create', 'UA-123456-7', 'auto', {" in r assert '"legacyCookieDomain": "example.com"' in r - assert '"allowLinker\": true' in r + assert '"allowLinker": true' in r def test_custom_vars(self): - context = Context({ - 'google_analytics_var1': ('test1', 'foo'), - 'google_analytics_var2': ('test2', 'bar'), - 'google_analytics_var4': ('test4', 1), - 'google_analytics_var5': ('test5', 2.2), - }) + context = Context( + { + 'google_analytics_var1': ('test1', 'foo'), + 'google_analytics_var2': ('test2', 'bar'), + 'google_analytics_var4': ('test4', 1), + 'google_analytics_var5': ('test5', 2.2), + } + ) r = GoogleAnalyticsJsNode().render(context) assert "ga('set', 'test1', 'foo');" in r assert "ga('set', 'test2', 'bar');" in r @@ -82,9 +99,12 @@ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) def test_display_advertising(self): with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True): r = GoogleAnalyticsJsNode().render(Context()) - assert """ga('create', 'UA-123456-7', 'auto', {}); + assert ( + """ga('create', 'UA-123456-7', 'auto', {}); ga('require', 'displayfeatures'); -ga('send', 'pageview');""" in r +ga('send', 'pageview');""" + in r + ) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): @@ -92,8 +112,7 @@ ga('send', 'pageview');""" in r req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsJsNode().render(context) - assert r.startswith( - '') @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True) @@ -131,12 +150,17 @@ ga('send', 'pageview');""" in r @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0) def test_set_site_speed_sample_rate_min(self): r = GoogleAnalyticsJsNode().render(Context()) - assert """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 0});""" in r + assert ( + """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 0});""" in r + ) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00') def test_set_site_speed_sample_rate_max(self): r = GoogleAnalyticsJsNode().render(Context()) - assert """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 100});""" in r + assert ( + """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 100});""" + in r + ) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1) def test_exception_whenset_site_speed_sample_rate_too_small(self): @@ -167,10 +191,12 @@ ga('send', 'pageview');""" in r GoogleAnalyticsJsNode().render(context) -@override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', - GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, - GOOGLE_ANALYTICS_DOMAIN=None, - ANALYTICAL_DOMAIN=None) +@override_settings( + GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', + GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, + GOOGLE_ANALYTICS_DOMAIN=None, + ANALYTICAL_DOMAIN=None, +) class NoDomainTestCase(TestCase): def test_exception_without_domain(self): context = Context() diff --git a/tests/unit/test_tag_gosquared.py b/tests/unit/test_tag_gosquared.py index 46ef40d..5c27653 100644 --- a/tests/unit/test_tag_gosquared.py +++ b/tests/unit/test_tag_gosquared.py @@ -39,17 +39,25 @@ class GoSquaredTagTestCase(TagTestCase): @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_auto_identify(self): - r = GoSquaredNode().render(Context({ - 'user': User(username='test', first_name='Test', last_name='User'), - })) + r = GoSquaredNode().render( + Context( + { + 'user': User(username='test', first_name='Test', last_name='User'), + } + ) + ) assert 'GoSquared.UserName = "Test User";' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_manual_identify(self): - r = GoSquaredNode().render(Context({ - 'user': User(username='test', first_name='Test', last_name='User'), - 'gosquared_identity': 'test_identity', - })) + r = GoSquaredNode().render( + Context( + { + 'user': User(username='test', first_name='Test', last_name='User'), + 'gosquared_identity': 'test_identity', + } + ) + ) assert 'GoSquared.UserName = "test_identity";' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) diff --git a/tests/unit/test_tag_heap.py b/tests/unit/test_tag_heap.py index 37ff046..906369c 100644 --- a/tests/unit/test_tag_heap.py +++ b/tests/unit/test_tag_heap.py @@ -20,11 +20,11 @@ class HeapTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('heap', 'heap') - assert "123456789" in r + assert '123456789' in r def test_node(self): r = HeapNode().render(Context({})) - assert "123456789" in r + assert '123456789' in r def test_tags_take_no_args(self): with pytest.raises(TemplateSyntaxError, match="'heap' takes no arguments"): diff --git a/tests/unit/test_tag_hotjar.py b/tests/unit/test_tag_hotjar.py index 4ad0f75..ba7f42e 100644 --- a/tests/unit/test_tag_hotjar.py +++ b/tests/unit/test_tag_hotjar.py @@ -1,6 +1,7 @@ """ Tests for the Hotjar template tags. """ + import pytest from django.http import HttpRequest from django.template import Context, Template, TemplateSyntaxError @@ -27,7 +28,6 @@ expected_html = """\ @override_settings(HOTJAR_SITE_ID='123456789') class HotjarTagTestCase(TagTestCase): - maxDiff = None def test_tag(self): @@ -44,13 +44,14 @@ class HotjarTagTestCase(TagTestCase): @override_settings(HOTJAR_SITE_ID=None) def test_no_id(self): - with pytest.raises(AnalyticalException, match="HOTJAR_SITE_ID setting is not set"): + with pytest.raises( + AnalyticalException, match='HOTJAR_SITE_ID setting is not set' + ): HotjarNode() @override_settings(HOTJAR_SITE_ID='invalid') def test_invalid_id(self): - expected_pattern = ( - r"^HOTJAR_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") + expected_pattern = r"^HOTJAR_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$" with pytest.raises(AnalyticalException, match=expected_pattern): HotjarNode() @@ -61,11 +62,13 @@ class HotjarTagTestCase(TagTestCase): context = Context({'request': request}) actual_html = HotjarNode().render(context) - disabled_html = '\n'.join([ + disabled_html = '\n'.join( + [ '', - ]) + ] + ) assert disabled_html == actual_html def test_contribute_to_analytical(self): diff --git a/tests/unit/test_tag_intercom.py b/tests/unit/test_tag_intercom.py index 149dfb7..f71e64a 100644 --- a/tests/unit/test_tag_intercom.py +++ b/tests/unit/test_tag_intercom.py @@ -15,7 +15,7 @@ from analytical.templatetags.intercom import IntercomNode, intercom_user_hash from analytical.utils import AnalyticalException -@override_settings(INTERCOM_APP_ID="abc123xyz") +@override_settings(INTERCOM_APP_ID='abc123xyz') class IntercomTagTestCase(TagTestCase): """ Tests for the ``intercom`` template tag. @@ -23,7 +23,9 @@ class IntercomTagTestCase(TagTestCase): def test_tag(self): rendered_tag = self.render_tag('intercom', 'intercom') - assert rendered_tag.strip().startswith(' -""" % {'user_id': user.pk} # noqa +""" + % {'user_id': user.pk} + ) # noqa @override_settings(INTERCOM_APP_ID=None) def test_no_account_number(self): @@ -59,32 +65,40 @@ class IntercomTagTestCase(TagTestCase): username='test', first_name='Firstname', last_name='Lastname', - email="test@example.com", + email='test@example.com', date_joined=now, ) - r = IntercomNode().render(Context({ - 'user': user, - })) + r = IntercomNode().render( + Context( + { + 'user': user, + } + ) + ) assert ( 'window.intercomSettings = {"app_id": "abc123xyz", "created_at": 1397074500, ' f'"email": "test@example.com", "name": "Firstname Lastname", "user_id": {user.pk}}};' ) in r def test_custom(self): - r = IntercomNode().render(Context({ - 'intercom_var1': 'val1', - 'intercom_var2': 'val2' - })) + r = IntercomNode().render( + Context({'intercom_var1': 'val1', 'intercom_var2': 'val2'}) + ) assert 'var1": "val1", "var2": "val2"' in r def test_identify_name_and_email(self): - r = IntercomNode().render(Context({ - 'user': User( - username='test', - first_name='Firstname', - last_name='Lastname', - email="test@example.com"), - })) + r = IntercomNode().render( + Context( + { + 'user': User( + username='test', + first_name='Firstname', + last_name='Lastname', + email='test@example.com', + ), + } + ) + ) assert '"email": "test@example.com", "name": "Firstname Lastname"' in r def test_identify_username_no_email(self): @@ -92,17 +106,25 @@ class IntercomTagTestCase(TagTestCase): assert '"name": "test"' in r, r def test_no_identify_when_explicit_name(self): - r = IntercomNode().render(Context({ - 'intercom_name': 'explicit', - 'user': User(username='implicit'), - })) + r = IntercomNode().render( + Context( + { + 'intercom_name': 'explicit', + 'user': User(username='implicit'), + } + ) + ) assert '"name": "explicit"' in r, r def test_no_identify_when_explicit_email(self): - r = IntercomNode().render(Context({ - 'intercom_email': 'explicit', - 'user': User(username='implicit'), - })) + r = IntercomNode().render( + Context( + { + 'intercom_email': 'explicit', + 'user': User(username='implicit'), + } + ) + ) assert '"email": "explicit"' in r, r @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') @@ -135,10 +157,14 @@ class IntercomTagTestCase(TagTestCase): """ 'user_hash' of context-provided `user_id`. """ - attrs = IntercomNode()._get_custom_attrs(Context({ - 'intercom_email': 'test@example.com', - 'intercom_user_id': '5', - })) + attrs = IntercomNode()._get_custom_attrs( + Context( + { + 'intercom_email': 'test@example.com', + 'intercom_user_id': '5', + } + ) + ) assert attrs == { 'created_at': None, 'email': 'test@example.com', @@ -152,9 +178,13 @@ class IntercomTagTestCase(TagTestCase): """ 'user_hash' of context-provided `email`. """ - attrs = IntercomNode()._get_custom_attrs(Context({ - 'intercom_email': 'test@example.com', - })) + attrs = IntercomNode()._get_custom_attrs( + Context( + { + 'intercom_email': 'test@example.com', + } + ) + ) assert attrs == { 'created_at': None, 'email': 'test@example.com', diff --git a/tests/unit/test_tag_kiss_insights.py b/tests/unit/test_tag_kiss_insights.py index e36e91b..e2e96fb 100644 --- a/tests/unit/test_tag_kiss_insights.py +++ b/tests/unit/test_tag_kiss_insights.py @@ -20,11 +20,11 @@ class KissInsightsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('kiss_insights', 'kiss_insights') - assert "//s3.amazonaws.com/ki.js/12345/abc.js" in r + assert '//s3.amazonaws.com/ki.js/12345/abc.js' in r def test_node(self): r = KissInsightsNode().render(Context()) - assert "//s3.amazonaws.com/ki.js/12345/abc.js" in r + assert '//s3.amazonaws.com/ki.js/12345/abc.js' in r @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER=None) def test_no_account_number(self): diff --git a/tests/unit/test_tag_kiss_metrics.py b/tests/unit/test_tag_kiss_metrics.py index 8abc274..129b435 100644 --- a/tests/unit/test_tag_kiss_metrics.py +++ b/tests/unit/test_tag_kiss_metrics.py @@ -21,11 +21,17 @@ class KissMetricsTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('kiss_metrics', 'kiss_metrics') - assert "//doug1izaerwt3.cloudfront.net/0123456789abcdef0123456789abcdef01234567.1.js" in r + assert ( + '//doug1izaerwt3.cloudfront.net/0123456789abcdef0123456789abcdef01234567.1.js' + in r + ) def test_node(self): r = KissMetricsNode().render(Context()) - assert "//doug1izaerwt3.cloudfront.net/0123456789abcdef0123456789abcdef01234567.1.js" in r + assert ( + '//doug1izaerwt3.cloudfront.net/0123456789abcdef0123456789abcdef01234567.1.js' + in r + ) @override_settings(KISS_METRICS_API_KEY=None) def test_no_api_key(self): @@ -53,22 +59,37 @@ class KissMetricsTagTestCase(TagTestCase): assert "_kmq.push(['identify', " not in r def test_event(self): - r = KissMetricsNode().render(Context({ - 'kiss_metrics_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}), - })) + r = KissMetricsNode().render( + Context( + { + 'kiss_metrics_event': ( + 'test_event', + {'prop1': 'val1', 'prop2': 'val2'}, + ), + } + ) + ) assert "_kmq.push(['record', 'test_event', " '{"prop1": "val1", "prop2": "val2"}]);' in r def test_property(self): - r = KissMetricsNode().render(Context({ - 'kiss_metrics_properties': {'prop1': 'val1', 'prop2': 'val2'}, - })) + r = KissMetricsNode().render( + Context( + { + 'kiss_metrics_properties': {'prop1': 'val1', 'prop2': 'val2'}, + } + ) + ) assert '_kmq.push([\'set\', {"prop1": "val1", "prop2": "val2"}]);' in r def test_alias(self): - r = KissMetricsNode().render(Context({ - 'kiss_metrics_alias': {'test': 'test_alias'}, - })) + r = KissMetricsNode().render( + Context( + { + 'kiss_metrics_alias': {'test': 'test_alias'}, + } + ) + ) assert "_kmq.push(['alias', 'test', 'test_alias']);" in r @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) diff --git a/tests/unit/test_tag_luckyorange.py b/tests/unit/test_tag_luckyorange.py index b22d053..20b56f6 100644 --- a/tests/unit/test_tag_luckyorange.py +++ b/tests/unit/test_tag_luckyorange.py @@ -1,6 +1,7 @@ """ Tests for the Lucky Orange template tags. """ + import pytest from django.http import HttpRequest from django.template import Context, Template, TemplateSyntaxError @@ -25,7 +26,6 @@ window.__lo_site_id = 123456; @override_settings(LUCKYORANGE_SITE_ID='123456') class LuckyOrangeTagTestCase(TagTestCase): - maxDiff = None def test_tag(self): @@ -37,18 +37,23 @@ class LuckyOrangeTagTestCase(TagTestCase): assert expected_html == html def test_tags_take_no_args(self): - with pytest.raises(TemplateSyntaxError, match="'luckyorange' takes no arguments"): - Template('{% load luckyorange %}{% luckyorange "arg" %}').render(Context({})) + with pytest.raises( + TemplateSyntaxError, match="'luckyorange' takes no arguments" + ): + Template('{% load luckyorange %}{% luckyorange "arg" %}').render( + Context({}) + ) @override_settings(LUCKYORANGE_SITE_ID=None) def test_no_id(self): - with pytest.raises(AnalyticalException, match="LUCKYORANGE_SITE_ID setting is not set"): + with pytest.raises( + AnalyticalException, match='LUCKYORANGE_SITE_ID setting is not set' + ): LuckyOrangeNode() @override_settings(LUCKYORANGE_SITE_ID='invalid') def test_invalid_id(self): - expected_pattern = ( - r"^LUCKYORANGE_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") + expected_pattern = r"^LUCKYORANGE_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$" with pytest.raises(AnalyticalException, match=expected_pattern): LuckyOrangeNode() @@ -59,11 +64,13 @@ class LuckyOrangeTagTestCase(TagTestCase): context = Context({'request': request}) actual_html = LuckyOrangeNode().render(context) - disabled_html = '\n'.join([ + disabled_html = '\n'.join( + [ '', - ]) + ] + ) assert disabled_html == actual_html def test_contribute_to_analytical(self): diff --git a/tests/unit/test_tag_matomo.py b/tests/unit/test_tag_matomo.py index 6e9a850..0765c12 100644 --- a/tests/unit/test_tag_matomo.py +++ b/tests/unit/test_tag_matomo.py @@ -31,20 +31,19 @@ class MatomoTagTestCase(TagTestCase): assert "_paq.push(['setSiteId', 345]);" in r assert 'img src="//example.com/matomo.php?idsite=345"' in r - @override_settings(MATOMO_DOMAIN_PATH='example.com/matomo', - MATOMO_SITE_ID='345') + @override_settings(MATOMO_DOMAIN_PATH='example.com/matomo', MATOMO_SITE_ID='345') def test_domain_path_valid(self): r = self.render_tag('matomo', 'matomo') assert '"//example.com/matomo/"' in r - @override_settings(MATOMO_DOMAIN_PATH='example.com:1234', - MATOMO_SITE_ID='345') + @override_settings(MATOMO_DOMAIN_PATH='example.com:1234', MATOMO_SITE_ID='345') def test_domain_port_valid(self): r = self.render_tag('matomo', 'matomo') assert '"//example.com:1234/";' in r - @override_settings(MATOMO_DOMAIN_PATH='example.com:1234/matomo', - MATOMO_SITE_ID='345') + @override_settings( + MATOMO_DOMAIN_PATH='example.com:1234/matomo', MATOMO_SITE_ID='345' + ) def test_domain_port_path_valid(self): r = self.render_tag('matomo', 'matomo') assert '"//example.com:1234/matomo/"' in r @@ -104,46 +103,54 @@ class MatomoTagTestCase(TagTestCase): assert r.endswith('-->') def test_uservars(self): - context = Context({'matomo_vars': [(1, 'foo', 'foo_val'), - (2, 'bar', 'bar_val', 'page'), - (3, 'spam', 'spam_val', 'visit')]}) + context = Context( + { + 'matomo_vars': [ + (1, 'foo', 'foo_val'), + (2, 'bar', 'bar_val', 'page'), + (3, 'spam', 'spam_val', 'visit'), + ] + } + ) r = MatomoNode().render(context) - for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', - '_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', - '_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']: + for var_code in [ + '_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', + '_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', + '_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);', + ]: assert var_code in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_default_usertrack(self): - context = Context({ - 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum') - }) + context = Context( + {'user': User(username='BDFL', first_name='Guido', last_name='van Rossum')} + ) r = MatomoNode().render(context) var_code = '_paq.push(["setUserId", "BDFL"]);' assert var_code in r def test_matomo_usertrack(self): - context = Context({ - 'matomo_identity': 'BDFL' - }) + context = Context({'matomo_identity': 'BDFL'}) r = MatomoNode().render(context) var_code = '_paq.push(["setUserId", "BDFL"]);' assert var_code in r def test_analytical_usertrack(self): - context = Context({ - 'analytical_identity': 'BDFL' - }) + context = Context({'analytical_identity': 'BDFL'}) r = MatomoNode().render(context) var_code = '_paq.push(["setUserId", "BDFL"]);' assert var_code in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_disable_usertrack(self): - context = Context({ - 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'), - 'matomo_identity': None - }) + context = Context( + { + 'user': User( + username='BDFL', first_name='Guido', last_name='van Rossum' + ), + 'matomo_identity': None, + } + ) r = MatomoNode().render(context) var_code = '_paq.push(["setUserId", "BDFL"]);' assert var_code not in r diff --git a/tests/unit/test_tag_mixpanel.py b/tests/unit/test_tag_mixpanel.py index 503dff2..8b2cb04 100644 --- a/tests/unit/test_tag_mixpanel.py +++ b/tests/unit/test_tag_mixpanel.py @@ -50,12 +50,19 @@ class MixpanelTagTestCase(TagTestCase): @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = MixpanelNode().render(Context({'user': AnonymousUser()})) - assert "mixpanel.register_once({distinct_id:" not in r + assert 'mixpanel.register_once({distinct_id:' not in r def test_event(self): - r = MixpanelNode().render(Context({ - 'mixpanel_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}), - })) + r = MixpanelNode().render( + Context( + { + 'mixpanel_event': ( + 'test_event', + {'prop1': 'val1', 'prop2': 'val2'}, + ), + } + ) + ) assert "mixpanel.track('test_event', " '{"prop1": "val1", "prop2": "val2"});' in r diff --git a/tests/unit/test_tag_olark.py b/tests/unit/test_tag_olark.py index 95079f2..34ba68b 100644 --- a/tests/unit/test_tag_olark.py +++ b/tests/unit/test_tag_olark.py @@ -38,10 +38,17 @@ class OlarkTestCase(TagTestCase): @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): - r = OlarkNode().render(Context({ - 'user': User(username='test', first_name='Test', last_name='User'), - })) - assert "olark('api.chat.updateVisitorNickname', {snippet: 'Test User (test)'});" in r + r = OlarkNode().render( + Context( + { + 'user': User(username='test', first_name='Test', last_name='User'), + } + ) + ) + assert ( + "olark('api.chat.updateVisitorNickname', {snippet: 'Test User (test)'});" + in r + ) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): @@ -58,37 +65,41 @@ class OlarkTestCase(TagTestCase): '{snippet: "teststatus"});' in r def test_status_string_list(self): - r = OlarkNode().render(Context({ - 'olark_status': ['teststatus1', 'teststatus2'], - })) + r = OlarkNode().render( + Context( + { + 'olark_status': ['teststatus1', 'teststatus2'], + } + ) + ) assert "olark('api.chat.updateVisitorStatus', " '{snippet: ["teststatus1", "teststatus2"]});' in r def test_messages(self): messages = [ - "welcome_title", - "chatting_title", - "unavailable_title", - "busy_title", - "away_message", - "loading_title", - "welcome_message", - "busy_message", - "chat_input_text", - "name_input_text", - "email_input_text", - "offline_note_message", - "send_button_text", - "offline_note_thankyou_text", - "offline_note_error_text", - "offline_note_sending_text", - "operator_is_typing_text", - "operator_has_stopped_typing_text", - "introduction_error_text", - "introduction_messages", - "introduction_submit_button_text", + 'welcome_title', + 'chatting_title', + 'unavailable_title', + 'busy_title', + 'away_message', + 'loading_title', + 'welcome_message', + 'busy_message', + 'chat_input_text', + 'name_input_text', + 'email_input_text', + 'offline_note_message', + 'send_button_text', + 'offline_note_thankyou_text', + 'offline_note_error_text', + 'offline_note_sending_text', + 'operator_is_typing_text', + 'operator_has_stopped_typing_text', + 'introduction_error_text', + 'introduction_messages', + 'introduction_submit_button_text', ] vars = {f'olark_{m}': m for m in messages} r = OlarkNode().render(Context(vars)) for m in messages: - assert f"olark.configure('locale.{m}', \"{m}\");" in r + assert f'olark.configure(\'locale.{m}\', "{m}");' in r diff --git a/tests/unit/test_tag_rating_mailru.py b/tests/unit/test_tag_rating_mailru.py index 16aa794..4493b72 100644 --- a/tests/unit/test_tag_rating_mailru.py +++ b/tests/unit/test_tag_rating_mailru.py @@ -20,11 +20,11 @@ class RatingMailruTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('rating_mailru', 'rating_mailru') - assert "counter?id=1234567;js=na" in r + assert 'counter?id=1234567;js=na' in r def test_node(self): r = RatingMailruNode().render(Context({})) - assert "counter?id=1234567;js=na" in r + assert 'counter?id=1234567;js=na' in r @override_settings(RATING_MAILRU_COUNTER_ID=None) def test_no_site_id(self): diff --git a/tests/unit/test_tag_snapengage.py b/tests/unit/test_tag_snapengage.py index e1f6ca2..51ac8ef 100644 --- a/tests/unit/test_tag_snapengage.py +++ b/tests/unit/test_tag_snapengage.py @@ -29,7 +29,7 @@ WIDGET_ID = 'ec329c69-0bf0-4db8-9b77-3f8150fb977e' SNAPENGAGE_WIDGET_ID=WIDGET_ID, SNAPENGAGE_BUTTON=BUTTON_STYLE_DEFAULT, SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_LEFT, - SNAPENGAGE_BUTTON_OFFSET="55%", + SNAPENGAGE_BUTTON_OFFSET='55%', ) class SnapEngageTestCase(TagTestCase): """ @@ -38,11 +38,15 @@ class SnapEngageTestCase(TagTestCase): def test_tag(self): r = self.render_tag('snapengage', 'snapengage') - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + ) def test_node(self): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + ) @override_settings(SNAPENGAGE_WIDGET_ID=None) def test_no_site_id(self): @@ -55,142 +59,220 @@ class SnapEngageTestCase(TagTestCase): SnapEngageNode() def test_no_button(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button': BUTTON_STYLE_NONE, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_button': BUTTON_STYLE_NONE, + } + ) + ) assert 'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_NONE): r = SnapEngageNode().render(Context()) assert 'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r def test_live_button(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button': BUTTON_STYLE_LIVE, - })) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%",true);' in r + r = SnapEngageNode().render( + Context( + { + 'snapengage_button': BUTTON_STYLE_LIVE, + } + ) + ) + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%",true);' + in r + ) with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_LIVE): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%",true);' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%",true);' + in r + ) def test_custom_button(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button': "http://www.example.com/button.png", - })) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + r = SnapEngageNode().render( + Context( + { + 'snapengage_button': 'http://www.example.com/button.png', + } + ) + ) + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + ) assert 'SnapABug.setButton("http://www.example.com/button.png");' in r - with override_settings( - SNAPENGAGE_BUTTON="http://www.example.com/button.png"): + with override_settings(SNAPENGAGE_BUTTON='http://www.example.com/button.png'): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","55%");' + in r + ) assert 'SnapABug.setButton("http://www.example.com/button.png");' in r def test_button_location_right(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button_location': BUTTON_LOCATION_RIGHT, - })) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1","55%");' in r + r = SnapEngageNode().render( + Context( + { + 'snapengage_button_location': BUTTON_LOCATION_RIGHT, + } + ) + ) + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1","55%");' in r + ) with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_RIGHT): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1","55%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1","55%");' + in r + ) def test_button_location_top(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button_location': BUTTON_LOCATION_TOP, - })) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2","55%");' in r + r = SnapEngageNode().render( + Context( + { + 'snapengage_button_location': BUTTON_LOCATION_TOP, + } + ) + ) + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2","55%");' in r + ) with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_TOP): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2","55%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2","55%");' + in r + ) def test_button_location_bottom(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button_location': BUTTON_LOCATION_BOTTOM, - })) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3","55%");' in r - with override_settings( - SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_BOTTOM): + r = SnapEngageNode().render( + Context( + { + 'snapengage_button_location': BUTTON_LOCATION_BOTTOM, + } + ) + ) + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3","55%");' in r + ) + with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_BOTTOM): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3","55%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3","55%");' + in r + ) def test_button_offset(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button_location_offset': "30%", - })) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","30%");' in r - with override_settings(SNAPENGAGE_BUTTON_LOCATION_OFFSET="30%"): + r = SnapEngageNode().render( + Context( + { + 'snapengage_button_location_offset': '30%', + } + ) + ) + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","30%");' in r + ) + with override_settings(SNAPENGAGE_BUTTON_LOCATION_OFFSET='30%'): r = SnapEngageNode().render(Context()) - assert 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","30%");' in r + assert ( + 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0","30%");' + in r + ) def test_button_effect(self): - r = SnapEngageNode().render(Context({ - 'snapengage_button_effect': "-4px", - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_button_effect': '-4px', + } + ) + ) assert 'SnapABug.setButtonEffect("-4px");' in r - with override_settings(SNAPENGAGE_BUTTON_EFFECT="-4px"): + with override_settings(SNAPENGAGE_BUTTON_EFFECT='-4px'): r = SnapEngageNode().render(Context()) assert 'SnapABug.setButtonEffect("-4px");' in r def test_form_position(self): - r = SnapEngageNode().render(Context({ - 'snapengage_form_position': FORM_POSITION_TOP_LEFT, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_form_position': FORM_POSITION_TOP_LEFT, + } + ) + ) assert 'SnapABug.setChatFormPosition("tl");' in r with override_settings(SNAPENGAGE_FORM_POSITION=FORM_POSITION_TOP_LEFT): r = SnapEngageNode().render(Context()) assert 'SnapABug.setChatFormPosition("tl");' in r def test_form_top_position(self): - r = SnapEngageNode().render(Context({ - 'snapengage_form_top_position': 40, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_form_top_position': 40, + } + ) + ) assert 'SnapABug.setFormTopPosition(40);' in r with override_settings(SNAPENGAGE_FORM_TOP_POSITION=40): r = SnapEngageNode().render(Context()) assert 'SnapABug.setFormTopPosition(40);' in r def test_domain(self): - r = SnapEngageNode().render(Context({ - 'snapengage_domain': "example.com"})) + r = SnapEngageNode().render(Context({'snapengage_domain': 'example.com'})) assert 'SnapABug.setDomain("example.com");' in r - with override_settings(SNAPENGAGE_DOMAIN="example.com"): + with override_settings(SNAPENGAGE_DOMAIN='example.com'): r = SnapEngageNode().render(Context()) assert 'SnapABug.setDomain("example.com");' in r def test_secure_connection(self): - r = SnapEngageNode().render(Context({ - 'snapengage_secure_connection': True})) + r = SnapEngageNode().render(Context({'snapengage_secure_connection': True})) assert 'SnapABug.setSecureConnexion();' in r with override_settings(SNAPENGAGE_SECURE_CONNECTION=True): r = SnapEngageNode().render(Context()) assert 'SnapABug.setSecureConnexion();' in r def test_show_offline(self): - r = SnapEngageNode().render(Context({ - 'snapengage_show_offline': False, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_show_offline': False, + } + ) + ) assert 'SnapABug.allowOffline(false);' in r with override_settings(SNAPENGAGE_SHOW_OFFLINE=False): r = SnapEngageNode().render(Context()) assert 'SnapABug.allowOffline(false);' in r def test_proactive_chat(self): - r = SnapEngageNode().render(Context({ - 'snapengage_proactive_chat': False})) + r = SnapEngageNode().render(Context({'snapengage_proactive_chat': False})) assert 'SnapABug.allowProactiveChat(false);' in r def test_screenshot(self): - r = SnapEngageNode().render(Context({ - 'snapengage_screenshots': False, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_screenshots': False, + } + ) + ) assert 'SnapABug.allowScreenshot(false);' in r with override_settings(SNAPENGAGE_SCREENSHOTS=False): r = SnapEngageNode().render(Context()) assert 'SnapABug.allowScreenshot(false);' in r def test_offline_screenshots(self): - r = SnapEngageNode().render(Context({ - 'snapengage_offline_screenshots': False, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_offline_screenshots': False, + } + ) + ) assert 'SnapABug.showScreenshotOption(false);' in r with override_settings(SNAPENGAGE_OFFLINE_SCREENSHOTS=False): r = SnapEngageNode().render(Context()) @@ -205,35 +287,55 @@ class SnapEngageTestCase(TagTestCase): @override_settings(SNAPENGAGE_READONLY_EMAIL=False) def test_email(self): - r = SnapEngageNode().render(Context({ - 'snapengage_email': 'test@example.com', - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_email': 'test@example.com', + } + ) + ) assert 'SnapABug.setUserEmail("test@example.com");' in r def test_email_readonly(self): - r = SnapEngageNode().render(Context({ - 'snapengage_email': 'test@example.com', - 'snapengage_readonly_email': True, - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_email': 'test@example.com', + 'snapengage_readonly_email': True, + } + ) + ) assert 'SnapABug.setUserEmail("test@example.com",true);' in r with override_settings(SNAPENGAGE_READONLY_EMAIL=True): - r = SnapEngageNode().render(Context({ - 'snapengage_email': 'test@example.com', - })) + r = SnapEngageNode().render( + Context( + { + 'snapengage_email': 'test@example.com', + } + ) + ) assert 'SnapABug.setUserEmail("test@example.com",true);' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): - r = SnapEngageNode().render(Context({ - 'user': User(username='test', email='test@example.com'), - })) + r = SnapEngageNode().render( + Context( + { + 'user': User(username='test', email='test@example.com'), + } + ) + ) assert 'SnapABug.setUserEmail("test@example.com");' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): - r = SnapEngageNode().render(Context({ - 'user': AnonymousUser(), - })) + r = SnapEngageNode().render( + Context( + { + 'user': AnonymousUser(), + } + ) + ) assert 'SnapABug.setUserEmail(' not in r def test_language(self): diff --git a/tests/unit/test_tag_spring_metrics.py b/tests/unit/test_tag_spring_metrics.py index c362721..c145216 100644 --- a/tests/unit/test_tag_spring_metrics.py +++ b/tests/unit/test_tag_spring_metrics.py @@ -39,9 +39,13 @@ class SpringMetricsTagTestCase(TagTestCase): @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): - r = SpringMetricsNode().render(Context({ - 'user': User(email='test@test.com'), - })) + r = SpringMetricsNode().render( + Context( + { + 'user': User(email='test@test.com'), + } + ) + ) assert "_springMetq.push(['setdata', {'email': 'test@test.com'}]);" in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) @@ -50,10 +54,14 @@ class SpringMetricsTagTestCase(TagTestCase): assert "_springMetq.push(['setdata', {'email':" not in r def test_custom(self): - r = SpringMetricsNode().render(Context({ - 'spring_metrics_var1': 'val1', - 'spring_metrics_var2': 'val2', - })) + r = SpringMetricsNode().render( + Context( + { + 'spring_metrics_var1': 'val1', + 'spring_metrics_var2': 'val2', + } + ) + ) assert "_springMetq.push(['setdata', {'var1': 'val1'}]);" in r assert "_springMetq.push(['setdata', {'var2': 'val2'}]);" in r diff --git a/tests/unit/test_tag_uservoice.py b/tests/unit/test_tag_uservoice.py index 1d60eea..5fa91ee 100644 --- a/tests/unit/test_tag_uservoice.py +++ b/tests/unit/test_tag_uservoice.py @@ -19,11 +19,11 @@ class UserVoiceTagTestCase(TagTestCase): def test_node(self): r = UserVoiceNode().render(Context()) - assert "widget.uservoice.com/abcdefghijklmnopqrst.js" in r + assert 'widget.uservoice.com/abcdefghijklmnopqrst.js' in r def test_tag(self): r = self.render_tag('uservoice', 'uservoice') - assert "widget.uservoice.com/abcdefghijklmnopqrst.js" in r + assert 'widget.uservoice.com/abcdefghijklmnopqrst.js' in r @override_settings(USERVOICE_WIDGET_KEY=None) def test_no_key(self): @@ -43,7 +43,7 @@ class UserVoiceTagTestCase(TagTestCase): def test_overridden_key(self): vars = {'uservoice_widget_key': 'defghijklmnopqrstuvw'} r = UserVoiceNode().render(Context(vars)) - assert "widget.uservoice.com/defghijklmnopqrstuvw.js" in r + assert 'widget.uservoice.com/defghijklmnopqrstuvw.js' in r @override_settings(USERVOICE_WIDGET_OPTIONS={'key1': 'val1'}) def test_options(self): diff --git a/tests/unit/test_tag_woopra.py b/tests/unit/test_tag_woopra.py index dd1536f..fa97367 100644 --- a/tests/unit/test_tag_woopra.py +++ b/tests/unit/test_tag_woopra.py @@ -40,23 +40,35 @@ class WoopraTagTestCase(TagTestCase): @override_settings(WOOPRA_IDLE_TIMEOUT=1234) def test_idle_timeout(self): r = WoopraNode().render(Context({})) - assert 'var woo_settings = {"domain": "example.com", "idle_timeout": "1234"};' in r + assert ( + 'var woo_settings = {"domain": "example.com", "idle_timeout": "1234"};' in r + ) def test_custom(self): - r = WoopraNode().render(Context({ - 'woopra_var1': 'val1', - 'woopra_var2': 'val2', - })) + r = WoopraNode().render( + Context( + { + 'woopra_var1': 'val1', + 'woopra_var2': 'val2', + } + ) + ) assert 'var woo_visitor = {"var1": "val1", "var2": "val2"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_name_and_email(self): - r = WoopraNode().render(Context({ - 'user': User(username='test', - first_name='Firstname', - last_name='Lastname', - email="test@example.com"), - })) + r = WoopraNode().render( + Context( + { + 'user': User( + username='test', + first_name='Firstname', + last_name='Lastname', + email='test@example.com', + ), + } + ) + ) assert 'var woo_visitor = ' '{"email": "test@example.com", "name": "Firstname Lastname"};' in r @@ -67,18 +79,26 @@ class WoopraTagTestCase(TagTestCase): @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_no_identify_when_explicit_name(self): - r = WoopraNode().render(Context({ - 'woopra_name': 'explicit', - 'user': User(username='implicit'), - })) + r = WoopraNode().render( + Context( + { + 'woopra_name': 'explicit', + 'user': User(username='implicit'), + } + ) + ) assert 'var woo_visitor = {"name": "explicit"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_no_identify_when_explicit_email(self): - r = WoopraNode().render(Context({ - 'woopra_email': 'explicit', - 'user': User(username='implicit'), - })) + r = WoopraNode().render( + Context( + { + 'woopra_email': 'explicit', + 'user': User(username='implicit'), + } + ) + ) assert 'var woo_visitor = {"email": "explicit"};' in r @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) diff --git a/tests/unit/test_tag_yandex_metrica.py b/tests/unit/test_tag_yandex_metrica.py index a30b58a..b28dd8f 100644 --- a/tests/unit/test_tag_yandex_metrica.py +++ b/tests/unit/test_tag_yandex_metrica.py @@ -2,7 +2,6 @@ Tests for the Yandex.Metrica template tags and filters. """ - import pytest from django.http import HttpRequest from django.template import Context @@ -21,11 +20,11 @@ class YandexMetricaTagTestCase(TagTestCase): def test_tag(self): r = self.render_tag('yandex_metrica', 'yandex_metrica') - assert "w.yaCounter12345678 = new Ya.Metrika" in r + assert 'w.yaCounter12345678 = new Ya.Metrika' in r def test_node(self): r = YandexMetricaNode().render(Context({})) - assert "w.yaCounter12345678 = new Ya.Metrika" in r + assert 'w.yaCounter12345678 = new Ya.Metrika' in r @override_settings(YANDEX_METRICA_COUNTER_ID=None) def test_no_site_id(self): diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 816acda..8816d73 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -21,15 +21,14 @@ from analytical.utils import ( class SettingDeletedTestCase(TestCase): - @override_settings(USER_ID=None) def test_get_required_setting(self): """ Make sure using get_required_setting fails in the right place. """ - with pytest.raises(AnalyticalException, match="USER_ID setting is not set"): - get_required_setting("USER_ID", r"\d+", "invalid USER_ID") + with pytest.raises(AnalyticalException, match='USER_ID setting is not set'): + get_required_setting('USER_ID', r'\d+', 'invalid USER_ID') class MyUser(AbstractBaseUser): @@ -47,20 +46,30 @@ class GetIdentityTestCase(TestCase): assert get_id == 'fake_id' def test_custom_identity_specific_provider(self): - get_id = get_identity(Context({ - 'foo_provider_identity': 'bar', - 'analytical_identity': 'baz', - }), prefix='foo_provider') + get_id = get_identity( + Context( + { + 'foo_provider_identity': 'bar', + 'analytical_identity': 'baz', + } + ), + prefix='foo_provider', + ) assert get_id == 'bar' def test_custom_identity_general(self): - get_id = get_identity(Context({ - 'analytical_identity': 'baz', - }), prefix='foo_provider') + get_id = get_identity( + Context( + { + 'analytical_identity': 'baz', + } + ), + prefix='foo_provider', + ) assert get_id == 'baz' -@override_settings(ANALYTICAL_DOMAIN="example.org") +@override_settings(ANALYTICAL_DOMAIN='example.org') class GetDomainTestCase(TestCase): def test_get_service_domain_from_context(self): context = Context({'test_domain': 'example.com'}) @@ -70,7 +79,7 @@ class GetDomainTestCase(TestCase): context = Context({'analytical_domain': 'example.com'}) assert get_domain(context, 'test') == 'example.com' - @override_settings(TEST_DOMAIN="example.net") + @override_settings(TEST_DOMAIN='example.net') def test_get_service_domain_from_settings(self): context = Context() assert get_domain(context, 'test') == 'example.net' @@ -92,7 +101,6 @@ class GetDomainTestCase(TestCase): class InternalIpTestCase(TestCase): - @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_no_internal_ip(self): context = Context() diff --git a/tests/unit/utils.py b/tests/unit/utils.py index 6008e08..1b3ecd6 100644 --- a/tests/unit/utils.py +++ b/tests/unit/utils.py @@ -16,7 +16,7 @@ class TagTestCase(TestCase): def render_tag(self, library, tag, vars=None, request=None): if vars is None: vars = {} - t = Template("{%% load %s %%}{%% %s %%}" % (library, tag)) + t = Template('{%% load %s %%}{%% %s %%}' % (library, tag)) if request is not None: context = RequestContext(request, vars) else: From 1dd5b2ac62547a7a1f4f6ed9941dc5b543662860 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Apr 2025 14:06:03 +0200 Subject: [PATCH 084/103] Fix Sphinx documentation issues, add RTD configuration --- .readthedocs.yaml | 18 ++++++++++++++++++ docs/services/google_analytics_gtag.rst | 6 +++--- docs/services/google_analytics_js.rst | 20 ++++++++++---------- docs/services/matomo.rst | 4 ++-- tox.ini | 2 +- 5 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..1a89680 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +# Read the Docs configuration file +# https://docs.readthedocs.io/en/stable/config-file/v2.html + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +# python: +# install: +# - requirements: docs/requirements.txt diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 1a97cf5..a5f6f24 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -26,7 +26,7 @@ application to :const:`INSTALLED_APPS` in your project 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`. +:ref:`google-analytics-configuration-gtag`. The Google Analytics tracking code is inserted into templates using a template tag. Load the :mod:`google_analytics_gtag` template tag library and @@ -43,7 +43,7 @@ template. Insert the tag at the bottom of the HTML head:: ... -.. _google-analytics-configuration: +.. _google-analytics-configuration-gtag: Configuration ============= @@ -54,7 +54,7 @@ code, you also need to set-up the domain. Finally, you can add custom segments for Google Analytics to track. -.. _google-analytics-property-id: +.. _google-analytics-gtag-property-id: Setting the property ID ----------------------- diff --git a/docs/services/google_analytics_js.rst b/docs/services/google_analytics_js.rst index b064d2e..c575bf8 100644 --- a/docs/services/google_analytics_js.rst +++ b/docs/services/google_analytics_js.rst @@ -26,7 +26,7 @@ application to :const:`INSTALLED_APPS` in your project 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`. +:ref:`google-analytics-configuration-js`. The Google Analytics tracking code is inserted into templates using a template tag. Load the :mod:`google_analytics_js` template tag library and @@ -43,7 +43,7 @@ template. Insert the tag at the bottom of the HTML head:: ... -.. _google-analytics-configuration: +.. _google-analytics-configuration-js: Configuration ============= @@ -54,7 +54,7 @@ code, you also need to set-up the domain. Finally, you can add custom segments for Google Analytics to track. -.. _google-analytics-property-id: +.. _google-analytics-js-property-id: Setting the property ID ----------------------- @@ -117,7 +117,7 @@ By default, display advertising features are disabled. .. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482 -.. _google-analytics-internal-ips: +.. _google-analytics-js-internal-ips: Internal IP addresses --------------------- @@ -131,7 +131,7 @@ setting, the tracking code is commented out. It takes the value of important information about detecting the visitor IP address. -.. _google-analytics-custom-variables: +.. _google-analytics-js-custom-variables: Custom variables ---------------- @@ -165,7 +165,7 @@ context processor, the latter clobbers the former. .. _`custom variables`: https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#custom-vars -.. _google-analytics-anonimyze-ips: +.. _google-analytics-js-anonimyze-ips: Anonymize IPs ------------- @@ -183,7 +183,7 @@ By default, IPs are not anonymized. .. _`IP anonymization`: https://support.google.com/analytics/bin/answer.py?hl=en&answer=2763052 -.. _google-analytics-sample-rate: +.. _google-analytics-js-sample-rate: Sample Rate ----------- @@ -199,7 +199,7 @@ integer value. .. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#sampleRate -.. _google-analytics-site-speed-sample-rate: +.. _google-analytics-js-site-speed-sample-rate: Site Speed Sample Rate ---------------------- @@ -218,7 +218,7 @@ integer value. .. _google-analytics-cookie-expiration: Cookie Expiration ----------------------- +----------------- You can configure the `Cookie Expiration`_ feature by setting the :const:`GOOGLE_ANALYTICS_COOKIE_EXPIRATION` configuration setting:: @@ -230,7 +230,7 @@ The value is the cookie expiration in seconds or 0 to delete the cookie when the .. _`Cookie Expiration`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout Custom Javascript Source ----------------------- +------------------------ You can configure a custom URL for the javascript file by setting the :const:`GOOGLE_ANALYTICS_JS_SOURCE` configuration setting:: diff --git a/docs/services/matomo.rst b/docs/services/matomo.rst index d6a5b83..2aad059 100644 --- a/docs/services/matomo.rst +++ b/docs/services/matomo.rst @@ -1,6 +1,6 @@ -================================== +==================================================== Matomo (formerly Piwik) -- open source web analytics -================================== +==================================================== Matomo_ is an open analytics platform currently used by individuals, companies and governments all over the world. diff --git a/tox.ini b/tox.ini index 9cc4be0..1da9e0c 100644 --- a/tox.ini +++ b/tox.ini @@ -59,7 +59,7 @@ commands = bandit {posargs:-r analytical} -v description = Clean up bytecode and build artifacts skip_install = true deps = pyclean -commands = pyclean {posargs:. --debris --erase requirements.txt tests/unittests-report.xml --yes} +commands = pyclean {posargs:. --debris --erase requirements.txt docs/_build/**/* docs/_build/ tests/unittests-report.xml --yes} [testenv:docs] description = Build the HTML documentation From d3ddf1ac6ae708c4df3181bd102fd97ca0838661 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Apr 2025 14:08:22 +0200 Subject: [PATCH 085/103] Update outdated PyPI URL references --- CHANGELOG.rst | 2 +- docs/index.rst | 2 +- docs/install.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f3e8561..a88255a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -205,7 +205,7 @@ Version 0.5.0 ------------- * Split off Geckoboard support into django-geckoboard_. -.. _django-geckoboard: http://pypi.python.org/pypi/django-geckoboard +.. _django-geckoboard: https://pypi.org/project/django-geckoboard Version 0.4.0 ------------- diff --git a/docs/index.rst b/docs/index.rst index 1e41fba..106fa82 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,7 @@ Django_ project. .. _Django: https://www.djangoproject.com/ -:Package: https://pypi.python.org/pypi/django-analytical/ +:Package: https://pypi.org/project/django-analytical/ :Source: https://github.com/jazzband/django-analytical diff --git a/docs/install.rst b/docs/install.rst index 927e238..daa105e 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -34,7 +34,7 @@ get the development code: $ git clone https://github.com/jazzband/django-analytical.git -.. _PyPI: http://pypi.python.org/pypi/django-analytical/ +.. _PyPI: https://pypi.org/project/django-analytical/ .. _GitHub: http://github.com/jazzband/django-analytical Then install the package by running the setup script: From 4540999dc1e60520879e1da1595627d37fc55d82 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 4 Apr 2025 14:12:37 +0200 Subject: [PATCH 086/103] Remove obsolete type attribute in script tags --- CHANGELOG.rst | 1 + analytical/templatetags/chartbeat.py | 6 ++---- analytical/templatetags/clickmap.py | 2 +- analytical/templatetags/clicky.py | 2 +- analytical/templatetags/crazy_egg.py | 4 ++-- analytical/templatetags/gauges.py | 2 +- analytical/templatetags/google_analytics.py | 2 +- analytical/templatetags/gosquared.py | 2 +- analytical/templatetags/heap.py | 4 ++-- analytical/templatetags/hubspot.py | 2 +- analytical/templatetags/kiss_insights.py | 4 ++-- analytical/templatetags/kiss_metrics.py | 2 +- analytical/templatetags/matomo.py | 2 +- analytical/templatetags/mixpanel.py | 2 +- analytical/templatetags/performable.py | 8 ++++---- analytical/templatetags/rating_mailru.py | 2 +- analytical/templatetags/snapengage.py | 4 ++-- analytical/templatetags/uservoice.py | 2 +- analytical/templatetags/woopra.py | 2 +- analytical/templatetags/yandex_metrica.py | 2 +- docs/services/gauges.rst | 4 ++-- docs/services/uservoice.rst | 2 +- tests/unit/test_tag_gauges.py | 4 ++-- 23 files changed, 33 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a88255a..300d973 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,7 @@ Unreleased * Remove deprecated Piwik integration. Use Matomo instead! (Peter Bittner) * Migrate packaging from setup.py to pyproject.toml with Ruff for linting and formatting (Peter Bittner) +* Remove obsolete type attribute in script tags for JavaScript (Peter Bittner) Version 3.1.0 ------------- diff --git a/analytical/templatetags/chartbeat.py b/analytical/templatetags/chartbeat.py index c499935..83a977f 100644 --- a/analytical/templatetags/chartbeat.py +++ b/analytical/templatetags/chartbeat.py @@ -12,11 +12,9 @@ from django.template import Library, Node, TemplateSyntaxError from analytical.utils import disable_html, get_required_setting, is_internal_ip USER_ID_RE = re.compile(r'^\d+$') -INIT_CODE = ( - """""" -) +INIT_CODE = """""" SETUP_CODE = """ - '.format( +SETUP_CODE = ''.format( placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/' '%(account_nr_1)s/%(account_nr_2)s.js' ) @@ -58,7 +58,7 @@ class CrazyEggNode(Node): } for (varnr, value) in params ) - html = '%s\n' % (html, js) + html = '%s\n' % (html, js) if is_internal_ip(context, 'CRAZY_EGG'): html = disable_html(html, 'Crazy Egg') return html diff --git a/analytical/templatetags/gauges.py b/analytical/templatetags/gauges.py index ee9c714..16b6c89 100644 --- a/analytical/templatetags/gauges.py +++ b/analytical/templatetags/gauges.py @@ -10,7 +10,7 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip SITE_ID_RE = re.compile(r'[\da-f]+$') TRACKING_CODE = """ - diff --git a/analytical/templatetags/hubspot.py b/analytical/templatetags/hubspot.py index cff51b8..fe897d8 100644 --- a/analytical/templatetags/hubspot.py +++ b/analytical/templatetags/hubspot.py @@ -11,7 +11,7 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip PORTAL_ID_RE = re.compile(r'^\d+$') TRACKING_CODE = """ - - + + """ # noqa IDENTIFY_CODE = "_kiq.push(['identify', '%s']);" SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);" diff --git a/analytical/templatetags/kiss_metrics.py b/analytical/templatetags/kiss_metrics.py index 73d09d5..53d6aa7 100644 --- a/analytical/templatetags/kiss_metrics.py +++ b/analytical/templatetags/kiss_metrics.py @@ -16,7 +16,7 @@ from analytical.utils import ( API_KEY_RE = re.compile(r'^[0-9a-f]{40}$') TRACKING_CODE = """ - + """ # noqa IDENTIFY_CODE = """ - """ EMBED_CODE = """ - - + """ # noqa diff --git a/analytical/templatetags/uservoice.py b/analytical/templatetags/uservoice.py index ee1d1c2..5c0f1ed 100644 --- a/analytical/templatetags/uservoice.py +++ b/analytical/templatetags/uservoice.py @@ -12,7 +12,7 @@ from analytical.utils import get_identity, get_required_setting WIDGET_KEY_RE = re.compile(r'^[a-zA-Z0-9]*$') TRACKING_CODE = """ - """ -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') diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index a5f6f24..9760930 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -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 diff --git a/tests/unit/test_tag_google_analytics_gtag.py b/tests/unit/test_tag_google_analytics_gtag.py index 16c356f..c84c327 100644 --- a/tests/unit/test_tag_google_analytics_gtag.py +++ b/tests/unit/test_tag_google_analytics_gtag.py @@ -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): '' ) 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): '' ) 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): '' ) 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): '' ) 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 + ) From 4ea46057918d3df2536ffb487806629cd9004f4d Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Mon, 21 Jul 2025 16:37:53 +0200 Subject: [PATCH 100/103] Add changelog instructions, remove Gitter badge --- README.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 2fd9f12..b80259c 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ django-analytical |latest-version| ================================== -|build-status| |coverage| |python-support| |license| |gitter| |jazzband| +|build-status| |coverage| |python-support| |license| |jazzband| The django-analytical application integrates analytics services into a Django_ project. @@ -38,12 +38,9 @@ an asynchronous version of the Javascript code if possible. .. |license| image:: https://img.shields.io/pypi/l/django-analytical.svg :alt: Software license :target: https://github.com/jazzband/django-analytical/blob/main/LICENSE.txt -.. |gitter| image:: https://img.shields.io/gitter/room/jazzband/django-analytical.svg - :alt: Gitter chat room - :target: https://gitter.im/jazzband/django-analytical .. |jazzband| image:: https://jazzband.co/static/img/badge.svg :alt: Jazzband - :target: https://jazzband.co/ + :target: https://jazzband.co/projects/django-analytical .. _`Django`: http://www.djangoproject.com/ Currently Supported Services @@ -108,9 +105,7 @@ Documentation and Support The documentation can be found in the ``docs`` directory or `read online`_. The source code and issue tracker are generously `hosted by -GitHub`_. Bugs should be reported there, whereas for lengthy chats -and coding support when implementing new service integrations you're -welcome to use our `Gitter chat room`_. +GitHub`_. .. _`read online`: https://django-analytical.readthedocs.io/ .. _`hosted by GitHub`: https://github.com/jazzband/django-analytical @@ -128,11 +123,17 @@ services to support, or suggesting documentation improvements, use the the repository, make changes and place a `pull request`_. Creating an issue to discuss your plans is useful. +At the end, don't forget to add yourself to the `list of authors`_ and +update the `changelog`_ with a short description of your contribution. +We want you to stand out from the crowd as an open source superstar! ✦ + This is a `Jazzband`_ project. By contributing you agree to abide by the `Contributor Code of Conduct`_ and follow the `guidelines`_. .. _`issue tracker`: https://github.com/jazzband/django-analytical/issues .. _`pull request`: https://github.com/jazzband/django-analytical/pulls +.. _`list of authors`: https://github.com/jazzband/django-analytical/blob/main/pyproject.toml +.. _`changelog`: https://github.com/jazzband/django-analytical/blob/main/CHANGELOG.rst .. _`Jazzband`: https://jazzband.co .. _`Contributor Code of Conduct`: https://jazzband.co/about/conduct .. _`guidelines`: https://jazzband.co/about/guidelines From 033d2dc02f12e2504b8ca3b9eefc2c2ba87981d0 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Mon, 21 Jul 2025 16:41:22 +0200 Subject: [PATCH 101/103] Align spelling of JavaScript across all files (docs, docstrings) --- CHANGELOG.rst | 5 +++++ README.rst | 4 ++-- analytical/templatetags/chartbeat.py | 4 ++-- analytical/templatetags/clickmap.py | 2 +- analytical/templatetags/clicky.py | 2 +- analytical/templatetags/crazy_egg.py | 2 +- analytical/templatetags/gauges.py | 2 +- analytical/templatetags/google_analytics.py | 2 +- analytical/templatetags/google_analytics_gtag.py | 2 +- analytical/templatetags/google_analytics_js.py | 2 +- analytical/templatetags/gosquared.py | 2 +- analytical/templatetags/heap.py | 2 +- analytical/templatetags/hubspot.py | 2 +- analytical/templatetags/intercom.py | 2 +- analytical/templatetags/kiss_insights.py | 2 +- analytical/templatetags/kiss_metrics.py | 2 +- analytical/templatetags/matomo.py | 2 +- analytical/templatetags/mixpanel.py | 2 +- analytical/templatetags/olark.py | 2 +- analytical/templatetags/optimizely.py | 2 +- analytical/templatetags/performable.py | 2 +- analytical/templatetags/rating_mailru.py | 2 +- analytical/templatetags/snapengage.py | 2 +- analytical/templatetags/spring_metrics.py | 2 +- analytical/templatetags/uservoice.py | 2 +- analytical/templatetags/woopra.py | 2 +- analytical/templatetags/yandex_metrica.py | 2 +- docs/install.rst | 2 +- docs/services/chartbeat.rst | 2 +- docs/services/clickmap.rst | 4 ++-- docs/services/clicky.rst | 4 ++-- docs/services/crazy_egg.rst | 2 +- docs/services/gauges.rst | 6 +++--- docs/services/google_analytics.rst | 2 +- docs/services/google_analytics_gtag.rst | 2 +- docs/services/google_analytics_js.rst | 4 ++-- docs/services/intercom.rst | 6 +++--- docs/services/kiss_insights.rst | 4 ++-- docs/services/kiss_metrics.rst | 6 +++--- docs/services/mixpanel.rst | 12 ++++++------ docs/services/olark.rst | 8 ++++---- docs/services/optimizely.rst | 6 +++--- docs/services/performable.rst | 8 ++++---- docs/services/rating_mailru.rst | 2 +- docs/services/snapengage.rst | 2 +- docs/services/spring_metrics.rst | 2 +- docs/services/uservoice.rst | 6 +++--- docs/services/woopra.rst | 2 +- docs/services/yandex_metrica.rst | 2 +- 49 files changed, 80 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2703614..1077d76 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +(unreleased) +------------ +* Change spelling of "JavaScript" across all files in docstrings and docs + (Peter Bittner) + Version 3.2.0 ------------- * Remove deprecated Piwik integration. Use Matomo instead! (Peter Bittner) diff --git a/README.rst b/README.rst index b80259c..0ed5fe1 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ Django_ project. .. start docs include -Using an analytics service with a Django project means adding Javascript +Using an analytics service with a Django project means adding JavaScript tracking code to the project templates. Of course, every service has its own specific installation instructions. Furthermore, you need to include your unique identifiers, which then end up in the templates. @@ -19,7 +19,7 @@ behind a generic interface, and keeps personal information and configuration out of the templates. Its goal is to make the basic set-up very simple, while allowing advanced users to customize tracking. Each service is set up as recommended by the services themselves, using -an asynchronous version of the Javascript code if possible. +an asynchronous version of the JavaScript code if possible. .. end docs include diff --git a/analytical/templatetags/chartbeat.py b/analytical/templatetags/chartbeat.py index 83a977f..dfac04b 100644 --- a/analytical/templatetags/chartbeat.py +++ b/analytical/templatetags/chartbeat.py @@ -44,7 +44,7 @@ def chartbeat_top(parser, token): """ Top Chartbeat template tag. - Render the top Javascript code for Chartbeat. + Render the top JavaScript code for Chartbeat. """ bits = token.split_contents() if len(bits) > 1: @@ -64,7 +64,7 @@ def chartbeat_bottom(parser, token): """ Bottom Chartbeat template tag. - Render the bottom Javascript code for Chartbeat. You must supply + Render the bottom JavaScript code for Chartbeat. You must supply your Chartbeat User ID (as a string) in the ``CHARTBEAT_USER_ID`` setting. """ diff --git a/analytical/templatetags/clickmap.py b/analytical/templatetags/clickmap.py index 5b93010..08e7851 100644 --- a/analytical/templatetags/clickmap.py +++ b/analytical/templatetags/clickmap.py @@ -30,7 +30,7 @@ def clickmap(parser, token): """ Clickmap tracker template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your clickmap tracker ID (as a string) in the ``CLICKMAP_TRACKER_ID`` setting. """ diff --git a/analytical/templatetags/clicky.py b/analytical/templatetags/clicky.py index f260386..e00128a 100644 --- a/analytical/templatetags/clicky.py +++ b/analytical/templatetags/clicky.py @@ -40,7 +40,7 @@ def clicky(parser, token): """ Clicky tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your Clicky Site ID (as a string) in the ``CLICKY_SITE_ID`` setting. """ diff --git a/analytical/templatetags/crazy_egg.py b/analytical/templatetags/crazy_egg.py index e9a949b..8e7155e 100644 --- a/analytical/templatetags/crazy_egg.py +++ b/analytical/templatetags/crazy_egg.py @@ -24,7 +24,7 @@ def crazy_egg(parser, token): """ Crazy Egg tracking template tag. - Renders Javascript code to track page clicks. You must supply + Renders JavaScript code to track page clicks. You must supply your Crazy Egg account number (as a string) in the ``CRAZY_EGG_ACCOUNT_NUMBER`` setting. """ diff --git a/analytical/templatetags/gauges.py b/analytical/templatetags/gauges.py index 16b6c89..3bfeaa6 100644 --- a/analytical/templatetags/gauges.py +++ b/analytical/templatetags/gauges.py @@ -33,7 +33,7 @@ def gauges(parser, token): """ Gaug.es template tag. - Renders Javascript code to gaug.es testing. You must supply + Renders JavaScript code to gaug.es testing. You must supply your Site ID account number in the ``GAUGES_SITE_ID`` setting. """ diff --git a/analytical/templatetags/google_analytics.py b/analytical/templatetags/google_analytics.py index 165585e..5e6a0eb 100644 --- a/analytical/templatetags/google_analytics.py +++ b/analytical/templatetags/google_analytics.py @@ -71,7 +71,7 @@ def google_analytics(parser, token): """ Google Analytics tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your website property ID (as a string) in the ``GOOGLE_ANALYTICS_PROPERTY_ID`` setting. """ diff --git a/analytical/templatetags/google_analytics_gtag.py b/analytical/templatetags/google_analytics_gtag.py index 07c5a00..eb40a71 100644 --- a/analytical/templatetags/google_analytics_gtag.py +++ b/analytical/templatetags/google_analytics_gtag.py @@ -37,7 +37,7 @@ def google_analytics_gtag(parser, token): """ Google Analytics tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your website property ID (as a string) in the ``GOOGLE_ANALYTICS_GTAG_PROPERTY_ID`` setting. """ diff --git a/analytical/templatetags/google_analytics_js.py b/analytical/templatetags/google_analytics_js.py index 9739888..495ad03 100644 --- a/analytical/templatetags/google_analytics_js.py +++ b/analytical/templatetags/google_analytics_js.py @@ -44,7 +44,7 @@ def google_analytics_js(parser, token): """ Google Analytics tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your website property ID (as a string) in the ``GOOGLE_ANALYTICS_JS_PROPERTY_ID`` setting. """ diff --git a/analytical/templatetags/gosquared.py b/analytical/templatetags/gosquared.py index ee0ccae..9668351 100644 --- a/analytical/templatetags/gosquared.py +++ b/analytical/templatetags/gosquared.py @@ -40,7 +40,7 @@ def gosquared(parser, token): """ GoSquared tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your GoSquared site token in the ``GOSQUARED_SITE_TOKEN`` setting. """ bits = token.split_contents() diff --git a/analytical/templatetags/heap.py b/analytical/templatetags/heap.py index d74bfdb..ec9b2e2 100644 --- a/analytical/templatetags/heap.py +++ b/analytical/templatetags/heap.py @@ -31,7 +31,7 @@ def heap(parser, token): """ Heap tracker template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your heap tracker ID (as a string) in the ``HEAP_TRACKER_ID`` setting. """ diff --git a/analytical/templatetags/hubspot.py b/analytical/templatetags/hubspot.py index fe897d8..37e7408 100644 --- a/analytical/templatetags/hubspot.py +++ b/analytical/templatetags/hubspot.py @@ -30,7 +30,7 @@ def hubspot(parser, token): """ HubSpot tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your portal ID (as a string) in the ``HUBSPOT_PORTAL_ID`` setting. """ bits = token.split_contents() diff --git a/analytical/templatetags/intercom.py b/analytical/templatetags/intercom.py index 69ae77e..5b5835c 100644 --- a/analytical/templatetags/intercom.py +++ b/analytical/templatetags/intercom.py @@ -63,7 +63,7 @@ def intercom(parser, token): """ Intercom.io template tag. - Renders Javascript code to intercom.io testing. You must supply + Renders JavaScript code to intercom.io testing. You must supply your APP ID account number in the ``INTERCOM_APP_ID`` setting. """ diff --git a/analytical/templatetags/kiss_insights.py b/analytical/templatetags/kiss_insights.py index fabd3dd..4eb21c6 100644 --- a/analytical/templatetags/kiss_insights.py +++ b/analytical/templatetags/kiss_insights.py @@ -27,7 +27,7 @@ def kiss_insights(parser, token): """ KISSinsights set-up template tag. - Renders Javascript code to set-up surveys. You must supply + Renders JavaScript code to set-up surveys. You must supply your account number and site code in the ``KISS_INSIGHTS_ACCOUNT_NUMBER`` and ``KISS_INSIGHTS_SITE_CODE`` settings. diff --git a/analytical/templatetags/kiss_metrics.py b/analytical/templatetags/kiss_metrics.py index 53d6aa7..683e15a 100644 --- a/analytical/templatetags/kiss_metrics.py +++ b/analytical/templatetags/kiss_metrics.py @@ -50,7 +50,7 @@ def kiss_metrics(parser, token): """ KISSinsights tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your KISSmetrics API key in the ``KISS_METRICS_API_KEY`` setting. """ diff --git a/analytical/templatetags/matomo.py b/analytical/templatetags/matomo.py index d6b16ec..2509150 100644 --- a/analytical/templatetags/matomo.py +++ b/analytical/templatetags/matomo.py @@ -59,7 +59,7 @@ def matomo(parser, token): """ Matomo tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your Matomo domain (plus optional URI path), and tracked site ID in the ``MATOMO_DOMAIN_PATH`` and the ``MATOMO_SITE_ID`` setting. diff --git a/analytical/templatetags/mixpanel.py b/analytical/templatetags/mixpanel.py index 2dc3ade..3bc94b2 100644 --- a/analytical/templatetags/mixpanel.py +++ b/analytical/templatetags/mixpanel.py @@ -37,7 +37,7 @@ def mixpanel(parser, token): """ Mixpanel tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your Mixpanel token in the ``MIXPANEL_API_TOKEN`` setting. """ bits = token.split_contents() diff --git a/analytical/templatetags/olark.py b/analytical/templatetags/olark.py index 34e5c4f..e1ceb07 100644 --- a/analytical/templatetags/olark.py +++ b/analytical/templatetags/olark.py @@ -57,7 +57,7 @@ def olark(parser, token): """ Olark set-up template tag. - Renders Javascript code to set-up Olark chat. You must supply + Renders JavaScript code to set-up Olark chat. You must supply your site ID in the ``OLARK_SITE_ID`` setting. """ bits = token.split_contents() diff --git a/analytical/templatetags/optimizely.py b/analytical/templatetags/optimizely.py index 91a8efc..34d945a 100644 --- a/analytical/templatetags/optimizely.py +++ b/analytical/templatetags/optimizely.py @@ -20,7 +20,7 @@ def optimizely(parser, token): """ Optimizely template tag. - Renders Javascript code to set-up A/B testing. You must supply + Renders JavaScript code to set-up A/B testing. You must supply your Optimizely account number in the ``OPTIMIZELY_ACCOUNT_NUMBER`` setting. """ diff --git a/analytical/templatetags/performable.py b/analytical/templatetags/performable.py index e02cf15..55bbe40 100644 --- a/analytical/templatetags/performable.py +++ b/analytical/templatetags/performable.py @@ -43,7 +43,7 @@ def performable(parser, token): """ Performable template tag. - Renders Javascript code to set-up Performable tracking. You must + Renders JavaScript code to set-up Performable tracking. You must supply your Performable API key in the ``PERFORMABLE_API_KEY`` setting. """ diff --git a/analytical/templatetags/rating_mailru.py b/analytical/templatetags/rating_mailru.py index cf506d1..aa61a31 100644 --- a/analytical/templatetags/rating_mailru.py +++ b/analytical/templatetags/rating_mailru.py @@ -35,7 +35,7 @@ def rating_mailru(parser, token): """ Rating@Mail.ru counter template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your website counter ID (as a string) in the ``RATING_MAILRU_COUNTER_ID`` setting. """ diff --git a/analytical/templatetags/snapengage.py b/analytical/templatetags/snapengage.py index ad33e35..df9ffaa 100644 --- a/analytical/templatetags/snapengage.py +++ b/analytical/templatetags/snapengage.py @@ -59,7 +59,7 @@ def snapengage(parser, token): """ SnapEngage set-up template tag. - Renders Javascript code to set-up SnapEngage chat. You must supply + Renders JavaScript code to set-up SnapEngage chat. You must supply your widget ID in the ``SNAPENGAGE_WIDGET_ID`` setting. """ bits = token.split_contents() diff --git a/analytical/templatetags/spring_metrics.py b/analytical/templatetags/spring_metrics.py index 9cb0ec1..69d93c3 100644 --- a/analytical/templatetags/spring_metrics.py +++ b/analytical/templatetags/spring_metrics.py @@ -40,7 +40,7 @@ def spring_metrics(parser, token): """ Spring Metrics tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your Spring Metrics Tracking ID in the ``SPRING_METRICS_TRACKING_ID`` setting. """ diff --git a/analytical/templatetags/uservoice.py b/analytical/templatetags/uservoice.py index 5c0f1ed..5c260f0 100644 --- a/analytical/templatetags/uservoice.py +++ b/analytical/templatetags/uservoice.py @@ -35,7 +35,7 @@ def uservoice(parser, token): """ UserVoice tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your UserVoice Widget Key in the ``USERVOICE_WIDGET_KEY`` setting or the ``uservoice_widget_key`` template context variable. """ diff --git a/analytical/templatetags/woopra.py b/analytical/templatetags/woopra.py index 80cf9f4..f8e20b6 100644 --- a/analytical/templatetags/woopra.py +++ b/analytical/templatetags/woopra.py @@ -39,7 +39,7 @@ def woopra(parser, token): """ Woopra tracking template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your Woopra domain in the ``WOOPRA_DOMAIN`` setting. """ bits = token.split_contents() diff --git a/analytical/templatetags/yandex_metrica.py b/analytical/templatetags/yandex_metrica.py index cead920..da1f2ee 100644 --- a/analytical/templatetags/yandex_metrica.py +++ b/analytical/templatetags/yandex_metrica.py @@ -44,7 +44,7 @@ def yandex_metrica(parser, token): """ Yandex.Metrica counter template tag. - Renders Javascript code to track page visits. You must supply + Renders JavaScript code to track page visits. You must supply your website counter ID (as a string) in the ``YANDEX_METRICA_COUNTER_ID`` setting. """ diff --git a/docs/install.rst b/docs/install.rst index daa105e..6949f3a 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -68,7 +68,7 @@ file of your project: Adding the template tags to the base template ============================================= -Because every analytics service uses own specific Javascript code that +Because every analytics service uses own specific JavaScript code that should be added to the top or bottom of either the head or body of the HTML page, django-analytical provides four general-purpose template tags that will render the code needed for the services you are using. Your diff --git a/docs/services/chartbeat.rst b/docs/services/chartbeat.rst index 62820dc..9f37395 100644 --- a/docs/services/chartbeat.rst +++ b/docs/services/chartbeat.rst @@ -96,7 +96,7 @@ important information about detecting the visitor IP address. Setting the domain ------------------ -The Javascript tracking code can send the website domain to Chartbeat. +The JavaScript tracking code can send the website domain to Chartbeat. If you use multiple subdomains this enables you to treat them as one website in Chartbeat. If your project uses the sites framework, the domain name of the current :class:`~django.contrib.sites.models.Site` diff --git a/docs/services/clickmap.rst b/docs/services/clickmap.rst index d3d9b9f..e7b3fdb 100644 --- a/docs/services/clickmap.rst +++ b/docs/services/clickmap.rst @@ -23,7 +23,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`clickmap-configuration`. -The Clickmap Javascript code is inserted into templates using a template +The Clickmap JavaScript code is inserted into templates using a template tag. Load the :mod:`clickmap` template tag library and insert the :ttag:`clickmap` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert @@ -54,7 +54,7 @@ Setting the Tracker ID ---------------------- Clickmap gives you a unique Tracker ID, and the :ttag:`clickmap` -tag will include it in the rendered Javascript code. You can find your +tag will include it in the rendered JavaScript code. You can find your Tracker ID clicking the link named "Tracker" in the dashboard of your Clickmap account. Set :const:`CLICKMAP_TRACKER_ID` in the project :file:`settings.py` file:: diff --git a/docs/services/clicky.rst b/docs/services/clicky.rst index 40e8bd3..62f1d38 100644 --- a/docs/services/clicky.rst +++ b/docs/services/clicky.rst @@ -53,7 +53,7 @@ Setting the Site ID ------------------- Every website you track with Clicky gets its own Site ID, and the -:ttag:`clicky` tag will include it in the rendered Javascript code. +:ttag:`clicky` tag will include it in the rendered JavaScript code. You can find the Site ID in the *Info* tab of the website *Preferences* page, in your Clicky account. Set :const:`CLICKY_SITE_ID` in the project :file:`settings.py` file:: @@ -84,7 +84,7 @@ Custom data As described in the Clicky `customized tracking`_ documentation page, the data that is tracked by Clicky can be customized by setting the -:data:`clicky_custom` Javascript variable before loading the tracking +:data:`clicky_custom` JavaScript variable before loading the tracking code. Using template context variables, you can let the :ttag:`clicky` tag pass custom data to Clicky automatically. You can set the context variables in your view when you render a template containing the diff --git a/docs/services/crazy_egg.rst b/docs/services/crazy_egg.rst index a0a9cc7..20ea8f7 100644 --- a/docs/services/crazy_egg.rst +++ b/docs/services/crazy_egg.rst @@ -53,7 +53,7 @@ Setting the account number -------------------------- Crazy Egg gives you a unique account number, and the :ttag:`crazy egg` -tag will include it in the rendered Javascript code. You can find your +tag will include it in the rendered JavaScript code. You can find your account number by clicking the link named "What's my code?" in the dashboard of your Crazy Egg account. Set :const:`CRAZY_EGG_ACCOUNT_NUMBER` in the project :file:`settings.py` diff --git a/docs/services/gauges.rst b/docs/services/gauges.rst index a26ff4d..d47dabf 100644 --- a/docs/services/gauges.rst +++ b/docs/services/gauges.rst @@ -23,7 +23,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`gauges-configuration`. -The Gaug.es Javascript code is inserted into templates using a +The Gaug.es JavaScript code is inserted into templates using a template tag. Load the :mod:`gauges` template tag library and insert the :ttag:`gauges` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. @@ -51,7 +51,7 @@ Setting the site id -------------------------- Gaug.es gives you a unique site id, and the :ttag:`gauges` -tag will include it in the rendered Javascript code. You can find your +tag will include it in the rendered JavaScript code. You can find your site id by clicking the *Tracking Code* link when logged into the on the gaug.es website. A page will display containing HTML code looking like this:: @@ -76,7 +76,7 @@ file:: GAUGES_SITE_ID = 'XXXXXXXXXXXXXXXXXXXXXXX' -If you do not set an site id, the Javascript code will not be +If you do not set an site id, the JavaScript code will not be rendered. diff --git a/docs/services/google_analytics.rst b/docs/services/google_analytics.rst index 12d008c..6b8704d 100644 --- a/docs/services/google_analytics.rst +++ b/docs/services/google_analytics.rst @@ -58,7 +58,7 @@ Setting the property ID Every website you track with Google Analytics gets its own property ID, and the :ttag:`google_analytics` tag will include it in the rendered -Javascript code. You can find the web property ID on the overview page +JavaScript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_PROPERTY_ID` in the project :file:`settings.py` file:: diff --git a/docs/services/google_analytics_gtag.rst b/docs/services/google_analytics_gtag.rst index 9760930..a4f3978 100644 --- a/docs/services/google_analytics_gtag.rst +++ b/docs/services/google_analytics_gtag.rst @@ -61,7 +61,7 @@ Setting the property ID Every website you track with Google Analytics gets its own property ID, and the :ttag:`google_analytics_gtag` tag will include it in the rendered -Javascript code. You can find the web property ID on the overview page +JavaScript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_GTAG_PROPERTY_ID` in the project :file:`settings.py` file:: diff --git a/docs/services/google_analytics_js.rst b/docs/services/google_analytics_js.rst index c575bf8..a2e285e 100644 --- a/docs/services/google_analytics_js.rst +++ b/docs/services/google_analytics_js.rst @@ -61,7 +61,7 @@ Setting the property ID Every website you track with Google Analytics gets its own property ID, and the :ttag:`google_analytics_js` tag will include it in the rendered -Javascript code. You can find the web property ID on the overview page +JavaScript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_JS_PROPERTY_ID` in the project :file:`settings.py` file:: @@ -229,7 +229,7 @@ The value is the cookie expiration in seconds or 0 to delete the cookie when the .. _`Cookie Expiration`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout -Custom Javascript Source +Custom JavaScript Source ------------------------ You can configure a custom URL for the javascript file by setting the diff --git a/docs/services/intercom.rst b/docs/services/intercom.rst index f458d28..f404243 100644 --- a/docs/services/intercom.rst +++ b/docs/services/intercom.rst @@ -23,7 +23,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`intercom-configuration`. -The Intercom.io Javascript code is inserted into templates using a +The Intercom.io JavaScript code is inserted into templates using a template tag. Load the :mod:`intercom` template tag library and insert the :ttag:`intercom` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. @@ -55,7 +55,7 @@ Setting the app id -------------------------- Intercom.io gives you a unique app id, and the :ttag:`intercom` -tag will include it in the rendered Javascript code. You can find your +tag will include it in the rendered JavaScript code. You can find your app id by clicking the *Tracking Code* link when logged into the on the intercom.io website. A page will display containing HTML code looking like this:: @@ -71,7 +71,7 @@ file:: INTERCOM_APP_ID = 'XXXXXXXXXXXXXXXXXXXXXXX' -If you do not set an app id, the Javascript code will not be +If you do not set an app id, the JavaScript code will not be rendered. diff --git a/docs/services/kiss_insights.rst b/docs/services/kiss_insights.rst index 95ef77a..606f570 100644 --- a/docs/services/kiss_insights.rst +++ b/docs/services/kiss_insights.rst @@ -54,10 +54,10 @@ Setting the account number and site code In order to install the survey code, you need to set your KISSinsights account number and website code. The :ttag:`kiss_insights` tag will -include it in the rendered Javascript code. You can find the account +include it in the rendered JavaScript code. You can find the account number and website code by visiting the code installation page of the website you want to place the surveys on. You will see some HTML code -with a Javascript tag with a ``src`` attribute containing +with a JavaScript tag with a ``src`` attribute containing ``//s3.amazonaws.com/ki.js/XXXXX/YYY.js``. Here ``XXXXX`` is the account number and ``YYY`` the website code. Set :const:`KISS_INSIGHTS_ACCOUNT_NUMBER` and diff --git a/docs/services/kiss_metrics.rst b/docs/services/kiss_metrics.rst index 629709a..d1593c0 100644 --- a/docs/services/kiss_metrics.rst +++ b/docs/services/kiss_metrics.rst @@ -24,7 +24,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`kiss-metrics-configuration`. -The KISSmetrics Javascript code is inserted into templates using a +The KISSmetrics JavaScript code is inserted into templates using a template tag. Load the :mod:`kiss_metrics` template tag library and insert the :ttag:`kiss_metrics` tag. Because every page that you want to track must have the tag, it is useful to add it to your base @@ -53,7 +53,7 @@ Setting the API key Every website you track events for with KISSmetrics gets its own API key, and the :ttag:`kiss_metrics` tag will include it in the rendered -Javascript code. You can find the website API key by visiting the +JavaScript code. You can find the website API key by visiting the website *Product center* on your KISSmetrics dashboard. Set :const:`KISS_METRICS_API_KEY` in the project :file:`settings.py` file:: @@ -144,7 +144,7 @@ For example:: }) return some_template.render(context) -The output script tag will then include the corresponding Javascript event as +The output script tag will then include the corresponding JavaScript event as documented in the `KISSmetrics record API`_ docs. diff --git a/docs/services/mixpanel.rst b/docs/services/mixpanel.rst index afbde2c..cfe1c35 100644 --- a/docs/services/mixpanel.rst +++ b/docs/services/mixpanel.rst @@ -24,7 +24,7 @@ step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`mixpanel-configuration`. -The Mixpanel Javascript code is inserted into templates using a +The Mixpanel JavaScript code is inserted into templates using a template tag. Load the :mod:`mixpanel` template tag library and insert the :ttag:`mixpanel` tag. Because every page that you want to track must have the tag, it is useful to add it to your base @@ -53,7 +53,7 @@ Setting the token ----------------- Every website you track events for with Mixpanel gets its own token, -and the :ttag:`mixpanel` tag will include it in the rendered Javascript +and the :ttag:`mixpanel` tag will include it in the rendered JavaScript code. You can find the project token on the Mixpanel *projects* page. Set :const:`MIXPANEL_API_TOKEN` in the project :file:`settings.py` file:: @@ -137,16 +137,16 @@ For example:: Tracking events =============== -The django-analytical app integrates the Mixpanel Javascript API in +The django-analytical app integrates the Mixpanel JavaScript API in templates. To tracking events in views or other parts of Django, you can use Wes Winham's `mixpanel-celery`_ package. -If you want to track an event in Javascript, use the asynchronous +If you want to track an event in JavaScript, use the asynchronous notation, as described in the section titled -`"Asynchronous Tracking with Javascript"`_ in the Mixpanel +`"Asynchronous Tracking with JavaScript"`_ in the Mixpanel documentation. For example:: mixpanel.track("play-game", {"level": "12", "weapon": "sword", "character": "knight"}); .. _mixpanel-celery: http://github.com/winhamwr/mixpanel-celery -.. _`"Asynchronous Tracking with Javascript"`: http://mixpanel.com/api/docs/guides/integration/js#async +.. _`"Asynchronous Tracking with JavaScript"`: http://mixpanel.com/api/docs/guides/integration/js#async diff --git a/docs/services/olark.rst b/docs/services/olark.rst index 9c5c313..4467ac1 100644 --- a/docs/services/olark.rst +++ b/docs/services/olark.rst @@ -24,7 +24,7 @@ step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`olark-configuration`. -The Olark Javascript code is inserted into templates using a template +The Olark JavaScript code is inserted into templates using a template tag. Load the :mod:`olark` template tag library and insert the :ttag:`olark` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert @@ -52,7 +52,7 @@ Setting the site ID ------------------- In order to install the chat code, you need to set your Olark site ID. -The :ttag:`olark` tag will include it in the rendered Javascript code. +The :ttag:`olark` tag will include it in the rendered JavaScript code. You can find the site ID on `installation page`_ of you Olark account. Set :const:`OLARK_SITE_ID` in the project :file:`settings.py` file:: @@ -93,7 +93,7 @@ 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. -See also `api.chat.updateVisitorNickname`_ in the Olark Javascript API +See also `api.chat.updateVisitorNickname`_ in the Olark JavaScript API documentation. .. _`api.chat.updateVisitorNickname`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorNickname @@ -113,7 +113,7 @@ and the :ttag:`olark` tag will pass them to Olark as status messages:: ]}) return some_template.render(context) -See also `api.chat.updateVisitorStatus`_ in the Olark Javascript API +See also `api.chat.updateVisitorStatus`_ in the Olark JavaScript API documentation. .. _`api.chat.updateVisitorStatus`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorStatus diff --git a/docs/services/optimizely.rst b/docs/services/optimizely.rst index e726042..225784b 100644 --- a/docs/services/optimizely.rst +++ b/docs/services/optimizely.rst @@ -25,7 +25,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`optimizely-configuration`. -The Optimizely Javascript code is inserted into templates using a +The Optimizely JavaScript code is inserted into templates using a template tag. Load the :mod:`optimizely` template tag library and insert the :ttag:`optimizely` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. @@ -53,7 +53,7 @@ Setting the account number -------------------------- Optimizely gives you a unique account number, and the :ttag:`optimizely` -tag will include it in the rendered Javascript code. You can find your +tag will include it in the rendered JavaScript code. You can find your account number by clicking the *Implementation* link in the top right-hand corner of the Optimizely website. A pop-up window will appear containing HTML code looking like this:: @@ -66,7 +66,7 @@ file:: OPTIMIZELY_ACCOUNT_NUMBER = 'XXXXXXX' -If you do not set an account number, the Javascript code will not be +If you do not set an account number, the JavaScript code will not be rendered. diff --git a/docs/services/performable.rst b/docs/services/performable.rst index 43869c3..dafd299 100644 --- a/docs/services/performable.rst +++ b/docs/services/performable.rst @@ -25,7 +25,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`performable-configuration`. -The Performable Javascript code is inserted into templates using a +The Performable JavaScript code is inserted into templates using a template tag. Load the :mod:`performable` template tag library and insert the :ttag:`performable` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. @@ -53,14 +53,14 @@ Setting the API key ------------------- You Performable account has its own API key, which :ttag:`performable` -tag will include it in the rendered Javascript code. You can find your +tag will include it in the rendered JavaScript code. You can find your API key on the *Account Settings* page (click 'Account Settings' in the top right-hand corner of your Performable dashboard). Set :const:`PERFORMABLE_API_KEY` in the project :file:`settings.py` file:: PERFORMABLE_API_KEY = 'XXXXXX' -If you do not set an API key, the Javascript code will not be rendered. +If you do not set an API key, the JavaScript code will not be rendered. .. _performable-identity-user: @@ -116,7 +116,7 @@ Embedding a landing page ======================== You can embed a Performable landing page in your Django website. The -:ttag:`performable_embed` template tag adds the Javascript code to embed +:ttag:`performable_embed` template tag adds the JavaScript code to embed the page. It takes two arguments: the hostname and the page ID:: {% performable_embed HOSTNAME PAGE_ID %} diff --git a/docs/services/rating_mailru.rst b/docs/services/rating_mailru.rst index e905e4a..20a05b5 100644 --- a/docs/services/rating_mailru.rst +++ b/docs/services/rating_mailru.rst @@ -53,7 +53,7 @@ Setting the counter ID Every website you track with Rating\@Mail.ru gets its own counter ID, and the :ttag:`rating_mailru` tag will include it in the rendered -Javascript code. You can find the web counter ID on the overview page +JavaScript code. You can find the web counter ID on the overview page of your account. Set :const:`RATING_MAILRU_COUNTER_ID` in the project :file:`settings.py` file:: diff --git a/docs/services/snapengage.rst b/docs/services/snapengage.rst index db9233f..bb15c2b 100644 --- a/docs/services/snapengage.rst +++ b/docs/services/snapengage.rst @@ -24,7 +24,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`snapengage-configuration`. -The SnapEngage Javascript code is inserted into templates using a +The SnapEngage JavaScript code is inserted into templates using a template tag. Load the :mod:`SnapEngage` template tag library and insert the :ttag:`SnapEngage` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. diff --git a/docs/services/spring_metrics.rst b/docs/services/spring_metrics.rst index e80ebb9..062a180 100644 --- a/docs/services/spring_metrics.rst +++ b/docs/services/spring_metrics.rst @@ -53,7 +53,7 @@ Setting the Tracking ID Every website you track with Spring Metrics gets its own Tracking ID, and the :ttag:`spring_metrics` tag will include it in the rendered -Javascript code. You can find the Tracking ID in the `Site Settings`_ +JavaScript code. You can find the Tracking ID in the `Site Settings`_ of your Spring Metrics account. Set :const:`SPRING_METRICS_TRACKING_ID` in the project :file:`settings.py` file:: diff --git a/docs/services/uservoice.rst b/docs/services/uservoice.rst index dcbbca5..4d1dae7 100644 --- a/docs/services/uservoice.rst +++ b/docs/services/uservoice.rst @@ -29,7 +29,7 @@ This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`uservoice-configuration`. -The UserVoice Javascript code is inserted into templates using a +The UserVoice JavaScript code is inserted into templates using a template tag. Load the :mod:`uservoice` template tag library and insert the :ttag:`uservoice` tag. Because every page that you want to have the feedback tab to appear on must have the tag, it is useful to add @@ -57,8 +57,8 @@ Setting the widget key In order to use the feedback widget, you need to configure which widget you want to show. You can find the widget keys in the *Channels* tab on -your UserVoice *Settings* page. Under the *Javascript Widget* heading, -find the Javascript embed code of the widget. The widget key is the +your UserVoice *Settings* page. Under the *JavaScript Widget* heading, +find the JavaScript embed code of the widget. The widget key is the alphanumerical string contained in the URL of the script imported by the embed code:: diff --git a/docs/services/woopra.rst b/docs/services/woopra.rst index 1a8b122..e2f8f54 100644 --- a/docs/services/woopra.rst +++ b/docs/services/woopra.rst @@ -37,7 +37,7 @@ the tag at the bottom of the HTML head:: ... -Because Javascript code is asynchronous, putting the tag in the head +Because JavaScript code is asynchronous, putting the tag in the head section increases the chances that a page view is going to be tracked before the visitor leaves the page. See for details the `Asynchronous JavaScript Developer’s Guide`_ on the Woopra website. diff --git a/docs/services/yandex_metrica.rst b/docs/services/yandex_metrica.rst index d9861bb..bff16b9 100644 --- a/docs/services/yandex_metrica.rst +++ b/docs/services/yandex_metrica.rst @@ -53,7 +53,7 @@ Setting the counter ID Every website you track with Yandex.Metrica gets its own counter ID, and the :ttag:`yandex_metrica` tag will include it in the rendered -Javascript code. You can find the web counter ID on the overview page +JavaScript code. You can find the web counter ID on the overview page of your account. Set :const:`YANDEX_METRICA_COUNTER_ID` in the project :file:`settings.py` file:: From 10102d10177cbcde96f2ba95c691ea39bca4cce8 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Mon, 21 Jul 2025 16:46:53 +0200 Subject: [PATCH 102/103] Add Erick Massip as author for contributing via PR #226 --- CHANGELOG.rst | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1077d76..4f3cc3f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,6 @@ (unreleased) ------------ +* Fix GA gtag user_id setup and add support for custom dimensions (Erick Massip) * Change spelling of "JavaScript" across all files in docstrings and docs (Peter Bittner) diff --git a/pyproject.toml b/pyproject.toml index a4be958..b51a2f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ authors = [ {name = "Eric Amador", email = "eric.amador14@gmail.com"}, {name = "Eric Davis", email = "eric@davislv.com"}, {name = "Eric Wang", email = "gnawrice@gmail.com"}, + {name = "Erick Massip", email = "ericmassip1@gmail.com"}, {name = "Garrett Coakley", email = "garrettc@users.noreply.github.com"}, {name = "Garrett Robinson", email = "garrett.f.robinson@gmail.com"}, {name = "GreenKahuna", email = "info@greenkahuna.com"}, From 694fc9097a9a3b88425f2707eea423f930601df7 Mon Sep 17 00:00:00 2001 From: Peter Bittner Date: Fri, 11 Jul 2025 00:30:44 +0200 Subject: [PATCH 103/103] Use up-to-date version of PyPI publish Action Fixes an outdated reference that aborted the last release attempt. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38218e3..2e97910 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: - name: Upload packages to Jazzband if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: user: jazzband password: ${{ secrets.JAZZBAND_RELEASE_KEY }}