Compare commits

..

No commits in common. "main" and "v3.1.0" have entirely different histories.
main ... v3.1.0

103 changed files with 1456 additions and 1888 deletions

2
.coveragerc Normal file
View file

@ -0,0 +1,2 @@
[run]
source = analytical

View file

@ -15,21 +15,21 @@ jobs:
fail-fast: false
matrix:
env:
- lint
- format
- audit
- package
- isort
- flake8
- bandit
- readme
- docs
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- uses: actions/setup-python@v5
- uses: actions/setup-python@v2
with:
python-version: '3.12'
python-version: '3.8'
- name: Install prerequisites
run: python -m pip install tox
run: python -m pip install --upgrade setuptools pip wheel tox
- name: Run ${{ matrix.env }}
run: tox -e ${{ matrix.env }}

View file

@ -5,39 +5,48 @@ 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@v4
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v2
with:
python-version: '3.12'
cache: pip
cache-dependency-path: |
**/pyproject.toml
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 tox
python -m pip install -U pip
python -m pip install -U setuptools twine wheel
- name: Build package
run: |
tox -e package
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@release/v1
uses: pypa/gh-action-pypi-publish@master
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}

View file

@ -8,10 +8,6 @@ on:
branches:
- main
env:
PIP_DISABLE_PIP_VERSION_CHECK: '1'
PY_COLORS: '1'
jobs:
python-django:
runs-on: ubuntu-latest
@ -19,33 +15,46 @@ 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:
- '4.2'
- '5.1'
- '5.2'
include:
- { python-version: '3.9', django-version: '4.2' }
- '2.2'
- '3.2'
- '4.0'
exclude:
- { python-version: '3.13', django-version: '4.2' }
- { 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@v4
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: |
**/pyproject.toml
- 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 tox tox-gh-actions
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions
- name: Tox tests (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }})
run: tox
@ -53,6 +62,6 @@ jobs:
DJANGO: ${{ matrix.django-version }}
- name: Upload coverage
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v1
with:
name: Python ${{ matrix.python-version }}

3
.gitignore vendored
View file

@ -18,6 +18,3 @@
/docs/_templates/layout.html
/MANIFEST
*.egg-info
/requirements.txt
/uv.lock

View file

@ -1,18 +0,0 @@
# 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

50
AUTHORS.rst Normal file
View file

@ -0,0 +1,50 @@
The django-analytical package was originally written by `Joost Cassee`_
and is now maintained by the `Jazzband community`_, with contributions
from `Eric Davis`_, `Paul Oswald`_, `Uros Trebec`_, `Steven Skoczen`_,
`Pi Delport`_, `Sandra Mau`_, `Simon Ye`_, `Tinnet Coronam`_,
`Philippe O. Wagner`_, `Max Arnold`_ , `Martín Gaitán`_, `Craig Bruce`_,
`Peter Bittner`_, `Scott Adams`_, `Eric Amador`_, `Alexandre Pocquet`_,
`Brad Pitcher`_, `Hugo Osvaldo Barrera`_, `Nikolay Korotkiy`_,
`Steve Schwarz`_, `Aleck Landgraf`_, `Marc Bourqui`_,
`Diederik van der Boor`_, `Matthäus G. Chajdas`_, `Scott Karlin`_
and others.
Included Javascript code snippets for integration of the analytics
services were written by the respective service providers.
The application was inspired by and uses ideas from Analytical_, Joshua
Krall's all-purpose analytics front-end for Rails.
The work on Crazy Egg was made possible by `Bateau Knowledge`_.
The work on Intercom was made possible by `GreenKahuna`_.
.. _`Joost Cassee`: https://github.com/jcassee
.. _`Jazzband community`: https://jazzband.co/
.. _`Eric Davis`: https://github.com/edavis
.. _`Paul Oswald`: https://github.com/poswald
.. _`Uros Trebec`: https://github.com/failedguidedog
.. _`Steven Skoczen`: https://github.com/skoczen
.. _`Pi Delport`: https://github.com/pjdelport
.. _`Sandra Mau`: https://github.com/xthepoet
.. _`Simon Ye`: https://github.com/yesimon
.. _`Tinnet Coronam`: https://github.com/tinnet
.. _`Philippe O. Wagner`: mailto:admin@arteria.ch
.. _`Max Arnold`: https://github.com/max-arnold
.. _`Martín Gaitán`: https://github.com/mgaitan
.. _`Craig Bruce`: https://github.com/craigbruce
.. _`Peter Bittner`: https://github.com/bittner
.. _`Scott Adams`: https://github.com/7wonders
.. _`Eric Amador`: https://github.com/amadornimbis
.. _`Alexandre Pocquet`: https://github.com/apocquet
.. _`Brad Pitcher`: https://github.com/brad
.. _`Hugo Osvaldo Barrera`: https://github.com/hobarrera
.. _`Nikolay Korotkiy`: https://github.com/sikmir
.. _`Steve Schwarz`: https://github.com/saschwarz
.. _`Aleck Landgraf`: https://github.com/alecklandgraf
.. _`Marc Bourqui`: https://github.com/mbourqui
.. _`Diederik van der Boor`: https://github.com/vdboor
.. _`Matthäus G. Chajdas`: https://github.com/Anteru
.. _`Analytical`: https://github.com/jkrall/analytical
.. _`Bateau Knowledge`: http://www.bateauknowledge.nl/
.. _`GreenKahuna`: http://www.greenkahuna.com/
.. _`Scott Karlin`: https://github.com/sckarlin

View file

@ -1,22 +1,3 @@
(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)
* Ask site visitors for consent when using Matomo (Julian Haluska & Ronard Luna)
Version 3.2.0
-------------
* 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)
* Drop the end-of-life Python 3.8 as required by changed semantics of the
license metadata field (Peter Bittner)
* Remove AUTHORS file to avoid confusion; this is now metadata maintained
in pyproject.toml (Peter Bittner)
* Add more configuration options for Woopra (Peter Bittner)
Version 3.1.0
-------------
* Rename default branch from master to main (Peter Bittner, Jannis Leidel)
@ -218,7 +199,7 @@ Version 0.5.0
-------------
* Split off Geckoboard support into django-geckoboard_.
.. _django-geckoboard: https://pypi.org/project/django-geckoboard
.. _django-geckoboard: http://pypi.python.org/pypi/django-geckoboard
Version 0.4.0
-------------

View file

@ -1,14 +1,14 @@
django-analytical |latest-version|
==================================
|build-status| |coverage| |python-support| |license| |jazzband|
|build-status| |coverage| |python-support| |license| |gitter| |jazzband|
The django-analytical application integrates analytics services into a
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
@ -38,9 +38,12 @@ 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/projects/django-analytical
:target: https://jazzband.co/
.. _`Django`: http://www.djangoproject.com/
Currently Supported Services
@ -74,7 +77,7 @@ Currently Supported Services
* `Yandex.Metrica`_ web analytics
.. _`Chartbeat`: http://www.chartbeat.com/
.. _`Clickmap`: http://clickmap.ch/
.. _`Clickmap`: http://getclickmap.com/
.. _`Clicky`: http://getclicky.com/
.. _`Crazy Egg`: http://www.crazyegg.com/
.. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/
@ -105,7 +108,9 @@ 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`_.
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`_.
.. _`read online`: https://django-analytical.readthedocs.io/
.. _`hosted by GitHub`: https://github.com/jazzband/django-analytical
@ -123,17 +128,11 @@ 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

View file

@ -1,5 +1,8 @@
"""
Analytics service integration for Django projects.
Analytics service integration for Django projects
"""
__version__ = '3.2.0'
__author__ = "Joost Cassee"
__email__ = "joost@cassee.net"
__version__ = "3.1.0"
__copyright__ = "Copyright (C) 2011-2022 Joost Cassee and contributors"

View file

@ -35,6 +35,7 @@ TAG_MODULES = [
'analytical.olark',
'analytical.optimizely',
'analytical.performable',
'analytical.piwik',
'analytical.rating_mailru',
'analytical.snapengage',
'analytical.spring_metrics',
@ -66,7 +67,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,15 +83,14 @@ 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()

View file

@ -12,9 +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 = """<script>var _sf_startpt=(new Date()).getTime()</script>"""
INIT_CODE = """<script type="text/javascript">var _sf_startpt=(new Date()).getTime()</script>"""
SETUP_CODE = """
<script>
<script type="text/javascript">
var _sf_async_config=%(config)s;
(function(){
function loadChartbeat() {
@ -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:
@ -55,7 +55,7 @@ def chartbeat_top(parser, token):
class ChartbeatTopNode(Node):
def render(self, context):
if is_internal_ip(context):
return disable_html(INIT_CODE, 'Chartbeat')
return disable_html(INIT_CODE, "Chartbeat")
return INIT_CODE
@ -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.
"""
@ -76,9 +76,8 @@ def chartbeat_bottom(parser, token):
class ChartbeatBottomNode(Node):
def __init__(self):
self.user_id = get_required_setting(
'CHARTBEAT_USER_ID', USER_ID_RE, 'must be (a string containing) a number'
)
self.user_id = get_required_setting('CHARTBEAT_USER_ID', USER_ID_RE,
"must be (a string containing) a number")
def render(self, context):
config = {'uid': self.user_id}
@ -107,7 +106,6 @@ def _get_domain(context):
return
elif getattr(settings, 'CHARTBEAT_AUTO_DOMAIN', True):
from django.contrib.sites.models import Site
try:
return Site.objects.get_current().domain
except (ImproperlyConfigured, Site.DoesNotExist): # pylint: disable=E1101

View file

@ -10,7 +10,7 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip
CLICKMAP_TRACKER_ID_RE = re.compile(r'^\w+$')
TRACKING_CODE = """
<script>
<script type="text/javascript">
var clickmapConfig = {tracker: '%(tracker_id)s', version:'2'};
window.clickmapAsyncInit = function(){ __clickmap.init(clickmapConfig); };
(function() { var _cmf = document.createElement('script'); _cmf.async = true;
@ -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.
"""
@ -42,11 +42,9 @@ def clickmap(parser, token):
class ClickmapNode(Node):
def __init__(self):
self.tracker_id = get_required_setting(
'CLICKMAP_TRACKER_ID',
CLICKMAP_TRACKER_ID_RE,
'must be an alphanumeric string',
)
self.tracker_id = get_required_setting('CLICKMAP_TRACKER_ID',
CLICKMAP_TRACKER_ID_RE,
"must be an alphanumeric string")
def render(self, context):
html = TRACKING_CODE % {'tracker_id': self.tracker_id}

View file

@ -16,7 +16,7 @@ from analytical.utils import (
SITE_ID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
<script>
<script type="text/javascript">
var clicky = { log: function(){ return; }, goal: function(){ return; }};
var clicky_site_ids = clicky_site_ids || [];
clicky_site_ids.push(%(site_id)s);
@ -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.
"""
@ -53,8 +53,8 @@ def clicky(parser, token):
class ClickyNode(Node):
def __init__(self):
self.site_id = get_required_setting(
'CLICKY_SITE_ID', SITE_ID_RE, 'must be a (string containing) a number'
)
'CLICKY_SITE_ID', SITE_ID_RE,
"must be a (string containing) a number")
def render(self, context):
custom = {}

View file

@ -9,10 +9,10 @@ from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import disable_html, get_required_setting, is_internal_ip
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
SETUP_CODE = '<script src="{placeholder_url}"></script>'.format(
placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/'
'%(account_nr_1)s/%(account_nr_2)s.js'
)
SETUP_CODE = '<script type="text/javascript" src="{placeholder_url}">' \
'</script>'.\
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');"
@ -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.
"""
@ -38,8 +38,7 @@ 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):
@ -50,15 +49,12 @@ 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<script>%s</script>' % (html, js)
js = " ".join(USERVAR_CODE % {
'varnr': varnr,
'value': value,
} for (varnr, value) in params)
html = '%s\n' \
'<script type="text/javascript">%s</script>' % (html, js)
if is_internal_ip(context, 'CRAZY_EGG'):
html = disable_html(html, 'Crazy Egg')
return html

View file

@ -60,12 +60,11 @@ 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):

View file

@ -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 = """
<script>
<script type="text/javascript">
var _gauges = _gauges || [];
(function() {
var t = document.createElement('script');
@ -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.
"""
@ -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}

View file

@ -28,7 +28,7 @@ SCOPE_PAGE = 3
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
SETUP_CODE = """
<script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '%(property_id)s']);
@ -45,9 +45,8 @@ 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']);"
@ -55,10 +54,7 @@ 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')
@ -71,7 +67,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.
"""
@ -84,10 +80,8 @@ 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)
@ -100,7 +94,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],
}
@ -110,17 +104,15 @@ 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:
@ -128,7 +120,9 @@ 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:
@ -138,15 +132,12 @@ 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):
@ -161,42 +152,29 @@ 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

View file

@ -1,9 +1,7 @@
"""
Google Analytics template tags and filters, using the new gtag.js library.
https://developers.google.com/tag-platform/gtagjs/reference
Google Analytics template tags and filters, using the new analytics.js library.
"""
import json
import re
from django.template import Library, Node, TemplateSyntaxError
@ -15,9 +13,7 @@ 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 = """
<script async src="https://www.googletagmanager.com/gtag/js?id={property_id}"></script>
<script>
@ -25,10 +21,13 @@ SETUP_CODE = """
function gtag(){{dataLayer.push(arguments);}}
gtag('js', new Date());
gtag('config', '{property_id}', {custom_dimensions});
{extra}
gtag('config', '{property_id}');
</script>
"""
GTAG_SET_CODE = """gtag('set', {{'{key}': '{value}'}});"""
register = Library()
@ -37,7 +36,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.
"""
@ -50,23 +49,24 @@ def google_analytics_gtag(parser, token):
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
'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')""",
)
'G-XXXXXXXX', 'DC-XXXXXXXX')''')
def render(self, context):
custom_dimensions = context.get('google_analytics_custom_dimensions', {})
other_fields = {}
identity = get_identity(context, prefix='google_analytics_gtag')
identity = get_identity(context)
if identity is not None:
custom_dimensions['user_id'] = identity
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,
custom_dimensions=json.dumps(custom_dimensions),
extra=extra,
)
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')

View file

@ -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','{js_source}','ga');
}})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{property_id}', 'auto', {create_fields});
{commands}ga('send', 'pageview');
@ -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.
"""
@ -57,35 +57,23 @@ def google_analytics_js(parser, token):
class GoogleAnalyticsJsNode(Node):
def __init__(self):
self.property_id = get_required_setting(
'GOOGLE_ANALYTICS_JS_PROPERTY_ID',
PROPERTY_ID_RE,
"must be a string looking like 'UA-XXXXXX-Y'",
)
'GOOGLE_ANALYTICS_JS_PROPERTY_ID', PROPERTY_ID_RE,
"must be a string looking like 'UA-XXXXXX-Y'")
def render(self, context):
import json
create_fields = self._get_domain_fields(context)
create_fields.update(self._get_other_create_fields(context))
commands = self._get_custom_var_commands(context)
commands.extend(self._get_other_commands(context))
display_features = getattr(
settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False
)
display_features = getattr(settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False)
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,
commands="".join(commands),
)
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')
@ -93,17 +81,14 @@ class GoogleAnalyticsJsNode(Node):
def _get_domain_fields(self, context):
domain_fields = {}
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")
domain_fields['legacyCookieDomain'] = domain
if tracking_type == TRACK_MULTIPLE_DOMAINS:
domain_fields['allowLinker'] = True
@ -112,39 +97,34 @@ class GoogleAnalyticsJsNode(Node):
def _get_other_create_fields(self, context):
other_fields = {}
site_speed_sample_rate = getattr(
settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False
)
site_speed_sample_rate = getattr(settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False)
if site_speed_sample_rate is not False:
value = int(decimal.Decimal(site_speed_sample_rate))
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")
other_fields['siteSpeedSampleRate'] = value
sample_rate = getattr(settings, 'GOOGLE_ANALYTICS_SAMPLE_RATE', False)
if sample_rate is not False:
value = int(decimal.Decimal(sample_rate))
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")
other_fields['sampleRate'] = value
cookie_expires = getattr(settings, 'GOOGLE_ANALYTICS_COOKIE_EXPIRATION', False)
if cookie_expires is not False:
value = int(decimal.Decimal(cookie_expires))
if value < 0:
raise AnalyticalException(
"'GOOGLE_ANALYTICS_COOKIE_EXPIRATION' must be >= 0"
)
raise AnalyticalException("'GOOGLE_ANALYTICS_COOKIE_EXPIRATION' must be >= 0")
other_fields['cookieExpires'] = value
return other_fields
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 _, var in params:
@ -154,12 +134,10 @@ class GoogleAnalyticsJsNode(Node):
float(value)
except ValueError:
value = f"'{value}'"
commands.append(
CUSTOM_VAR_CODE.format(
name=name,
value=value,
)
)
commands.append(CUSTOM_VAR_CODE.format(
name=name,
value=value,
))
return commands
def _get_other_commands(self, context):

View file

@ -15,7 +15,7 @@ from analytical.utils import (
TOKEN_RE = re.compile(r'^\S+-\S+-\S+$')
TRACKING_CODE = """
<script>
<script type="text/javascript">
var GoSquared={};
%(config)s
(function(w){
@ -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()
@ -52,10 +52,8 @@ def gosquared(parser, token):
class GoSquaredNode(Node):
def __init__(self):
self.site_token = get_required_setting(
'GOSQUARED_SITE_TOKEN',
TOKEN_RE,
'must be a string looking like XXX-XXXXXX-X',
)
'GOSQUARED_SITE_TOKEN', TOKEN_RE,
"must be a string looking like XXX-XXXXXX-X")
def render(self, context):
configs = [TOKEN_CODE % self.site_token]

View file

@ -10,12 +10,12 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip
HEAP_TRACKER_ID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
<script>
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
<script type="text/javascript">
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
heap.load("%(tracker_id)s");
</script>
""" # noqa
""" # noqa
register = Library()
@ -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.
"""
@ -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}

View file

@ -41,11 +41,12 @@ 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):

View file

@ -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 = """
<!-- Start of Async HubSpot Analytics Code -->
<script>
<script type="text/javascript">
(function(d,s,i,r) {
if (d.getElementById(i)){return;}
var n=d.createElement(s),e=d.getElementsByTagName(s)[0];
@ -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()
@ -41,9 +41,8 @@ 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}

View file

@ -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.
"""
@ -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,7 +95,8 @@ 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
@ -119,8 +120,10 @@ 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')

View file

@ -11,8 +11,8 @@ from analytical.utils import get_identity, get_required_setting
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
SITE_CODE_RE = re.compile(r'^[\w]+$')
SETUP_CODE = """
<script>var _kiq = _kiq || []; %(commands)s</script>
<script src="//s3.amazonaws.com/ki.js/%(account_number)s/%(site_code)s.js" async="true"></script>
<script type="text/javascript">var _kiq = _kiq || []; %(commands)s</script>
<script type="text/javascript" src="//s3.amazonaws.com/ki.js/%(account_number)s/%(site_code)s.js" async="true"></script>
""" # noqa
IDENTIFY_CODE = "_kiq.push(['identify', '%s']);"
SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);"
@ -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.
@ -41,15 +41,11 @@ 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 = []
@ -63,7 +59,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

View file

@ -16,7 +16,7 @@ from analytical.utils import (
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
TRACKING_CODE = """
<script>
<script type="text/javascript">
var _kmq = _kmq || [];
%(commands)s
function _kms(u){
@ -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.
"""
@ -63,10 +63,8 @@ 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 = []
@ -81,28 +79,22 @@ 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')

View file

@ -39,11 +39,12 @@ 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):

View file

@ -23,7 +23,7 @@ DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)
SITEID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
<script>
<script type="text/javascript">
var _paq = window._paq || [];
%(variables)s
%(commands)s
@ -37,38 +37,12 @@ TRACKING_CODE = """
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="//%(url)s/matomo.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
<noscript><p><img src="//%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
""" # 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']);"
GIVE_CONSENT_CLASS = 'matomo_give_consent'
REMOVE_CONSENT_CLASS = 'matomo_remove_consent'
ASK_FOR_CONSENT_CODE = """
_paq.push(['requireConsent']);
var elements = document.getElementsByClassName("{}");
for (var i = 0; i < elements.length; i++) {{
elements[i].addEventListener("click",
function () {{
_paq.push(["forgetConsentGiven"]);
}}
);
}}
var elements = document.getElementsByClassName("{}");
for (var i = 0; i < elements.length; i++) {{
elements[i].addEventListener("click",
function () {{
_paq.push(["rememberConsentGiven"]);
}}
);
}}
""".format(REMOVE_CONSENT_CLASS, GIVE_CONSENT_CLASS)
DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);'
DEFAULT_SCOPE = 'page'
@ -83,7 +57,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.
@ -101,46 +75,39 @@ 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):
commands.append(DISABLE_COOKIES_CODE)
if getattr(settings, 'MATOMO_ASK_FOR_CONSENT', False):
commands.append(ASK_FOR_CONSENT_CODE)
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')

View file

@ -17,7 +17,7 @@ from analytical.utils import (
MIXPANEL_API_TOKEN_RE = re.compile(r'^[0-9a-f]{32}$')
TRACKING_CODE = """
<script>(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
<script type="text/javascript">(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,
e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
mixpanel.init('%(token)s');
@ -25,7 +25,7 @@ e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
</script>
""" # 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'
@ -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()
@ -49,38 +49,29 @@ 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')

View file

@ -24,29 +24,16 @@ 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()
@ -57,7 +44,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()
@ -69,10 +56,8 @@ 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 = []
@ -91,22 +76,21 @@ 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

View file

@ -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.
"""
@ -33,10 +33,8 @@ 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}

View file

@ -16,17 +16,17 @@ from analytical.utils import (
API_KEY_RE = re.compile(r'^\w+$')
SETUP_CODE = """
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/pax/%(api_key)s.js"></script>
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/pax/%(api_key)s.js" type="text/javascript"></script>
""" # noqa
IDENTIFY_CODE = """
<script>
<script type="text/javascript">
var _paq = _paq || [];
_paq.push(["identify", {identity: "%s"}]);
</script>
"""
EMBED_CODE = """
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/embed/page.js"></script>
<script>
<script type="text/javascript" src="//d1nu2rn22elx8m.cloudfront.net/performable/embed/page.js"></script>
<script type="text/javascript">
(function() {
var $f = new PerformableEmbed();
$f.initialize({'host': '%(hostname)s', 'page': '%(page_id)s'});
@ -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.
"""
@ -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,13 +74,10 @@ 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):

View file

@ -0,0 +1,121 @@
"""
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 = """
<script type="text/javascript">
var _paq = _paq || [];
%(variables)s
%(commands)s
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//%(url)s/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', %(siteid)s]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="//%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
""" # 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)

View file

@ -10,7 +10,7 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip
COUNTER_ID_RE = re.compile(r'^\d{7}$')
COUNTER_CODE = """
<script>
<script type="text/javascript">
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({id: "%(counter_id)s", type: "pageView", start: (new Date()).getTime()});
(function (d, w, id) {
@ -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.
"""
@ -48,10 +48,8 @@ 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 % {

View file

@ -24,21 +24,17 @@ 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 = """
<script>
document.write(unescape("%%3Cscript src='" + ((document.location.protocol=="https:")?"https://snapabug.appspot.com":"http://www.snapengage.com") + "/snapabug.js' type='text/javascript'%%3E%%3C/script%%3E"));</script><script>
<script type="text/javascript">
document.write(unescape("%%3Cscript src='" + ((document.location.protocol=="https:")?"https://snapabug.appspot.com":"http://www.snapengage.com") + "/snapabug.js' type='text/javascript'%%3E%%3C/script%%3E"));</script><script type="text/javascript">
%(settings_code)s
</script>
""" # noqa
DOMAIN_CODE = 'SnapABug.setDomain("%s");'
SECURE_CONNECTION_CODE = 'SnapABug.setSecureConnexion();'
INIT_CODE = 'SnapABug.init("%s");'
ADDBUTTON_CODE = (
'SnapABug.addButton("%(id)s","%(location)s","%(offset)s"%(dynamic_tail)s);'
)
ADDBUTTON_CODE = 'SnapABug.addButton("%(id)s","%(location)s","%(offset)s"%(dynamic_tail)s);'
SETBUTTON_CODE = 'SnapABug.setButton("%s");'
SETEMAIL_CODE = 'SnapABug.setUserEmail("%s"%s);'
SETLOCALE_CODE = 'SnapABug.setLocale("%s");'
@ -59,7 +55,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()
@ -71,24 +67,21 @@ def snapengage(parser, token):
class SnapEngageNode(Node):
def __init__(self):
self.widget_id = get_required_setting(
'SNAPENGAGE_WIDGET_ID',
WIDGET_ID_RE,
"must be a string looking like this: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'",
)
'SNAPENGAGE_WIDGET_ID', WIDGET_ID_RE,
"must be a string looking like this: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'")
def render(self, context):
settings_code = []
domain = self._get_setting(context, 'snapengage_domain', 'SNAPENGAGE_DOMAIN')
domain = self._get_setting(context, 'snapengage_domain',
'SNAPENGAGE_DOMAIN')
if domain is not None:
settings_code.append(DOMAIN_CODE % domain)
secure_connection = self._get_setting(
context,
'snapengage_secure_connection',
'SNAPENGAGE_SECURE_CONNECTION',
False,
)
secure_connection = self._get_setting(context,
'snapengage_secure_connection',
'SNAPENGAGE_SECURE_CONNECTION',
False)
if secure_connection:
settings_code.append(SECURE_CONNECTION_CODE)
@ -96,70 +89,61 @@ class SnapEngageNode(Node):
if email is None:
email = get_identity(context, 'snapengage', lambda u: u.email)
if email is not None:
if self._get_setting(
context, 'snapengage_readonly_email', 'SNAPENGAGE_READONLY_EMAIL', False
):
if self._get_setting(context, 'snapengage_readonly_email',
'SNAPENGAGE_READONLY_EMAIL', False):
readonly_tail = ',true'
else:
readonly_tail = ''
settings_code.append(SETEMAIL_CODE % (email, readonly_tail))
locale = self._get_setting(context, 'snapengage_locale', 'SNAPENGAGE_LOCALE')
locale = self._get_setting(context, 'snapengage_locale',
'SNAPENGAGE_LOCALE')
if locale is None:
locale = translation.to_locale(translation.get_language())
settings_code.append(SETLOCALE_CODE % locale)
form_position = self._get_setting(
context, 'snapengage_form_position', 'SNAPENGAGE_FORM_POSITION'
)
form_position = self._get_setting(context,
'snapengage_form_position', 'SNAPENGAGE_FORM_POSITION')
if form_position is not None:
settings_code.append(FORM_POSITION_CODE % form_position)
form_top_position = self._get_setting(
context, 'snapengage_form_top_position', 'SNAPENGAGE_FORM_TOP_POSITION'
)
form_top_position = self._get_setting(context,
'snapengage_form_top_position',
'SNAPENGAGE_FORM_TOP_POSITION')
if form_top_position is not None:
settings_code.append(FORM_TOP_POSITION_CODE % form_top_position)
show_offline = self._get_setting(
context, 'snapengage_show_offline', 'SNAPENGAGE_SHOW_OFFLINE', True
)
show_offline = self._get_setting(context, 'snapengage_show_offline',
'SNAPENGAGE_SHOW_OFFLINE', True)
if not show_offline:
settings_code.append(DISABLE_OFFLINE_CODE)
screenshots = self._get_setting(
context, 'snapengage_screenshots', 'SNAPENGAGE_SCREENSHOTS', True
)
screenshots = self._get_setting(context, 'snapengage_screenshots',
'SNAPENGAGE_SCREENSHOTS', True)
if not screenshots:
settings_code.append(DISABLE_SCREENSHOT_CODE)
offline_screenshots = self._get_setting(
context,
'snapengage_offline_screenshots',
'SNAPENGAGE_OFFLINE_SCREENSHOTS',
True,
)
offline_screenshots = self._get_setting(context,
'snapengage_offline_screenshots',
'SNAPENGAGE_OFFLINE_SCREENSHOTS', True)
if not offline_screenshots:
settings_code.append(DISABLE_OFFLINE_SCREENSHOT_CODE)
if not context.get('snapengage_proactive_chat', True):
settings_code.append(DISABLE_PROACTIVE_CHAT_CODE)
sounds = self._get_setting(
context, 'snapengage_sounds', 'SNAPENGAGE_SOUNDS', True
)
sounds = self._get_setting(context, 'snapengage_sounds',
'SNAPENGAGE_SOUNDS', True)
if not sounds:
settings_code.append(DISABLE_SOUNDS_CODE)
button_effect = self._get_setting(
context, 'snapengage_button_effect', 'SNAPENGAGE_BUTTON_EFFECT'
)
button_effect = self._get_setting(context, 'snapengage_button_effect',
'SNAPENGAGE_BUTTON_EFFECT')
if button_effect is not None:
settings_code.append(BUTTONEFFECT_CODE % button_effect)
button = self._get_setting(
context, 'snapengage_button', 'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT
)
button = self._get_setting(context, 'snapengage_button',
'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT)
if button == BUTTON_STYLE_NONE:
settings_code.append(INIT_CODE % self.widget_id)
else:
@ -168,28 +152,21 @@ class SnapEngageNode(Node):
settings_code.append(SETBUTTON_CODE % button)
button_location = self._get_setting(
context,
'snapengage_button_location',
'SNAPENGAGE_BUTTON_LOCATION',
BUTTON_LOCATION_LEFT,
)
'snapengage_button_location', 'SNAPENGAGE_BUTTON_LOCATION',
BUTTON_LOCATION_LEFT)
button_offset = self._get_setting(
context,
'snapengage_button_location_offset',
'SNAPENGAGE_BUTTON_LOCATION_OFFSET',
'55%',
)
settings_code.append(
ADDBUTTON_CODE
% {
'id': self.widget_id,
'location': button_location,
'offset': button_offset,
'dynamic_tail': ',true' if (button == BUTTON_STYLE_LIVE) else '',
}
)
'SNAPENGAGE_BUTTON_LOCATION_OFFSET', '55%')
settings_code.append(ADDBUTTON_CODE % {
'id': self.widget_id,
'location': button_location,
'offset': button_offset,
'dynamic_tail': ',true' if (button == BUTTON_STYLE_LIVE) else '',
})
html = SETUP_CODE % {
'widget_id': self.widget_id,
'settings_code': ' '.join(settings_code),
'settings_code': " ".join(settings_code),
}
return html

View file

@ -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.
"""
@ -53,8 +53,8 @@ def spring_metrics(parser, token):
class SpringMetricsNode(Node):
def __init__(self):
self.tracking_id = get_required_setting(
'SPRING_METRICS_TRACKING_ID', TRACKING_ID_RE, 'must be a hexadecimal string'
)
'SPRING_METRICS_TRACKING_ID',
TRACKING_ID_RE, "must be a hexadecimal string")
def render(self, context):
custom = {}
@ -63,7 +63,8 @@ class SpringMetricsNode(Node):
if var.startswith('spring_metrics_'):
custom[var[15:]] = val
if 'email' not in custom:
identity = get_identity(context, 'spring_metrics', lambda u: u.email)
identity = get_identity(context, 'spring_metrics',
lambda u: u.email)
if identity is not None:
custom['email'] = identity
@ -80,11 +81,9 @@ class SpringMetricsNode(Node):
convert = params.pop('convert', None)
if convert is not None:
commands.append("_springMetq.push(['convert', '%s'])" % convert)
commands.extend(
"_springMetq.push(['setdata', {'%s': '%s'}]);" % (var, val)
for var, val in params.items()
)
return ' '.join(commands)
commands.extend("_springMetq.push(['setdata', {'%s': '%s'}]);"
% (var, val) for var, val in params.items())
return " ".join(commands)
def contribute_to_analytical(add_node):

View file

@ -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 = """
<script>
<script type="text/javascript">
UserVoice=window.UserVoice||[];(function(){
var uv=document.createElement('script');uv.type='text/javascript';
@ -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.
"""
@ -48,8 +48,7 @@ def uservoice(parser, token):
class UserVoiceNode(Node):
def __init__(self):
self.default_widget_key = get_required_setting(
'USERVOICE_WIDGET_KEY', WIDGET_KEY_RE, 'must be an alphanumeric string'
)
'USERVOICE_WIDGET_KEY', WIDGET_KEY_RE, "must be an alphanumeric string")
def render(self, context):
widget_key = context.get('uservoice_widget_key')
@ -66,16 +65,13 @@ class UserVoiceNode(Node):
if identity:
identity = IDENTITY % {'options': json.dumps(identity, sort_keys=True)}
trigger = context.get(
'uservoice_add_trigger', getattr(settings, 'USERVOICE_ADD_TRIGGER', True)
)
trigger = context.get('uservoice_add_trigger',
getattr(settings, 'USERVOICE_ADD_TRIGGER', True))
html = TRACKING_CODE % {
'widget_key': widget_key,
'options': json.dumps(options, sort_keys=True),
'trigger': TRIGGER if trigger else '',
'identity': identity if identity else '',
}
html = TRACKING_CODE % {'widget_key': widget_key,
'options': json.dumps(options, sort_keys=True),
'trigger': TRIGGER if trigger else '',
'identity': identity if identity else ''}
return html
def _identify(self, user):

View file

@ -4,13 +4,11 @@ Woopra template tags and filters.
import json
import re
from contextlib import suppress
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (
AnalyticalException,
disable_html,
get_identity,
get_required_setting,
@ -21,7 +19,7 @@ from analytical.utils import (
DOMAIN_RE = re.compile(r'^\S+$')
TRACKING_CODE = """
<script>
<script type="text/javascript">
var woo_settings = %(settings)s;
var woo_visitor = %(visitor)s;
!function(){var a,b,c,d=window,e=document,f=arguments,g="script",h=["config","track","trackForm","trackClick","identify","visit","push","call"],i=function(){var a,b=this,c=function(a){b[a]=function(){return b._e.push([a].concat(Array.prototype.slice.call(arguments,0))),b}};for(b._e=[],a=0;a<h.length;a++)c(h[a])};for(d.__woo=d.__woo||{},a=0;a<f.length;a++)d.__woo[f[a]]=d[f[a]]=d[f[a]]||new i;b=e.createElement(g),b.async=1,b.src="//static.woopra.com/js/w.js",c=e.getElementsByTagName(g)[0],c.parentNode.insertBefore(b,c)}("woopra");
@ -39,7 +37,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()
@ -51,8 +49,8 @@ def woopra(parser, token):
class WoopraNode(Node):
def __init__(self):
self.domain = get_required_setting(
'WOOPRA_DOMAIN', DOMAIN_RE, 'must be a domain name'
)
'WOOPRA_DOMAIN', DOMAIN_RE,
"must be a domain name")
def render(self, context):
settings = self._get_settings(context)
@ -68,42 +66,10 @@ class WoopraNode(Node):
def _get_settings(self, context):
variables = {'domain': self.domain}
woopra_int_settings = {
'idle_timeout': 'WOOPRA_IDLE_TIMEOUT',
}
woopra_str_settings = {
'cookie_name': 'WOOPRA_COOKIE_NAME',
'cookie_domain': 'WOOPRA_COOKIE_DOMAIN',
'cookie_path': 'WOOPRA_COOKIE_PATH',
'cookie_expire': 'WOOPRA_COOKIE_EXPIRE',
}
woopra_bool_settings = {
'click_tracking': 'WOOPRA_CLICK_TRACKING',
'download_tracking': 'WOOPRA_DOWNLOAD_TRACKING',
'outgoing_tracking': 'WOOPRA_OUTGOING_TRACKING',
'outgoing_ignore_subdomain': 'WOOPRA_OUTGOING_IGNORE_SUBDOMAIN',
'ignore_query_url': 'WOOPRA_IGNORE_QUERY_URL',
'hide_campaign': 'WOOPRA_HIDE_CAMPAIGN',
}
for key, name in woopra_int_settings.items():
with suppress(AttributeError):
variables[key] = getattr(settings, name)
if type(variables[key]) is not int:
raise AnalyticalException(f'{name} must be an int value')
for key, name in woopra_str_settings.items():
with suppress(AttributeError):
variables[key] = getattr(settings, name)
if type(variables[key]) is not str:
raise AnalyticalException(f'{name} must be a string value')
for key, name in woopra_bool_settings.items():
with suppress(AttributeError):
variables[key] = getattr(settings, name)
if type(variables[key]) is not bool:
raise AnalyticalException(f'{name} must be a boolean value')
try:
variables['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT)
except AttributeError:
pass
return variables
def _get_visitor(self, context):
@ -115,7 +81,8 @@ class WoopraNode(Node):
if 'name' not in params and 'email' not in params:
user = get_user_from_context(context)
if user is not None and get_user_is_authenticated(user):
params['name'] = get_identity(context, 'woopra', self._identify, user)
params['name'] = get_identity(
context, 'woopra', self._identify, user)
if user.email:
params['email'] = user.email
return params

View file

@ -12,7 +12,7 @@ from analytical.utils import disable_html, get_required_setting, is_internal_ip
COUNTER_ID_RE = re.compile(r'^\d{8}$')
COUNTER_CODE = """
<script>
<script type="text/javascript">
(function (d, w, c) {
(w[c] = w[c] || []).push(function() {
try {
@ -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.
"""
@ -57,17 +57,15 @@ def yandex_metrica(parser, token):
class YandexMetricaNode(Node):
def __init__(self):
self.counter_id = get_required_setting(
'YANDEX_METRICA_COUNTER_ID',
COUNTER_ID_RE,
"must be (a string containing) a number'",
)
'YANDEX_METRICA_COUNTER_ID', COUNTER_ID_RE,
"must be (a string containing) a number'")
def render(self, context):
options = {
'id': int(self.counter_id),
'clickmap': True,
'trackLinks': True,
'accurateTrackBounce': True,
'accurateTrackBounce': True
}
if getattr(settings, 'YANDEX_METRICA_WEBVISOR', False):
options['webvisor'] = True

View file

@ -5,7 +5,8 @@ Utility function for django-analytical.
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
HTML_COMMENT = '<!-- %(service)s disabled on internal IP address\n%(html)s\n-->'
HTML_COMMENT = "<!-- %(service)s disabled on internal IP " \
"address\n%(html)s\n-->"
def get_required_setting(setting, value_re, invalid_msg):
@ -18,14 +19,13 @@ def get_required_setting(setting, value_re, invalid_msg):
try:
value = getattr(settings, setting)
except AttributeError:
raise AnalyticalException('%s setting: not found' % setting)
raise AnalyticalException("%s setting: not found" % setting)
if not value:
raise AnalyticalException('%s setting is not set' % setting)
raise AnalyticalException("%s setting is not set" % setting)
value = str(value)
if not value_re.search(value):
raise AnalyticalException(
"%s setting: %s: '%s'" % (setting, invalid_msg, value)
)
raise AnalyticalException("%s setting: %s: '%s'"
% (setting, invalid_msg, value))
return value
@ -112,7 +112,6 @@ def get_domain(context, prefix):
if domain is None:
if 'django.contrib.sites' in settings.INSTALLED_APPS:
from django.contrib.sites.models import Site
try:
domain = Site.objects.get_current().domain
except (ImproperlyConfigured, Site.DoesNotExist):
@ -163,5 +162,4 @@ class AnalyticalException(Exception):
Raised when an exception occurs in any django-analytical code that should
be silenced in templates.
"""
silent_variable_failure = True

View file

@ -1,21 +1,21 @@
def setup(app):
app.add_crossref_type(
directivename='setting',
rolename='setting',
indextemplate='pair: %s; setting',
directivename="setting",
rolename="setting",
indextemplate="pair: %s; setting",
)
app.add_crossref_type(
directivename='templatetag',
rolename='ttag',
indextemplate='pair: %s; template tag',
directivename="templatetag",
rolename="ttag",
indextemplate="pair: %s; template tag"
)
app.add_crossref_type(
directivename='templatefilter',
rolename='tfilter',
indextemplate='pair: %s; template filter',
directivename="templatefilter",
rolename="tfilter",
indextemplate="pair: %s; template filter"
)
app.add_crossref_type(
directivename='fieldlookup',
rolename='lookup',
indextemplate='pair: %s; field lookup type',
directivename="fieldlookup",
rolename="lookup",
indextemplate="pair: %s; field lookup type",
)

View file

@ -13,7 +13,7 @@ import analytical # noqa
# -- General configuration --------------------------------------------------
project = 'django-analytical'
copyright = '2011, Joost Cassee <joost@cassee.net>'
copyright = '2011-2020, Joost Cassee <joost@cassee.net>'
release = analytical.__version__
# The short X.Y version.
@ -21,15 +21,16 @@ version = release.rsplit('.', 1)[0]
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'local']
templates_path = ['_templates']
source_suffix = {'.rst': 'restructuredtext'}
source_suffix = '.rst'
master_doc = 'index'
add_function_parentheses = True
pygments_style = 'sphinx'
intersphinx_mapping = {
'python': ('https://docs.python.org/3.13', None),
'django': ('https://docs.djangoproject.com/en/stable', None),
'http://docs.python.org/2.7': None,
'http://docs.djangoproject.com/en/1.9':
'http://docs.djangoproject.com/en/1.9/_objects/',
}
@ -42,11 +43,6 @@ htmlhelp_basename = 'analyticaldoc'
# -- Options for LaTeX output -----------------------------------------------
latex_documents = [
(
'index',
'django-analytical.tex',
'Documentation for django-analytical',
'Joost Cassee',
'manual',
),
('index', 'django-analytical.tex', 'Documentation for django-analytical',
'Joost Cassee', 'manual'),
]

View file

@ -68,52 +68,3 @@ 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 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)
or in the template:
.. code-block:: django
{% with analytical_identity=request.user.uuid|default:None %}
{% analytical_head_top %}
{% endwith %}
or by implementing a context processor, e.g.
.. 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 the ``*`` prefix is the module name
of the specific provider.

View file

@ -23,21 +23,8 @@ considered to be a backward-incompatible change.
Credits
=======
The django-analytical package was originally written by `Joost Cassee`_
and is now maintained by `Peter Bittner`_ and the `Jazzband community`_.
All known contributors are listed as ``authors`` in the `project metadata`_.
.. include:: ../AUTHORS.rst
Included JavaScript code snippets for integration of the analytics services
were written by the respective service providers.
The application was inspired by and uses ideas from Analytical_, Joshua
Krall's all-purpose analytics front-end for Rails.
.. _`Joost Cassee`: https://github.com/jcassee
.. _`Peter Bittner`: https://github.com/bittner
.. _`Jazzband community`: https://jazzband.co/
.. _`project metadata`: https://github.com/jazzband/django-analytical/blob/main/pyproject.toml#L15-L60
.. _`Analytical`: https://github.com/jkrall/analytical
.. _helping-out:

View file

@ -7,7 +7,7 @@ Django_ project.
.. _Django: https://www.djangoproject.com/
:Package: https://pypi.org/project/django-analytical/
:Package: https://pypi.python.org/pypi/django-analytical/
:Source: https://github.com/jazzband/django-analytical

View file

@ -34,7 +34,7 @@ get the development code:
$ git clone https://github.com/jazzband/django-analytical.git
.. _PyPI: https://pypi.org/project/django-analytical/
.. _PyPI: http://pypi.python.org/pypi/django-analytical/
.. _GitHub: http://github.com/jazzband/django-analytical
Then install the package by running the setup script:
@ -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
@ -188,6 +188,11 @@ settings required to enable each service are listed here:
PERFORMABLE_API_KEY = '123abc'
* :doc:`Piwik (deprecated, see Matomo) <services/piwik>`::
PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path'
PIWIK_SITE_ID = '123'
* :doc:`Rating\@Mail.ru <services/rating_mailru>`::
RATING_MAILRU_COUNTER_ID = '1234567'

View file

@ -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`

View file

@ -2,10 +2,9 @@
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.clickmap.ch/
.. _`Clickmap`: http://www.getclickmap.com/
.. clickmap-installation:
@ -23,7 +22,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
@ -45,7 +44,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.clickmap.ch/
.. _`sign up`: http://www.getclickmap.com/
.. _clickmap-tracker-id:
@ -54,7 +53,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::
@ -73,6 +72,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.

View file

@ -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

View file

@ -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`

View file

@ -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,12 +51,12 @@ 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::
<script>
<script type="text/javascript">
var _gauges = _gauges || [];
(function() {
var t = document.createElement('script');
@ -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.
@ -88,6 +88,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.

View file

@ -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::

View file

@ -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-gtag`.
: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
@ -43,7 +43,7 @@ template. Insert the tag at the bottom of the HTML head::
...
.. _google-analytics-configuration-gtag:
.. _google-analytics-configuration:
Configuration
=============
@ -54,14 +54,14 @@ code, you also need to set-up the domain. Finally, you can add custom
segments for Google Analytics to track.
.. _google-analytics-gtag-property-id:
.. _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
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::
@ -94,75 +94,4 @@ 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:
.. code-block:: python
context = RequestContext({'google_analytics_gtag_identity': user.uuid})
return some_template.render(context)
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
.. _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
automatically as the `user_id`. See :ref:`identifying-visitors`.

View file

@ -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-js`.
:ref:`google-analytics-configuration`.
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-js:
.. _google-analytics-configuration:
Configuration
=============
@ -54,14 +54,14 @@ code, you also need to set-up the domain. Finally, you can add custom
segments for Google Analytics to track.
.. _google-analytics-js-property-id:
.. _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_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::
@ -117,7 +117,7 @@ By default, display advertising features are disabled.
.. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482
.. _google-analytics-js-internal-ips:
.. _google-analytics-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-js-custom-variables:
.. _google-analytics-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-js-anonimyze-ips:
.. _google-analytics-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-js-sample-rate:
.. _google-analytics-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-js-site-speed-sample-rate:
.. _google-analytics-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::
@ -228,11 +228,3 @@ 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'

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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.
@ -149,28 +149,6 @@ If you want to `disable cookies`_, set :data:`MATOMO_DISABLE_COOKIES` to
.. _`disable cookies`: https://matomo.org/faq/general/faq_157/
Ask for consent
---------------
If you do not want to track visitors without permission, you can `ask for consent`_ first.
To enable this, set :data:`MATOMO_ASK_FOR_CONSENT` to :const:`True`.
By default, no consent for tracking is needed (i.e. :const:`False`).
To give and remove consent in your page, create DOM elements with the following classes:
`matomo_give_consent` - class name for element to click when visitors want to **give** consent
`matomo_remove_consent` - class name for element to click when visitors want to **remove** consent
Examples::
# button to allow tracking
<button class="matomo_give_consent">Track me!</button>
# button to remove tracking consent
<button class="matomo_remove_consent">Don't track me anymore!</button>
.. _`asking for consent`: https://developer.matomo.org/guides/tracking-javascript-guide#asking-for-consent
Internal IP addresses
---------------------

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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 %}

179
docs/services/piwik.rst Normal file
View file

@ -0,0 +1,179 @@
==================================
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 %}
</body>
</html>
.. _`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/

View file

@ -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::

View file

@ -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.

View file

@ -2,7 +2,7 @@
Spring Metrics -- conversion tracking
=====================================
`Spring Metrics`_ is a conversions analysis tool. It shows you the top
`Spring Metrics`_ is a convesions 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.
@ -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::

View file

@ -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,12 +57,12 @@ 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::
<script>
<script type="text/javascript">
UserVoice=window.UserVoice||[];(function(){
var uv=document.createElement('script');uv.type='text/javascript';

View file

@ -37,7 +37,7 @@ the tag at the bottom of the HTML head::
</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 Developers Guide`_ on the Woopra website.
@ -94,38 +94,6 @@ a page reporting. So its important to keep the number reasonable in
order to accurately make predictions.
Cookie control
--------------
Optional settings that influence the cookie behavior::
WOOPRA_COOKIE_NAME = "wooTracker"
WOOPRA_COOKIE_DOMAIN = ".example.com"
WOOPRA_COOKIE_PATH = "/"
WOOPRA_COOKIE_EXPIRE = "Fri Jan 01 2027 15:00:00 GMT+0000"
See the `Woopra documentation`_ for more specific details.
Tracking control
----------------
Optional settings that influence the tracking as such::
WOOPRA_CLICK_TRACKING = False
WOOPRA_DOWNLOAD_TRACKING = False
WOOPRA_OUTGOING_TRACKING = False
WOOPRA_OUTGOING_IGNORE_SUBDOMAIN = True
WOOPRA_IGNORE_QUERY_URL = True
WOOPRA_HIDE_CAMPAIGN = False
See the `Woopra documentation`_ for more specific details.
.. _`Woopra documentation`:
https://docs.woopra.com/reference/woopraconfig#configuring-your-tracker
Internal IP addresses
---------------------

View file

@ -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::

View file

@ -1,127 +1,20 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools>=80"]
[tool.bandit]
# Exclude/ignore of files and TOML reading is currently broken in Bandit
exclude = [".cache",".git",".github",".tox","build","dist","docs","tests"]
[project]
name = "django-analytical"
dynamic = ["version"]
description = "Analytics service integration for Django projects"
readme = "README.rst"
license = "MIT"
license-files = ["LICENSE.txt"]
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 = "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"},
{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"},
{name = "Julian Haluska", email = "mail@julianhaluska.de"},
{name = "Ronard Luna", email = "rlunag@proton.me"},
]
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",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"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 :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
]
requires-python = ">=3.9"
dependencies = [
"django>=4.2",
]
[tool.black]
color = true
[project.urls]
Homepage = "https://github.com/jazzband/django-analytical"
Documentation = "https://django-analytical.readthedocs.io/"
[tool.coverage.xml]
output = "tests/coverage-report.xml"
[tool.coverage.report]
show_missing = true
skip_covered = true
[tool.isort]
color_output = true
profile = "black"
[tool.coverage.run]
source = ["analytical"]
[tool.pylint.master]
output-format = "colorized"
[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__"}

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
Django>=2.2.*

13
setup.cfg Normal file
View file

@ -0,0 +1,13 @@
[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

62
setup.py Normal file
View file

@ -0,0 +1,62 @@
"""
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/',
},
)

View file

@ -12,8 +12,7 @@ register = Library()
def _location_node(location):
class DummyNode(Node):
def render(self, context):
return '<!-- dummy_%s -->' % location
return "<!-- dummy_%s -->" % location
return DummyNode
@ -26,7 +25,6 @@ def _location_tag(location):
if len(bits) > 1:
raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0])
return _location_nodes[location]
return dummy_tag

View file

@ -27,7 +27,7 @@ class AnalyticsTagTestCase(TagTestCase):
def render_location_tag(self, location, vars=None):
if vars is None:
vars = {}
t = Template('{%% load analytical %%}{%% analytical_%s %%}' % location)
t = Template("{%% load analytical %%}{%% analytical_%s %%}" % location)
return t.render(Context(vars))
def test_location_tags(self):

View file

@ -22,25 +22,21 @@ class ChartbeatTagTestCaseNoSites(TestCase):
self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r)
@override_settings(
INSTALLED_APPS=(
'analytical',
'django.contrib.sites',
'django.contrib.auth',
'django.contrib.contenttypes',
)
)
@override_settings(INSTALLED_APPS=(
'analytical',
'django.contrib.sites',
'django.contrib.auth',
'django.contrib.contenttypes',
))
@override_settings(CHARTBEAT_USER_ID='12345')
class ChartbeatTagTestCaseWithSites(TestCase):
def setUp(self):
from django.core.management import call_command
call_command('migrate', verbosity=0)
call_command("migrate", verbosity=0)
def test_rendering_setup_site(self):
from django.contrib.sites.models import Site
site = Site.objects.create(domain='test.com', name='test')
site = Site.objects.create(domain="test.com", name="test")
with override_settings(SITE_ID=site.id):
r = ChartbeatBottomNode().render(Context())
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
@ -64,36 +60,24 @@ class ChartbeatTagTestCase(TagTestCase):
"""
def test_top_tag(self):
r = self.render_tag(
'chartbeat', 'chartbeat_top', {'chartbeat_domain': 'test.com'}
)
r = self.render_tag('chartbeat', 'chartbeat_top', {'chartbeat_domain': "test.com"})
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'}
)
r = self.render_tag('chartbeat', 'chartbeat_bottom', {'chartbeat_domain': "test.com"})
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',
}
)
)
r = ChartbeatTopNode().render(Context({
'chartbeat_domain': "test.com",
}))
assert 'var _sf_startpt=(new Date()).getTime()' in r
def test_bottom_node(self):
r = ChartbeatBottomNode().render(
Context(
{
'chartbeat_domain': 'test.com',
}
)
)
r = ChartbeatBottomNode().render(Context({
'chartbeat_domain': "test.com",
}))
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r)

View file

@ -52,17 +52,11 @@ class ClickyTagTestCase(TagTestCase):
assert 'var clicky_custom = {"session": {"username":' not in r
def test_custom(self):
r = ClickyNode().render(
Context(
{
'clicky_var1': 'val1',
'clicky_var2': 'val2',
}
)
)
assert re.search(
r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r
)
r = ClickyNode().render(Context({
'clicky_var1': 'val1',
'clicky_var2': 'val2',
}))
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):

View file

@ -1,7 +1,6 @@
"""
Tests for the Facebook Pixel template tags.
"""
import pytest
from django.http import HttpRequest
from django.template import Context, Template, TemplateSyntaxError
@ -40,6 +39,7 @@ expected_body_html = """\
@override_settings(FACEBOOK_PIXEL_ID='1234567890')
class FacebookPixelTagTestCase(TagTestCase):
maxDiff = None
def test_head_tag(self):
@ -60,15 +60,11 @@ class FacebookPixelTagTestCase(TagTestCase):
def test_tags_take_no_args(self):
template = '{%% load facebook_pixel %%}{%% facebook_pixel_%s "arg" %%}'
with pytest.raises(
TemplateSyntaxError, match="'facebook_pixel_head' takes no arguments"
):
Template(template % 'head').render(Context({}))
with pytest.raises(TemplateSyntaxError, match="'facebook_pixel_head' takes no arguments"):
Template(template % "head").render(Context({}))
with pytest.raises(
TemplateSyntaxError, match="'facebook_pixel_body' takes no arguments"
):
Template(template % 'body').render(Context({}))
with pytest.raises(TemplateSyntaxError, match="'facebook_pixel_body' takes no arguments"):
Template(template % "body").render(Context({}))
@override_settings(FACEBOOK_PIXEL_ID=None)
def test_no_id(self):
@ -80,7 +76,9 @@ class FacebookPixelTagTestCase(TagTestCase):
@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'$"
expected_pattern = (
r"FACEBOOK_PIXEL_ID setting: must be \(a string containing\) a number: 'invalid'$"
)
with pytest.raises(AnalyticalException, match=expected_pattern):
FacebookPixelHeadNode()
with pytest.raises(AnalyticalException, match=expected_pattern):
@ -93,13 +91,11 @@ class FacebookPixelTagTestCase(TagTestCase):
context = Context({'request': request})
def _disabled(html):
return '\n'.join(
[
'<!-- Facebook Pixel disabled on internal IP address',
html,
'-->',
]
)
return '\n'.join([
'<!-- Facebook Pixel disabled on internal IP address',
html,
'-->',
])
head_html = FacebookPixelHeadNode().render(context)
assert _disabled(expected_head_html) == head_html

View file

@ -19,10 +19,8 @@ class GaugesTagTestCase(TagTestCase):
"""
def test_tag(self):
assert (
self.render_tag('gauges', 'gauges')
== """
<script>
assert self.render_tag('gauges', 'gauges') == """
<script type="text/javascript">
var _gauges = _gauges || [];
(function() {
var t = document.createElement('script');
@ -36,13 +34,10 @@ class GaugesTagTestCase(TagTestCase):
})();
</script>
"""
)
def test_node(self):
assert (
GaugesNode().render(Context())
== """
<script>
assert GaugesNode().render(Context()) == """
<script type="text/javascript">
var _gauges = _gauges || [];
(function() {
var t = document.createElement('script');
@ -56,7 +51,6 @@ class GaugesTagTestCase(TagTestCase):
})();
</script>
"""
)
@override_settings(GAUGES_SITE_ID=None)
def test_no_account_number(self):

View file

@ -20,10 +20,8 @@ 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.
@ -49,19 +47,15 @@ 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
@ -69,14 +63,12 @@ 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
@ -91,10 +83,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):
@ -193,12 +185,10 @@ 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()

View file

@ -16,7 +16,7 @@ from analytical.utils import AnalyticalException
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='UA-123456-7')
class GoogleAnalyticsTagTestCase(TagTestCase):
"""
Tests for the ``google_analytics_gtag`` template tag.
Tests for the ``google_analytics_js`` template tag.
"""
def test_tag(self):
@ -25,7 +25,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'UA-123456-7', {});" in r
assert "gtag('config', 'UA-123456-7');" in r
def test_node(self):
r = GoogleAnalyticsGTagNode().render(Context())
@ -33,7 +33,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'UA-123456-7', {});" in r
assert "gtag('config', 'UA-123456-7');" in r
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID=None)
def test_no_property_id(self):
@ -57,41 +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(\'config\', \'UA-123456-7\', {"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',
'user': User(username='test'),
}
)
)
assert (
'gtag(\'config\', \'UA-123456-7\', {"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(\'config\', \'UA-123456-7\', {"user_id": "bar_analytical_identity"});'
in 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):
@ -100,7 +66,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=G-12345678"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'G-12345678', {});" in r
assert "gtag('config', 'G-12345678');" in r
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='AW-1234567890')
def test_tag_with_conversion_id(self):
@ -109,7 +75,7 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=AW-1234567890"></script'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'AW-1234567890', {});" in r
assert "gtag('config', 'AW-1234567890');" in r
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='DC-12345678')
def test_tag_with_advertiser_id(self):
@ -118,47 +84,4 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
'<script async src="https://www.googletagmanager.com/gtag/js?id=DC-12345678"></script>'
) in r
assert "gtag('js', new Date());" in r
assert "gtag('config', 'DC-12345678', {});" in r
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
)
assert "gtag('config', 'DC-12345678');" in r

View file

@ -17,10 +17,8 @@ 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.
@ -28,25 +26,19 @@ 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
@ -60,36 +52,27 @@ 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
@ -99,12 +82,9 @@ 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):
@ -112,7 +92,8 @@ ga('send', 'pageview');"""
req.META['REMOTE_ADDR'] = '1.1.1.1'
context = Context({'request': req})
r = GoogleAnalyticsJsNode().render(context)
assert r.startswith('<!-- Google Analytics disabled on internal IP address')
assert r.startswith(
'<!-- Google Analytics disabled on internal IP address')
assert r.endswith('-->')
@override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True)
@ -150,17 +131,12 @@ ga('send', 'pageview');"""
@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):
@ -191,12 +167,10 @@ ga('send', 'pageview');"""
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()

View file

@ -39,25 +39,17 @@ 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)

View file

@ -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"):

View file

@ -1,7 +1,6 @@
"""
Tests for the Hotjar template tags.
"""
import pytest
from django.http import HttpRequest
from django.template import Context, Template, TemplateSyntaxError
@ -28,6 +27,7 @@ expected_html = """\
@override_settings(HOTJAR_SITE_ID='123456789')
class HotjarTagTestCase(TagTestCase):
maxDiff = None
def test_tag(self):
@ -44,14 +44,13 @@ 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()
@ -62,13 +61,11 @@ class HotjarTagTestCase(TagTestCase):
context = Context({'request': request})
actual_html = HotjarNode().render(context)
disabled_html = '\n'.join(
[
disabled_html = '\n'.join([
'<!-- Hotjar disabled on internal IP address',
expected_html,
'-->',
]
)
])
assert disabled_html == actual_html
def test_contribute_to_analytical(self):

View file

@ -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,9 +23,7 @@ class IntercomTagTestCase(TagTestCase):
def test_tag(self):
rendered_tag = self.render_tag('intercom', 'intercom')
assert rendered_tag.strip().startswith(
'<script id="IntercomSettingsScriptTag">'
)
assert rendered_tag.strip().startswith('<script id="IntercomSettingsScriptTag">')
def test_node(self):
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
@ -33,21 +31,17 @@ class IntercomTagTestCase(TagTestCase):
username='test',
first_name='Firstname',
last_name='Lastname',
email='test@example.com',
email="test@example.com",
date_joined=now,
)
rendered_tag = IntercomNode().render(Context({'user': user}))
# Because the json isn't predictably ordered, we can't just test the whole thing verbatim.
assert (
rendered_tag
== """
assert rendered_tag == """
<script id="IntercomSettingsScriptTag">
window.intercomSettings = {"app_id": "abc123xyz", "created_at": 1397074500, "email": "test@example.com", "name": "Firstname Lastname", "user_id": %(user_id)s};
</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
"""
% {'user_id': user.pk}
) # noqa
""" % {'user_id': user.pk} # noqa
@override_settings(INTERCOM_APP_ID=None)
def test_no_account_number(self):
@ -65,34 +59,32 @@ 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):
@ -100,25 +92,17 @@ 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')
@ -151,14 +135,10 @@ 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',
@ -172,13 +152,9 @@ 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',

View file

@ -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):

View file

@ -21,17 +21,11 @@ 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):
@ -59,37 +53,22 @@ 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'])

View file

@ -1,7 +1,6 @@
"""
Tests for the Lucky Orange template tags.
"""
import pytest
from django.http import HttpRequest
from django.template import Context, Template, TemplateSyntaxError
@ -26,6 +25,7 @@ window.__lo_site_id = 123456;
@override_settings(LUCKYORANGE_SITE_ID='123456')
class LuckyOrangeTagTestCase(TagTestCase):
maxDiff = None
def test_tag(self):
@ -37,23 +37,18 @@ 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()
@ -64,13 +59,11 @@ class LuckyOrangeTagTestCase(TagTestCase):
context = Context({'request': request})
actual_html = LuckyOrangeNode().render(context)
disabled_html = '\n'.join(
[
disabled_html = '\n'.join([
'<!-- Lucky Orange disabled on internal IP address',
expected_html,
'-->',
]
)
])
assert disabled_html == actual_html
def test_contribute_to_analytical(self):

View file

@ -23,27 +23,28 @@ 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/matomo.php?idsite=345"' in r
assert 'img src="//example.com/piwik.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/matomo.php?idsite=345"' in r
assert 'img src="//example.com/piwik.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
@ -103,54 +104,46 @@ 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
@ -159,8 +152,3 @@ class MatomoTagTestCase(TagTestCase):
def test_disable_cookies(self):
r = MatomoNode().render(Context({}))
assert "_paq.push(['disableCookies']);" in r
@override_settings(MATOMO_ASK_FOR_CONSENT=True)
def test_ask_for_consent(self):
r = MatomoNode().render(Context({}))
self.assertTrue("_paq.push(['requireConsent']);" in r, r)

View file

@ -50,19 +50,12 @@ 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

View file

@ -38,17 +38,10 @@ 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):
@ -65,41 +58,37 @@ 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

View file

@ -0,0 +1,154 @@
"""
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('<!-- Piwik disabled on internal IP address')
assert r.endswith('-->')
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

View file

@ -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):

View file

@ -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,15 +38,11 @@ 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):
@ -59,220 +55,142 @@ 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())
@ -287,55 +205,35 @@ 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):

View file

@ -39,13 +39,9 @@ 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)
@ -54,14 +50,10 @@ 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

View file

@ -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):

View file

@ -2,8 +2,6 @@
Tests for the Woopra template tags and filters.
"""
from datetime import datetime
import pytest
from django.contrib.auth.models import AnonymousUser, User
from django.http import HttpRequest
@ -42,124 +40,23 @@ 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
@override_settings(WOOPRA_COOKIE_NAME='foo')
def test_cookie_name(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"cookie_name": "foo", "domain": "example.com"};'
) in r
@override_settings(WOOPRA_COOKIE_DOMAIN='.example.com')
def test_cookie_domain(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"cookie_domain": ".example.com",'
' "domain": "example.com"};'
) in r
@override_settings(WOOPRA_COOKIE_PATH='/foo/cookie/path')
def test_cookie_path(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"cookie_path": "/foo/cookie/path",'
' "domain": "example.com"};'
) in r
@override_settings(WOOPRA_COOKIE_EXPIRE='Fri Jan 01 2027 15:00:00 GMT+0000')
def test_cookie_expire(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"cookie_expire":'
' "Fri Jan 01 2027 15:00:00 GMT+0000", "domain": "example.com"};'
) in r
@override_settings(WOOPRA_CLICK_TRACKING=True)
def test_click_tracking(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"click_tracking": true, "domain": "example.com"};'
) in r
@override_settings(WOOPRA_DOWNLOAD_TRACKING=True)
def test_download_tracking(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"domain": "example.com", "download_tracking": true};'
) in r
@override_settings(WOOPRA_OUTGOING_TRACKING=True)
def test_outgoing_tracking(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"domain": "example.com", "outgoing_tracking": true};'
) in r
@override_settings(WOOPRA_OUTGOING_IGNORE_SUBDOMAIN=False)
def test_outgoing_ignore_subdomain(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"domain": "example.com",'
' "outgoing_ignore_subdomain": false};'
) in r
@override_settings(WOOPRA_IGNORE_QUERY_URL=False)
def test_ignore_query_url(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"domain": "example.com", "ignore_query_url": false};'
) in r
@override_settings(WOOPRA_HIDE_CAMPAIGN=True)
def test_hide_campaign(self):
r = WoopraNode().render(Context({}))
assert (
'var woo_settings = {"domain": "example.com", "hide_campaign": true};'
) in r
@override_settings(WOOPRA_IDLE_TIMEOUT='1234')
def test_invalid_int_setting(self):
with pytest.raises(AnalyticalException, match=r'must be an int'):
WoopraNode().render(Context({}))
@override_settings(WOOPRA_HIDE_CAMPAIGN='tomorrow')
def test_invalid_bool_setting(self):
with pytest.raises(AnalyticalException, match=r'must be a boolean'):
WoopraNode().render(Context({}))
@override_settings(WOOPRA_COOKIE_EXPIRE=datetime.now())
def test_invalid_str_setting(self):
with pytest.raises(AnalyticalException, match=r'must be a string'):
WoopraNode().render(Context({}))
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
@ -170,26 +67,18 @@ 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)

View file

@ -2,6 +2,7 @@
Tests for the Yandex.Metrica template tags and filters.
"""
import pytest
from django.http import HttpRequest
from django.template import Context
@ -20,11 +21,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):

Some files were not shown because too many files have changed in this diff Show more