pax_global_header 0000666 0000000 0000000 00000000064 13763253701 0014521 g ustar 00root root 0000000 0000000 52 comment=896a19196f0ecca146e4cb8fb3b26e71e15642c8
django-analytical-3.0.0/ 0000775 0000000 0000000 00000000000 13763253701 0015102 5 ustar 00root root 0000000 0000000 django-analytical-3.0.0/.bandit 0000777 0000000 0000000 00000000000 13763253701 0017652 2tox.ini ustar 00root root 0000000 0000000 django-analytical-3.0.0/.coveragerc 0000664 0000000 0000000 00000000032 13763253701 0017216 0 ustar 00root root 0000000 0000000 [run]
source = analytical
django-analytical-3.0.0/.github/ 0000775 0000000 0000000 00000000000 13763253701 0016442 5 ustar 00root root 0000000 0000000 django-analytical-3.0.0/.github/workflows/ 0000775 0000000 0000000 00000000000 13763253701 0020477 5 ustar 00root root 0000000 0000000 django-analytical-3.0.0/.github/workflows/release.yml 0000664 0000000 0000000 00000002510 13763253701 0022640 0 ustar 00root root 0000000 0000000 name: Release
on:
push:
tags:
- '*'
jobs:
build:
if: github.repository == 'jazzband/django-analytical'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: release-${{ hashFiles('**/setup.py') }}
restore-keys: |
release-
- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -U setuptools twine wheel
- name: Build package
run: |
python setup.py --version
python setup.py sdist --format=gztar bdist_wheel
twine check dist/*
- name: Upload packages to Jazzband
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository_url: https://jazzband.co/projects/django-analytical/upload
django-analytical-3.0.0/.github/workflows/test.yml 0000664 0000000 0000000 00000002110 13763253701 0022173 0 ustar 00root root 0000000 0000000 name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 5
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ matrix.python-version }}-v1-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox tox-gh-actions
- name: Tox tests
run: |
tox -v
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
name: Python ${{ matrix.python-version }}
django-analytical-3.0.0/.gitignore 0000664 0000000 0000000 00000000222 13763253701 0017066 0 ustar 00root root 0000000 0000000 .*.sw?
/*.geany
/.idea
/.tox
/.coverage
/coverage.xml
/build
/dist
/docs/_build
/MANIFEST
/docs/_templates/layout.html
*.pyc
*.pyo
*.egg-info
django-analytical-3.0.0/AUTHORS.rst 0000664 0000000 0000000 00000004656 13763253701 0016774 0 ustar 00root root 0000000 0000000 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
django-analytical-3.0.0/CHANGELOG.rst 0000664 0000000 0000000 00000013221 13763253701 0017122 0 ustar 00root root 0000000 0000000 Version 3.0.0
-------------
* Add support for Lucky Orange (Peter Bittner)
* Add missing instructions in Installation chapter of the docs (Peter Bittner)
* Migrate test setup to Pytest (David Smith, Peter Bittner, Pi Delport)
* Support Django 3.1 and Python 3.9, drop Django 1.11 and Python 2.7/3.5 (David Smith)
* Migrate from Travis CI to GitHub Actions (Jannis Leidel)
* Update accepted patterns (regex) for Google Analytics GTag (Taha Rushain)
* Scope Piwik warning to use of Piwik (Hugo Barrera)
* Add ``user_id`` to Google Analytics GTag (Sean Wallace)
Version 2.6.0
-------------
* Support Django 3.0 and Python 3.8, drop Django 2.1
* Add support for Google Analytics Tag Manager (Marc Bourqui)
* Add Matomo, the renamed version of Piwik (Scott Karlin)
* Move Joost's project over to the Jazzband
Version 2.5.0
-------------
* Add support for Google analytics.js (Marc Bourqui)
* Add support for Intercom HMAC identity verification (Pi Delport)
* Add support for Hotjar (Pi Delport)
* Make sure _trackPageview happens before other settings in Google Analytics
(Diederik van der Boor)
Version 2.4.0
-------------
* Support Django 2.0 (Matthäus G. Chajdas)
Version 2.3.0
-------------
* Add Facebook Pixel support (Pi Delport)
* Add Python 3.6 and Django 1.10 & 1.11 tests (Pi Delport)
* Drop Python 3.2 support
Version 2.2.2
-------------
* Allow port in Piwik domain path. (Alex Ramsay)
Version 2.2.1
-------------
* Fix a bug with the extra Google Analytics variables also pushing the `_gat.`
flag onto the configuration array.
Version 2.2.0
-------------
* Update Woopra JavaScript snippet (Aleck Landgraf)
Version 2.1.0
-------------
* Support Rating\@mail.ru (Nikolay Korotkiy)
* Support Yandex.Metrica (Nikolay Korotkiy)
* Add support for extra Google Analytics variables (Steve Schwarz)
* Remove support for Reinvigorate (service shut down)
Version 2.0.0
-------------
* Support Django 1.9, drop support for Django < 1.7 (Hugo Osvaldo Barrera)
* Support custom user models with an alternative username field (Brad Pitcher)
Version 1.0.0
-------------
* Add Piwik user variables support (Alexandre Pocquet)
Version 0.22.0
--------------
* Mark package as Python 3 compatible (Martín Gaitán)
* Fix Clickmap tracker id regular expression
* Test with Django 1.8
Version 0.21.0
--------------
* Added compatibility with Python 3 (Eric Amador)
Version 0.20.0
--------------
* Support Django 1.7 (Craig Bruce)
* Update Mixpanel identity code (Martín Gaitán)
* Identify authenticated users in Uservoice (Martín Gaitán)
* Add full name and email to Olark (Scott Adams)
Version 0.19.0
--------------
* Add Piwik integration (Peter Bittner)
Version 0.18.0
--------------
* Update HubSpot code (Craig Bruce)
Version 0.17.1
--------------
* Fix typo in Intercom.io support (Steven Skoczen)
Version 0.17.0
--------------
* Update UserVoice support (Martín Gaitán)
* Add support for Intercom.io (Steven Skoczen)
Version 0.16.0
--------------
* Add support for GA Display Advertising features (Max Arnold)
Version 0.15.0
--------------
* Add IP anonymization setting to GA tracking pixel (Tinnet Coronam)
* Include Django 1.5 in tox.ini (Tinnet Coronam)
* Add Clickmap integration (Philippe O. Wagner)
Version 0.14.0
--------------
* Update mixpanel integration to latest code (Simon Ye)
Version 0.13.0
--------------
* Add support for the KISSmetrics alias feature (Sandra Mau)
* Update testing code for Django 1.4 (Pi Delport)
Version 0.12.0
--------------
* Add support for the UserVoice service.
Version 0.11.3
--------------
* Added support for Gaug.es (Steven Skoczen)
Version 0.11.2
--------------
* Fix Spring Metrics custom variables.
* Update Spring Metrics documentation.
Version 0.11.1
--------------
* Fix Woopra for anonymous users (Steven Skoczen).
Version 0.11.0
--------------
* Added support for the Spring Metrics service.
* Allow sending events and properties to KISSmetrics (Paul Oswald).
* Add support for the Site Speed report in Google Analytics (Uros
Trebec).
Version 0.10.0
--------------
* Added multiple domains support for Google Analytics.
* Fixed bug in deleted settings testing code (Eric Davis).
Version 0.9.2
-------------
* Added support for the SnapEngage service.
* Updated Mixpanel code (Julien Grenier).
Version 0.9.1
-------------
* Fixed compatibility with Python 2.5 (Iván Raskovsky).
Version 0.9.0
-------------
* Updated Clicky tracking code to support multiple site ids.
* Fixed Chartbeat auto-domain bug when the Sites framework is not used
(Eric Davis).
* Improved testing code (Eric Davis).
Version 0.8.1
-------------
* Fixed MANIFEST bug that caused GoSquared support to be missing from
the source distribution.
Version 0.8.0
-------------
* Added support for the GoSquared service.
* Updated Clicky tracking code to use relative URLs.
Version 0.7.0
-------------
* Added support for the Woopra service.
* Added chat window text customization to Olark.
* Renamed ``MIXPANEL_TOKEN`` setting to ``MIXPANEL_API_TOKEN`` for
compatibility with Wes Winham's mixpanel-celery_ package.
* Fixed the ``"""
SETUP_CODE = """
""" # noqa
DOMAIN_CONTEXT_KEY = 'chartbeat_domain'
register = Library()
@register.tag
def chartbeat_top(parser, token):
"""
Top Chartbeat template tag.
Render the top Javascript code for Chartbeat.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return ChartbeatTopNode()
class ChartbeatTopNode(Node):
def render(self, context):
if is_internal_ip(context):
return disable_html(INIT_CODE, "Chartbeat")
return INIT_CODE
@register.tag
def chartbeat_bottom(parser, token):
"""
Bottom Chartbeat template tag.
Render the bottom Javascript code for Chartbeat. You must supply
your Chartbeat User ID (as a string) in the ``CHARTBEAT_USER_ID``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return ChartbeatBottomNode()
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")
def render(self, context):
config = {'uid': self.user_id}
domain = _get_domain(context)
if domain is not None:
config['domain'] = domain
html = SETUP_CODE % {'config': json.dumps(config, sort_keys=True)}
if is_internal_ip(context, 'CHARTBEAT'):
html = disable_html(html, 'Chartbeat')
return html
def contribute_to_analytical(add_node):
ChartbeatBottomNode() # ensure properly configured
add_node('head_top', ChartbeatTopNode, 'first')
add_node('body_bottom', ChartbeatBottomNode, 'last')
def _get_domain(context):
domain = context.get(DOMAIN_CONTEXT_KEY)
if domain is not None:
return domain
else:
if 'django.contrib.sites' not in settings.INSTALLED_APPS:
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
return
django-analytical-3.0.0/analytical/templatetags/clickmap.py 0000664 0000000 0000000 00000003643 13763253701 0024060 0 ustar 00root root 0000000 0000000 """
Clickmap template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
CLICKMAP_TRACKER_ID_RE = re.compile(r'^\w+$')
TRACKING_CODE = """
"""
register = Library()
@register.tag
def clickmap(parser, token):
"""
Clickmap tracker template tag.
Renders Javascript code to track page visits. You must supply
your clickmap tracker ID (as a string) in the ``CLICKMAP_TRACKER_ID``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return ClickmapNode()
class ClickmapNode(Node):
def __init__(self):
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}
if is_internal_ip(context, 'CLICKMAP'):
html = disable_html(html, 'Clickmap')
return html
def contribute_to_analytical(add_node):
ClickmapNode() # ensure properly configured
add_node('body_bottom', ClickmapNode)
django-analytical-3.0.0/analytical/templatetags/clicky.py 0000664 0000000 0000000 00000004507 13763253701 0023553 0 ustar 00root root 0000000 0000000 """
Clicky template tags and filters.
"""
import json
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_identity, is_internal_ip, disable_html, \
get_required_setting
SITE_ID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
""" # noqa
register = Library()
@register.tag
def clicky(parser, token):
"""
Clicky tracking template tag.
Renders Javascript code to track page visits. You must supply
your Clicky Site ID (as a string) in the ``CLICKY_SITE_ID``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return ClickyNode()
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")
def render(self, context):
custom = {}
for dict_ in context:
for var, val in dict_.items():
if var.startswith('clicky_'):
custom[var[7:]] = val
if 'username' not in custom.get('session', {}):
identity = get_identity(context, 'clicky')
if identity is not None:
custom.setdefault('session', {})['username'] = identity
html = TRACKING_CODE % {
'site_id': self.site_id,
'custom': json.dumps(custom, sort_keys=True),
}
if is_internal_ip(context, 'CLICKY'):
html = disable_html(html, 'Clicky')
return html
def contribute_to_analytical(add_node):
ClickyNode() # ensure properly configured
add_node('body_bottom', ClickyNode)
django-analytical-3.0.0/analytical/templatetags/crazy_egg.py 0000664 0000000 0000000 00000003777 13763253701 0024257 0 ustar 00root root 0000000 0000000 """
Crazy Egg template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
SETUP_CODE = ''.\
format(placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/'
'%(account_nr_1)s/%(account_nr_2)s.js')
USERVAR_CODE = "CE2.set(%(varnr)d, '%(value)s');"
register = Library()
@register.tag
def crazy_egg(parser, token):
"""
Crazy Egg tracking template tag.
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.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return CrazyEggNode()
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"
)
def render(self, context):
html = SETUP_CODE % {
'account_nr_1': self.account_nr[:4],
'account_nr_2': self.account_nr[4:],
}
values = (context.get('crazy_egg_var%d' % i) for i in range(1, 6))
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
if params:
js = " ".join(USERVAR_CODE % {
'varnr': varnr,
'value': value,
} for (varnr, value) in params)
html = '%s\n' \
'' % (html, js)
if is_internal_ip(context, 'CRAZY_EGG'):
html = disable_html(html, 'Crazy Egg')
return html
def contribute_to_analytical(add_node):
CrazyEggNode() # ensure properly configured
add_node('body_bottom', CrazyEggNode)
django-analytical-3.0.0/analytical/templatetags/facebook_pixel.py 0000664 0000000 0000000 00000004744 13763253701 0025252 0 ustar 00root root 0000000 0000000 """
Facebook Pixel template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_required_setting, is_internal_ip, disable_html
FACEBOOK_PIXEL_HEAD_CODE = """\
"""
FACEBOOK_PIXEL_BODY_CODE = """\
"""
register = Library()
def _validate_no_args(token):
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
@register.tag
def facebook_pixel_head(parser, token):
"""
Facebook Pixel head template tag.
"""
_validate_no_args(token)
return FacebookPixelHeadNode()
@register.tag
def facebook_pixel_body(parser, token):
"""
Facebook Pixel body template tag.
"""
_validate_no_args(token)
return FacebookPixelBodyNode()
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",
)
def render(self, context):
html = self.code_template % {'FACEBOOK_PIXEL_ID': self.pixel_id}
if is_internal_ip(context, 'FACEBOOK_PIXEL'):
return disable_html(html, 'Facebook Pixel')
else:
return html
@property
def code_template(self):
raise NotImplementedError # pragma: no cover
class FacebookPixelHeadNode(_FacebookPixelNode):
code_template = FACEBOOK_PIXEL_HEAD_CODE
class FacebookPixelBodyNode(_FacebookPixelNode):
code_template = FACEBOOK_PIXEL_BODY_CODE
def contribute_to_analytical(add_node):
# ensure properly configured
FacebookPixelHeadNode()
FacebookPixelBodyNode()
add_node('head_bottom', FacebookPixelHeadNode)
add_node('body_bottom', FacebookPixelBodyNode)
django-analytical-3.0.0/analytical/templatetags/gauges.py 0000664 0000000 0000000 00000003132 13763253701 0023541 0 ustar 00root root 0000000 0000000 """
Gaug.es template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
SITE_ID_RE = re.compile(r'[\da-f]+$')
TRACKING_CODE = """
"""
register = Library()
@register.tag
def gauges(parser, token):
"""
Gaug.es template tag.
Renders Javascript code to gaug.es testing. You must supply
your Site ID account number in the ``GAUGES_SITE_ID``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return GaugesNode()
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'")
def render(self, context):
html = TRACKING_CODE % {'site_id': self.site_id}
if is_internal_ip(context, 'GAUGES'):
html = disable_html(html, 'Gauges')
return html
def contribute_to_analytical(add_node):
GaugesNode()
add_node('head_bottom', GaugesNode)
django-analytical-3.0.0/analytical/templatetags/google_analytics.py 0000664 0000000 0000000 00000015502 13763253701 0025615 0 ustar 00root root 0000000 0000000 """
Google Analytics template tags and filters.
DEPRECATED
"""
import decimal
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (
AnalyticalException,
disable_html,
get_domain,
get_required_setting,
is_internal_ip,
)
TRACK_SINGLE_DOMAIN = 1
TRACK_MULTIPLE_SUBDOMAINS = 2
TRACK_MULTIPLE_DOMAINS = 3
SCOPE_VISITOR = 1
SCOPE_SESSION = 2
SCOPE_PAGE = 3
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
SETUP_CODE = """
"""
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]);"
SITE_SPEED_CODE = "_gaq.push(['_trackPageLoadTime']);"
ANONYMIZE_IP_CODE = "_gaq.push(['_gat._anonymizeIp']);"
SAMPLE_RATE_CODE = "_gaq.push(['_setSampleRate', '%s']);"
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'")
ZEROPLACES = decimal.Decimal('0')
TWOPLACES = decimal.Decimal('0.01')
register = Library()
@register.tag
def google_analytics(parser, token):
"""
Google Analytics tracking template tag.
Renders Javascript code to track page visits. You must supply
your website property ID (as a string) in the
``GOOGLE_ANALYTICS_PROPERTY_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return GoogleAnalyticsNode()
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'")
def render(self, context):
commands = self._get_domain_commands(context)
commands.extend(self._get_custom_var_commands(context))
commands.extend(self._get_other_commands(context))
commands.append(TRACK_PAGE_VIEW)
if getattr(settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False):
source = DISPLAY_ADVERTISING_SOURCE
else:
source = DEFAULT_SOURCE
html = SETUP_CODE % {
'property_id': self.property_id,
'commands': " ".join(commands),
'source_scheme': source[0],
'source_url': source[1],
}
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')
return html
def _get_domain_commands(self, context):
commands = []
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")
commands.append(DOMAIN_CODE % domain)
commands.append(NO_ALLOW_HASH_CODE)
if tracking_type == TRACK_MULTIPLE_DOMAINS:
commands.append(ALLOW_LINKER_CODE)
return commands
def _get_custom_var_commands(self, context):
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:
name = var[0]
value = var[1]
try:
scope = var[2]
except IndexError:
scope = SCOPE_PAGE
commands.append(CUSTOM_VAR_CODE % {
'index': index,
'name': name,
'value': value,
'scope': scope,
})
return commands
def _get_other_commands(self, context):
commands = []
if getattr(settings, 'GOOGLE_ANALYTICS_SITE_SPEED', False):
commands.append(SITE_SPEED_CODE)
if getattr(settings, 'GOOGLE_ANALYTICS_ANONYMIZE_IP', False):
commands.append(ANONYMIZE_IP_CODE)
sampleRate = getattr(settings, 'GOOGLE_ANALYTICS_SAMPLE_RATE', False)
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")
commands.append(SAMPLE_RATE_CODE % value.quantize(TWOPLACES))
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")
commands.append(SITE_SPEED_SAMPLE_RATE_CODE % value.quantize(TWOPLACES))
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")
commands.append(SESSION_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES))
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")
commands.append(VISITOR_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES))
return commands
def contribute_to_analytical(add_node):
GoogleAnalyticsNode() # ensure properly configured
add_node('head_bottom', GoogleAnalyticsNode)
django-analytical-3.0.0/analytical/templatetags/google_analytics_gtag.py 0000664 0000000 0000000 00000004312 13763253701 0026614 0 ustar 00root root 0000000 0000000 """
Google Analytics template tags and filters, using the new analytics.js library.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (
disable_html,
get_identity,
get_required_setting,
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]+$')
SETUP_CODE = """
"""
GTAG_SET_CODE = """gtag('set', {{'{key}': '{value}'}});"""
register = Library()
@register.tag
def google_analytics_gtag(parser, token):
"""
Google Analytics tracking template tag.
Renders Javascript code to track page visits. You must supply
your website property ID (as a string) in the
``GOOGLE_ANALYTICS_GTAG_PROPERTY_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return GoogleAnalyticsGTagNode()
class GoogleAnalyticsGTagNode(Node):
def __init__(self):
self.property_id = get_required_setting(
'GOOGLE_ANALYTICS_GTAG_PROPERTY_ID', PROPERTY_ID_RE,
'''must be a string looking like one of these patterns
('UA-XXXXXX-Y' , 'AW-XXXXXXXXXX',
'G-XXXXXXXX', 'DC-XXXXXXXX')''')
def render(self, context):
other_fields = {}
identity = get_identity(context)
if identity is not None:
other_fields['user_id'] = identity
extra = '\n'.join([
GTAG_SET_CODE.format(key=key, value=value) for key, value in other_fields.items()
])
html = SETUP_CODE.format(
property_id=self.property_id,
extra=extra,
)
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')
return html
def contribute_to_analytical(add_node):
GoogleAnalyticsGTagNode() # ensure properly configured
add_node('head_top', GoogleAnalyticsGTagNode)
django-analytical-3.0.0/analytical/templatetags/google_analytics_js.py 0000664 0000000 0000000 00000012546 13763253701 0026316 0 ustar 00root root 0000000 0000000 """
Google Analytics template tags and filters, using the new analytics.js library.
"""
import decimal
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (
AnalyticalException,
disable_html,
get_domain,
get_required_setting,
is_internal_ip,
)
TRACK_SINGLE_DOMAIN = 1
TRACK_MULTIPLE_SUBDOMAINS = 2
TRACK_MULTIPLE_DOMAINS = 3
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
SETUP_CODE = """
"""
REQUIRE_DISPLAY_FEATURES = "ga('require', 'displayfeatures');\n"
CUSTOM_VAR_CODE = "ga('set', '{name}', {value});\n"
ANONYMIZE_IP_CODE = "ga('set', 'anonymizeIp', true);\n"
register = Library()
@register.tag
def google_analytics_js(parser, token):
"""
Google Analytics tracking template tag.
Renders Javascript code to track page visits. You must supply
your website property ID (as a string) in the
``GOOGLE_ANALYTICS_JS_PROPERTY_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return GoogleAnalyticsJsNode()
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'")
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)
if display_features:
commands.insert(0, REQUIRE_DISPLAY_FEATURES)
html = SETUP_CODE.format(
property_id=self.property_id,
create_fields=json.dumps(create_fields),
commands="".join(commands),
)
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
html = disable_html(html, 'Google Analytics')
return html
def _get_domain_fields(self, context):
domain_fields = {}
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")
domain_fields['legacyCookieDomain'] = domain
if tracking_type == TRACK_MULTIPLE_DOMAINS:
domain_fields['allowLinker'] = True
return domain_fields
def _get_other_create_fields(self, context):
other_fields = {}
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")
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")
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")
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)
)
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
commands = []
for _, var in params:
name = var[0]
value = var[1]
try:
float(value)
except ValueError:
value = f"'{value}'"
commands.append(CUSTOM_VAR_CODE.format(
name=name,
value=value,
))
return commands
def _get_other_commands(self, context):
commands = []
if getattr(settings, 'GOOGLE_ANALYTICS_ANONYMIZE_IP', False):
commands.append(ANONYMIZE_IP_CODE)
return commands
def contribute_to_analytical(add_node):
GoogleAnalyticsJsNode() # ensure properly configured
add_node('head_bottom', GoogleAnalyticsJsNode)
django-analytical-3.0.0/analytical/templatetags/gosquared.py 0000664 0000000 0000000 00000004360 13763253701 0024264 0 ustar 00root root 0000000 0000000 """
GoSquared template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_identity, \
is_internal_ip, disable_html, get_required_setting
TOKEN_RE = re.compile(r'^\S+-\S+-\S+$')
TRACKING_CODE = """
""" # noqa
TOKEN_CODE = 'GoSquared.acct = "%s";'
IDENTIFY_CODE = 'GoSquared.UserName = "%s";'
register = Library()
@register.tag
def gosquared(parser, token):
"""
GoSquared tracking template tag.
Renders Javascript code to track page visits. You must supply
your GoSquared site token in the ``GOSQUARED_SITE_TOKEN`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return GoSquaredNode()
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")
def render(self, context):
configs = [TOKEN_CODE % self.site_token]
identity = get_identity(context, 'gosquared', self._identify)
if identity:
configs.append(IDENTIFY_CODE % identity)
html = TRACKING_CODE % {
'token': self.site_token,
'config': ' '.join(configs),
}
if is_internal_ip(context, 'GOSQUARED'):
html = disable_html(html, 'GoSquared')
return html
def _identify(self, user):
name = user.get_full_name()
if not name:
name = user.username
return name
def contribute_to_analytical(add_node):
GoSquaredNode() # ensure properly configured
add_node('body_bottom', GoSquaredNode)
django-analytical-3.0.0/analytical/templatetags/hotjar.py 0000664 0000000 0000000 00000002771 13763253701 0023565 0 ustar 00root root 0000000 0000000 """
Hotjar template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_required_setting, is_internal_ip, disable_html
HOTJAR_TRACKING_CODE = """\
"""
register = Library()
def _validate_no_args(token):
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
@register.tag
def hotjar(parser, token):
"""
Hotjar template tag.
"""
_validate_no_args(token)
return HotjarNode()
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",
)
def render(self, context):
html = HOTJAR_TRACKING_CODE % {'HOTJAR_SITE_ID': self.site_id}
if is_internal_ip(context, 'HOTJAR'):
return disable_html(html, 'Hotjar')
else:
return html
def contribute_to_analytical(add_node):
# ensure properly configured
HotjarNode()
add_node('head_bottom', HotjarNode)
django-analytical-3.0.0/analytical/templatetags/hubspot.py 0000664 0000000 0000000 00000003306 13763253701 0023755 0 ustar 00root root 0000000 0000000 """
HubSpot template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
PORTAL_ID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
""" # noqa
register = Library()
@register.tag
def hubspot(parser, token):
"""
HubSpot tracking template tag.
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()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return HubSpotNode()
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")
def render(self, context):
html = TRACKING_CODE % {'portal_id': self.portal_id}
if is_internal_ip(context, 'HUBSPOT'):
html = disable_html(html, 'HubSpot')
return html
def contribute_to_analytical(add_node):
HubSpotNode() # ensure properly configured
add_node('body_bottom', HubSpotNode)
django-analytical-3.0.0/analytical/templatetags/intercom.py 0000664 0000000 0000000 00000010474 13763253701 0024115 0 ustar 00root root 0000000 0000000 """
intercom.io template tags and filters.
"""
import hashlib
import hmac
import json
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import disable_html, get_required_setting, \
is_internal_ip, get_user_from_context, get_identity, \
get_user_is_authenticated
APP_ID_RE = re.compile(r'[\da-z]+$')
TRACKING_CODE = """
""" # noqa
register = Library()
def _hashable_bytes(data):
"""
Coerce strings to hashable bytes.
"""
if isinstance(data, bytes):
return data
elif isinstance(data, str):
return data.encode('ascii') # Fail on anything non-ASCII.
else:
raise TypeError(data)
def intercom_user_hash(data):
"""
Return a SHA-256 HMAC `user_hash` as expected by Intercom, if configured.
Return None if the `INTERCOM_HMAC_SECRET_KEY` setting is not configured.
"""
if getattr(settings, 'INTERCOM_HMAC_SECRET_KEY', None):
return hmac.new(
key=_hashable_bytes(settings.INTERCOM_HMAC_SECRET_KEY),
msg=_hashable_bytes(data),
digestmod=hashlib.sha256,
).hexdigest()
else:
return None
@register.tag
def intercom(parser, token):
"""
Intercom.io template tag.
Renders Javascript code to intercom.io testing. You must supply
your APP ID account number in the ``INTERCOM_APP_ID``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return IntercomNode()
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'")
def _identify(self, user):
name = user.get_full_name()
if not name:
name = user.username
return name
def _get_custom_attrs(self, context):
params = {}
for dict_ in context:
for var, val in dict_.items():
if var.startswith('intercom_'):
params[var[9:]] = val
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)
if 'email' not in params and user.email:
params['email'] = user.email
params.setdefault('user_id', user.pk)
params['created_at'] = int(user.date_joined.timestamp())
else:
params['created_at'] = None
# Generate a user_hash HMAC to verify the user's identity, if configured.
# (If both user_id and email are present, the user_id field takes precedence.)
# See:
# https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
user_hash_data = params.get('user_id', params.get('email'))
if user_hash_data:
user_hash = intercom_user_hash(str(user_hash_data))
if user_hash is not None:
params.setdefault('user_hash', user_hash)
return params
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)
}
if is_internal_ip(context, 'INTERCOM'):
html = disable_html(html, 'Intercom')
return html
def contribute_to_analytical(add_node):
IntercomNode()
add_node('body_bottom', IntercomNode)
django-analytical-3.0.0/analytical/templatetags/kiss_insights.py 0000664 0000000 0000000 00000004201 13763253701 0025145 0 ustar 00root root 0000000 0000000 """
KISSinsights template tags.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
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 = """
""" # noqa
IDENTIFY_CODE = "_kiq.push(['identify', '%s']);"
SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);"
SHOW_SURVEY_CONTEXT_KEY = 'kiss_insights_show_survey'
register = Library()
@register.tag
def kiss_insights(parser, token):
"""
KISSinsights set-up template tag.
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.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return KissInsightsNode()
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")
self.site_code = get_required_setting(
'KISS_INSIGHTS_SITE_CODE', SITE_CODE_RE,
"must be a string containing three characters")
def render(self, context):
commands = []
identity = get_identity(context, 'kiss_insights')
if identity is not None:
commands.append(IDENTIFY_CODE % identity)
try:
commands.append(SHOW_SURVEY_CODE % context[SHOW_SURVEY_CONTEXT_KEY])
except KeyError:
pass
html = SETUP_CODE % {
'account_number': self.account_number,
'site_code': self.site_code,
'commands': " ".join(commands),
}
return html
def contribute_to_analytical(add_node):
KissInsightsNode() # ensure properly configured
add_node('body_top', KissInsightsNode)
django-analytical-3.0.0/analytical/templatetags/kiss_metrics.py 0000664 0000000 0000000 00000006054 13763253701 0024773 0 ustar 00root root 0000000 0000000 """
KISSmetrics template tags.
"""
import json
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_identity, \
get_required_setting
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
TRACKING_CODE = """
"""
IDENTIFY_CODE = "_kmq.push(['identify', '%s']);"
EVENT_CODE = "_kmq.push(['record', '%(name)s', %(properties)s]);"
PROPERTY_CODE = "_kmq.push(['set', %(properties)s]);"
ALIAS_CODE = "_kmq.push(['alias', '%s', '%s']);"
EVENT_CONTEXT_KEY = 'kiss_metrics_event'
PROPERTY_CONTEXT_KEY = 'kiss_metrics_properties'
ALIAS_CONTEXT_KEY = 'kiss_metrics_alias'
register = Library()
@register.tag
def kiss_metrics(parser, token):
"""
KISSinsights tracking template tag.
Renders Javascript code to track page visits. You must supply
your KISSmetrics API key in the ``KISS_METRICS_API_KEY``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return KissMetricsNode()
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")
def render(self, context):
commands = []
identity = get_identity(context, 'kiss_metrics')
if identity is not None:
commands.append(IDENTIFY_CODE % identity)
try:
properties = context[ALIAS_CONTEXT_KEY]
key, value = properties.popitem()
commands.append(ALIAS_CODE % (key, value))
except KeyError:
pass
try:
name, properties = context[EVENT_CONTEXT_KEY]
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),
})
except KeyError:
pass
html = TRACKING_CODE % {
'api_key': self.api_key,
'commands': " ".join(commands),
}
if is_internal_ip(context, 'KISS_METRICS'):
html = disable_html(html, 'KISSmetrics')
return html
def contribute_to_analytical(add_node):
KissMetricsNode() # ensure properly configured
add_node('head_top', KissMetricsNode)
django-analytical-3.0.0/analytical/templatetags/luckyorange.py 0000664 0000000 0000000 00000003031 13763253701 0024607 0 ustar 00root root 0000000 0000000 """
Lucky Orange template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_required_setting, is_internal_ip, disable_html
LUCKYORANGE_TRACKING_CODE = """\
"""
register = Library()
def _validate_no_args(token):
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
@register.tag
def luckyorange(parser, token):
"""
Lucky Orange template tag.
"""
_validate_no_args(token)
return LuckyOrangeNode()
class LuckyOrangeNode(Node):
def __init__(self):
self.site_id = get_required_setting(
'LUCKYORANGE_SITE_ID',
re.compile(r'^\d+$'),
"must be (a string containing) a number",
)
def render(self, context):
html = LUCKYORANGE_TRACKING_CODE % {'LUCKYORANGE_SITE_ID': self.site_id}
if is_internal_ip(context, 'LUCKYORANGE'):
return disable_html(html, 'Lucky Orange')
else:
return html
def contribute_to_analytical(add_node):
# ensure properly configured
LuckyOrangeNode()
add_node('head_bottom', LuckyOrangeNode)
django-analytical-3.0.0/analytical/templatetags/matomo.py 0000664 0000000 0000000 00000007666 13763253701 0023602 0 ustar 00root root 0000000 0000000 """
Matomo template tags and filters.
"""
from collections import namedtuple
from itertools import chain
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (is_internal_ip, disable_html,
get_required_setting, get_identity)
# domain name (characters separated by a dot), optional port, optional URI path, no slash
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$')
# numeric ID
SITEID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
""" # noqa
VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa
IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);'
DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);'
DEFAULT_SCOPE = 'page'
MatomoVar = namedtuple('MatomoVar', ('index', 'name', 'value', 'scope'))
register = Library()
@register.tag
def matomo(parser, token):
"""
Matomo tracking template tag.
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.
Custom variables can be passed in the ``matomo_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.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return MatomoNode()
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")
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)
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)
userid = get_identity(context, 'matomo')
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, 'MATOMO'):
html = disable_html(html, 'Matomo')
return html
def contribute_to_analytical(add_node):
MatomoNode() # ensure properly configured
add_node('body_bottom', MatomoNode)
django-analytical-3.0.0/analytical/templatetags/mixpanel.py 0000664 0000000 0000000 00000006552 13763253701 0024114 0 ustar 00root root 0000000 0000000 """
Mixpanel template tags and filters.
"""
import json
import re
from django.template import Library, Node, TemplateSyntaxError
from django.utils.safestring import mark_safe
from analytical.utils import is_internal_ip, disable_html, get_identity, \
get_required_setting
MIXPANEL_API_TOKEN_RE = re.compile(r'^[0-9a-f]{32}$')
TRACKING_CODE = """
""" # noqa
IDENTIFY_CODE = "mixpanel.identify('%s');"
IDENTIFY_PROPERTIES = "mixpanel.people.set(%s);"
EVENT_CODE = "mixpanel.track('%(name)s', %(properties)s);"
EVENT_CONTEXT_KEY = 'mixpanel_event'
register = Library()
@register.tag
def mixpanel(parser, token):
"""
Mixpanel tracking template tag.
Renders Javascript code to track page visits. You must supply
your Mixpanel token in the ``MIXPANEL_API_TOKEN`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return MixpanelNode()
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")
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))
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),
})
except KeyError:
pass
html = TRACKING_CODE % {
'token': self._token,
'commands': " ".join(commands),
}
if is_internal_ip(context, 'MIXPANEL'):
html = disable_html(html, 'Mixpanel')
return mark_safe(html)
def contribute_to_analytical(add_node):
MixpanelNode() # ensure properly configured
add_node('head_bottom', MixpanelNode)
django-analytical-3.0.0/analytical/templatetags/olark.py 0000664 0000000 0000000 00000010770 13763253701 0023404 0 ustar 00root root 0000000 0000000 """
Olark template tags.
"""
import json
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_identity, get_required_setting
SITE_ID_RE = re.compile(r'^\d+-\d+-\d+-\d+$')
SETUP_CODE = """
""" # noqa
NICKNAME_CODE = "olark('api.chat.updateVisitorNickname', {snippet: '%s'});"
NICKNAME_CONTEXT_KEY = 'olark_nickname'
FULLNAME_CODE = "olark('api.visitor.updateFullName', {{fullName: '{0}'}});"
FULLNAME_CONTEXT_KEY = 'olark_fullname'
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_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",
}
register = Library()
@register.tag
def olark(parser, token):
"""
Olark set-up template tag.
Renders Javascript code to set-up Olark chat. You must supply
your site ID in the ``OLARK_SITE_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return OlarkNode()
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'")
def render(self, context):
extra_code = []
try:
extra_code.append(NICKNAME_CODE % context[NICKNAME_CONTEXT_KEY])
except KeyError:
identity = get_identity(context, 'olark', self._get_nickname)
if identity is not None:
extra_code.append(NICKNAME_CODE % identity)
try:
extra_code.append(FULLNAME_CODE.format(context[FULLNAME_CONTEXT_KEY]))
except KeyError:
pass
try:
extra_code.append(EMAIL_CODE.format(context[EMAIL_CONTEXT_KEY]))
except KeyError:
pass
try:
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),
}
return html
def _get_nickname(self, user):
name = user.get_full_name()
if name:
return "%s (%s)" % (name, user.username)
else:
return user.username
def _get_configuration(self, context):
code = []
for dict_ in context:
for var, val in dict_.items():
if var.startswith('olark_'):
key = var[6:]
if key in MESSAGE_KEYS:
code.append(MESSAGE_CODE % {'key': key, 'msg': val})
return code
def contribute_to_analytical(add_node):
OlarkNode() # ensure properly configured
add_node('body_bottom', OlarkNode)
django-analytical-3.0.0/analytical/templatetags/optimizely.py 0000664 0000000 0000000 00000002477 13763253701 0024506 0 ustar 00root root 0000000 0000000 """
Optimizely template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
SETUP_CODE = """"""
register = Library()
@register.tag
def optimizely(parser, token):
"""
Optimizely template tag.
Renders Javascript code to set-up A/B testing. You must supply
your Optimizely account number in the ``OPTIMIZELY_ACCOUNT_NUMBER``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return OptimizelyNode()
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'")
def render(self, context):
html = SETUP_CODE % {'account_number': self.account_number}
if is_internal_ip(context, 'OPTIMIZELY'):
html = disable_html(html, 'Optimizely')
return html
def contribute_to_analytical(add_node):
OptimizelyNode() # ensure properly configured
add_node('head_top', OptimizelyNode)
django-analytical-3.0.0/analytical/templatetags/performable.py 0000664 0000000 0000000 00000004364 13763253701 0024574 0 ustar 00root root 0000000 0000000 """
Performable template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from django.utils.safestring import mark_safe
from analytical.utils import is_internal_ip, disable_html, get_identity, \
get_required_setting
API_KEY_RE = re.compile(r'^\w+$')
SETUP_CODE = """
""" # noqa
IDENTIFY_CODE = """
"""
EMBED_CODE = """
""" # noqa
register = Library()
@register.tag
def performable(parser, token):
"""
Performable template tag.
Renders Javascript code to set-up Performable tracking. You must
supply your Performable API key in the ``PERFORMABLE_API_KEY``
setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return PerformableNode()
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'")
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)
if is_internal_ip(context, 'PERFORMABLE'):
html = disable_html(html, 'Performable')
return html
@register.simple_tag
def performable_embed(hostname, page_id):
"""
Include a Performable landing page.
"""
return mark_safe(EMBED_CODE % {
'hostname': hostname,
'page_id': page_id,
})
def contribute_to_analytical(add_node):
PerformableNode() # ensure properly configured
add_node('body_bottom', PerformableNode)
django-analytical-3.0.0/analytical/templatetags/piwik.py 0000664 0000000 0000000 00000010006 13763253701 0023407 0 ustar 00root root 0000000 0000000 """
Piwik template tags and filters.
"""
from collections import namedtuple
from itertools import chain
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (is_internal_ip, disable_html,
get_required_setting, get_identity)
import warnings
# domain name (characters separated by a dot), optional port, optional URI path, no slash
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$')
# numeric ID
SITEID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
""" # noqa
VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa
IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);'
DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);'
DEFAULT_SCOPE = 'page'
PiwikVar = namedtuple('PiwikVar', ('index', 'name', 'value', 'scope'))
register = Library()
@register.tag
def piwik(parser, token):
"""
Piwik tracking template tag.
Renders Javascript code to track page visits. You must supply
your Piwik domain (plus optional URI path), and tracked site ID
in the ``PIWIK_DOMAIN_PATH`` and the ``PIWIK_SITE_ID`` setting.
Custom variables can be passed in the ``piwik_vars`` context
variable. It is an iterable of custom variables as tuples like:
``(index, name, value[, scope])`` where scope may be ``'page'``
(default) or ``'visit'``. Index should be an integer and the
other parameters should be strings.
"""
warnings.warn('The Piwik module is deprecated; use the Matomo module.', DeprecationWarning)
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return PiwikNode()
class PiwikNode(Node):
def __init__(self):
self.domain_path = \
get_required_setting('PIWIK_DOMAIN_PATH', DOMAINPATH_RE,
"must be a domain name, optionally followed "
"by an URI path, no trailing slash (e.g. "
"piwik.example.com or my.piwik.server/path)")
self.site_id = \
get_required_setting('PIWIK_SITE_ID', SITEID_RE,
"must be a (string containing a) number")
def render(self, context):
custom_variables = context.get('piwik_vars', ())
complete_variables = (var if len(var) >= 4 else var + (DEFAULT_SCOPE,)
for var in custom_variables)
variables_code = (VARIABLE_CODE % PiwikVar(*var)._asdict()
for var in complete_variables)
commands = []
if getattr(settings, 'PIWIK_DISABLE_COOKIES', False):
commands.append(DISABLE_COOKIES_CODE)
userid = get_identity(context, 'piwik')
if userid is not None:
variables_code = chain(variables_code, (
IDENTITY_CODE % {'userid': userid},
))
html = TRACKING_CODE % {
'url': self.domain_path,
'siteid': self.site_id,
'variables': '\n '.join(variables_code),
'commands': '\n '.join(commands)
}
if is_internal_ip(context, 'PIWIK'):
html = disable_html(html, 'Piwik')
return html
def contribute_to_analytical(add_node):
PiwikNode() # ensure properly configured
add_node('body_bottom', PiwikNode)
django-analytical-3.0.0/analytical/templatetags/rating_mailru.py 0000664 0000000 0000000 00000004341 13763253701 0025126 0 ustar 00root root 0000000 0000000 """
Rating@Mail.ru template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, \
get_required_setting
COUNTER_ID_RE = re.compile(r'^\d{7}$')
COUNTER_CODE = """
""" # noqa
register = Library()
@register.tag
def rating_mailru(parser, token):
"""
Rating@Mail.ru counter template tag.
Renders Javascript code to track page visits. You must supply
your website counter ID (as a string) in the
``RATING_MAILRU_COUNTER_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return RatingMailruNode()
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'")
def render(self, context):
html = COUNTER_CODE % {
'counter_id': self.counter_id,
}
if is_internal_ip(context, 'RATING_MAILRU_METRICA'):
html = disable_html(html, 'Rating@Mail.ru')
return html
def contribute_to_analytical(add_node):
RatingMailruNode() # ensure properly configured
add_node('head_bottom', RatingMailruNode)
django-analytical-3.0.0/analytical/templatetags/snapengage.py 0000664 0000000 0000000 00000016537 13763253701 0024413 0 ustar 00root root 0000000 0000000 """
SnapEngage template tags.
"""
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from django.utils import translation
from analytical.utils import get_identity, get_required_setting
BUTTON_LOCATION_LEFT = 0
BUTTON_LOCATION_RIGHT = 1
BUTTON_LOCATION_TOP = 2
BUTTON_LOCATION_BOTTOM = 3
BUTTON_STYLE_NONE = 0
BUTTON_STYLE_DEFAULT = 1
BUTTON_STYLE_LIVE = 2
FORM_POSITION_TOP_LEFT = 'tl'
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}$')
SETUP_CODE = """
""" # 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);'
SETBUTTON_CODE = 'SnapABug.setButton("%s");'
SETEMAIL_CODE = 'SnapABug.setUserEmail("%s"%s);'
SETLOCALE_CODE = 'SnapABug.setLocale("%s");'
FORM_POSITION_CODE = 'SnapABug.setChatFormPosition("%s");'
FORM_TOP_POSITION_CODE = 'SnapABug.setFormTopPosition(%d);'
BUTTONEFFECT_CODE = 'SnapABug.setButtonEffect("%s");'
DISABLE_OFFLINE_CODE = 'SnapABug.allowOffline(false);'
DISABLE_SCREENSHOT_CODE = 'SnapABug.allowScreenshot(false);'
DISABLE_OFFLINE_SCREENSHOT_CODE = 'SnapABug.showScreenshotOption(false);'
DISABLE_PROACTIVE_CHAT_CODE = 'SnapABug.allowProactiveChat(false);'
DISABLE_SOUNDS_CODE = 'SnapABug.allowChatSound(false);'
register = Library()
@register.tag
def snapengage(parser, token):
"""
SnapEngage set-up template tag.
Renders Javascript code to set-up SnapEngage chat. You must supply
your widget ID in the ``SNAPENGAGE_WIDGET_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return SnapEngageNode()
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'")
def render(self, context):
settings_code = []
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)
if secure_connection:
settings_code.append(SECURE_CONNECTION_CODE)
email = context.get('snapengage_email')
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):
readonly_tail = ',true'
else:
readonly_tail = ''
settings_code.append(SETEMAIL_CODE % (email, readonly_tail))
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')
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')
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)
if not show_offline:
settings_code.append(DISABLE_OFFLINE_CODE)
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)
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)
if not sounds:
settings_code.append(DISABLE_SOUNDS_CODE)
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)
if button == BUTTON_STYLE_NONE:
settings_code.append(INIT_CODE % self.widget_id)
else:
if not isinstance(button, int):
# Assume button as a URL to a custom image
settings_code.append(SETBUTTON_CODE % button)
button_location = self._get_setting(
context,
'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 '',
})
html = SETUP_CODE % {
'widget_id': self.widget_id,
'settings_code': " ".join(settings_code),
}
return html
def _get_setting(self, context, context_key, setting=None, default=None):
try:
return context[context_key]
except KeyError:
if setting is not None:
return getattr(settings, setting, default)
else:
return default
def contribute_to_analytical(add_node):
SnapEngageNode() # ensure properly configured
add_node('body_bottom', SnapEngageNode)
django-analytical-3.0.0/analytical/templatetags/spring_metrics.py 0000664 0000000 0000000 00000005405 13763253701 0025323 0 ustar 00root root 0000000 0000000 """
Spring Metrics template tags and filters.
"""
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_identity, is_internal_ip, disable_html, \
get_required_setting
TRACKING_ID_RE = re.compile(r'^[0-9a-f]+$')
TRACKING_CODE = """
""" # noqa
register = Library()
@register.tag
def spring_metrics(parser, token):
"""
Spring Metrics tracking template tag.
Renders Javascript code to track page visits. You must supply
your Spring Metrics Tracking ID in the
``SPRING_METRICS_TRACKING_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return SpringMetricsNode()
class SpringMetricsNode(Node):
def __init__(self):
self.tracking_id = get_required_setting(
'SPRING_METRICS_TRACKING_ID',
TRACKING_ID_RE, "must be a hexadecimal string")
def render(self, context):
custom = {}
for dict_ in context:
for var, val in dict_.items():
if var.startswith('spring_metrics_'):
custom[var[15:]] = val
if 'email' not in custom:
identity = get_identity(context, 'spring_metrics',
lambda u: u.email)
if identity is not None:
custom['email'] = identity
html = TRACKING_CODE % {
'tracking_id': self.tracking_id,
'custom_commands': self._generate_custom_javascript(custom),
}
if is_internal_ip(context, 'SPRING_METRICS'):
html = disable_html(html, 'Spring Metrics')
return html
def _generate_custom_javascript(self, params):
commands = []
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)
def contribute_to_analytical(add_node):
SpringMetricsNode() # ensure properly configured
add_node('head_bottom', SpringMetricsNode)
django-analytical-3.0.0/analytical/templatetags/uservoice.py 0000664 0000000 0000000 00000005414 13763253701 0024277 0 ustar 00root root 0000000 0000000 """
UserVoice template tags.
"""
import json
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import get_required_setting, get_identity
WIDGET_KEY_RE = re.compile(r'^[a-zA-Z0-9]*$')
TRACKING_CODE = """
"""
IDENTITY = """UserVoice.push(['identify', %(options)s]);"""
TRIGGER = "UserVoice.push(['addTrigger', {}]);"
register = Library()
@register.tag
def uservoice(parser, token):
"""
UserVoice tracking template tag.
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.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return UserVoiceNode()
class UserVoiceNode(Node):
def __init__(self):
self.default_widget_key = get_required_setting(
'USERVOICE_WIDGET_KEY', WIDGET_KEY_RE, "must be an alphanumeric string")
def render(self, context):
widget_key = context.get('uservoice_widget_key')
if not widget_key:
widget_key = self.default_widget_key
if not widget_key:
return ''
# default
options = {}
options.update(getattr(settings, 'USERVOICE_WIDGET_OPTIONS', {}))
options.update(context.get('uservoice_widget_options', {}))
identity = get_identity(context, 'uservoice', self._identify)
if identity:
identity = IDENTITY % {'options': json.dumps(identity, sort_keys=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 ''}
return html
def _identify(self, user):
name = user.get_full_name()
if not name:
name = user.username
return {'name': name, 'email': user.email}
def contribute_to_analytical(add_node):
UserVoiceNode() # ensure properly configured
add_node('body_bottom', UserVoiceNode)
django-analytical-3.0.0/analytical/templatetags/woopra.py 0000664 0000000 0000000 00000006262 13763253701 0023604 0 ustar 00root root 0000000 0000000 """
Woopra template tags and filters.
"""
import json
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import (
disable_html,
get_identity,
get_required_setting,
get_user_from_context,
get_user_is_authenticated,
is_internal_ip,
)
DOMAIN_RE = re.compile(r'^\S+$')
TRACKING_CODE = """
""" # noqa
register = Library()
@register.tag
def woopra(parser, token):
"""
Woopra tracking template tag.
Renders Javascript code to track page visits. You must supply
your Woopra domain in the ``WOOPRA_DOMAIN`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return WoopraNode()
class WoopraNode(Node):
def __init__(self):
self.domain = get_required_setting(
'WOOPRA_DOMAIN', DOMAIN_RE,
"must be a domain name")
def render(self, context):
settings = self._get_settings(context)
visitor = self._get_visitor(context)
html = TRACKING_CODE % {
'settings': json.dumps(settings, sort_keys=True),
'visitor': json.dumps(visitor, sort_keys=True),
}
if is_internal_ip(context, 'WOOPRA'):
html = disable_html(html, 'Woopra')
return html
def _get_settings(self, context):
variables = {'domain': self.domain}
try:
variables['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT)
except AttributeError:
pass
return variables
def _get_visitor(self, context):
params = {}
for dict_ in context:
for var, val in dict_.items():
if var.startswith('woopra_'):
params[var[7:]] = val
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)
if user.email:
params['email'] = user.email
return params
def _identify(self, user):
name = user.get_full_name()
if not name:
name = user.username
return name
def contribute_to_analytical(add_node):
WoopraNode() # ensure properly configured
add_node('head_bottom', WoopraNode)
django-analytical-3.0.0/analytical/templatetags/yandex_metrica.py 0000664 0000000 0000000 00000005610 13763253701 0025265 0 ustar 00root root 0000000 0000000 """
Yandex.Metrica template tags and filters.
"""
import json
import re
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, \
get_required_setting
COUNTER_ID_RE = re.compile(r'^\d{8}$')
COUNTER_CODE = """
""" # noqa
register = Library()
@register.tag
def yandex_metrica(parser, token):
"""
Yandex.Metrica counter template tag.
Renders Javascript code to track page visits. You must supply
your website counter ID (as a string) in the
``YANDEX_METRICA_COUNTER_ID`` setting.
"""
bits = token.split_contents()
if len(bits) > 1:
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
return YandexMetricaNode()
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'")
def render(self, context):
options = {
'id': int(self.counter_id),
'clickmap': True,
'trackLinks': True,
'accurateTrackBounce': True
}
if getattr(settings, 'YANDEX_METRICA_WEBVISOR', False):
options['webvisor'] = True
if getattr(settings, 'YANDEX_METRICA_TRACKHASH', False):
options['trackHash'] = True
if getattr(settings, 'YANDEX_METRICA_NOINDEX', False):
options['ut'] = 'noindex'
if getattr(settings, 'YANDEX_METRICA_ECOMMERCE', False):
options['ecommerce'] = 'dataLayer'
html = COUNTER_CODE % {
'counter_id': self.counter_id,
'options': json.dumps(options),
}
if is_internal_ip(context, 'YANDEX_METRICA'):
html = disable_html(html, 'Yandex.Metrica')
return html
def contribute_to_analytical(add_node):
YandexMetricaNode() # ensure properly configured
add_node('head_bottom', YandexMetricaNode)
django-analytical-3.0.0/analytical/utils.py 0000664 0000000 0000000 00000012340 13763253701 0020735 0 ustar 00root root 0000000 0000000 """
Utility function for django-analytical.
"""
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
HTML_COMMENT = ""
def get_required_setting(setting, value_re, invalid_msg):
"""
Return a constant from ``django.conf.settings``. The `setting`
argument is the constant name, the `value_re` argument is a regular
expression used to validate the setting value and the `invalid_msg`
argument is used as exception message if the value is not valid.
"""
try:
value = getattr(settings, setting)
except AttributeError:
raise AnalyticalException("%s setting: not found" % setting)
if not value:
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))
return value
def get_user_from_context(context):
"""
Get the user instance from the template context, if possible.
If the context does not contain a `request` or `user` attribute,
`None` is returned.
"""
try:
return context['user']
except KeyError:
pass
try:
request = context['request']
return request.user
except (KeyError, AttributeError):
pass
return None
def get_user_is_authenticated(user):
"""Check if the user is authenticated.
This is a compatibility function needed to support both Django 1.x and 2.x;
Django 2.x turns the function into a proper boolean so function calls will
fail.
"""
if callable(user.is_authenticated):
return user.is_authenticated()
else:
return user.is_authenticated
def get_identity(context, prefix=None, identity_func=None, user=None):
"""
Get the identity of a logged in user from a template context.
The `prefix` argument is used to provide different identities to
different analytics services. The `identity_func` argument is a
function that returns the identity of the user; by default the
identity is the username.
"""
if prefix is not None:
try:
return context['%s_identity' % prefix]
except KeyError:
pass
try:
return context['analytical_identity']
except KeyError:
pass
if getattr(settings, 'ANALYTICAL_AUTO_IDENTIFY', True):
try:
if user is None:
user = get_user_from_context(context)
if get_user_is_authenticated(user):
if identity_func is not None:
return identity_func(user)
else:
return user.get_username()
except (KeyError, AttributeError):
pass
return None
def get_domain(context, prefix):
"""
Return the domain used for the tracking code. Each service may be
configured with its own domain (called `_domain`), or a
django-analytical-wide domain may be set (using `analytical_domain`.
If no explicit domain is found in either the context or the
settings, try to get the domain from the contrib sites framework.
"""
domain = context.get('%s_domain' % prefix)
if domain is None:
domain = context.get('analytical_domain')
if domain is None:
domain = getattr(settings, '%s_DOMAIN' % prefix.upper(), None)
if domain is None:
domain = getattr(settings, 'ANALYTICAL_DOMAIN', None)
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):
pass
return domain
def is_internal_ip(context, prefix=None):
"""
Return whether the visitor is coming from an internal IP address,
based on information from the template context.
The prefix is used to allow different analytics services to have
different notions of internal addresses.
"""
try:
request = context['request']
remote_ip = request.META.get('HTTP_X_FORWARDED_FOR', '')
if not remote_ip:
remote_ip = request.META.get('REMOTE_ADDR', '')
if not remote_ip:
return False
internal_ips = None
if prefix is not None:
internal_ips = getattr(settings, '%s_INTERNAL_IPS' % prefix, None)
if internal_ips is None:
internal_ips = getattr(settings, 'ANALYTICAL_INTERNAL_IPS', None)
if internal_ips is None:
internal_ips = getattr(settings, 'INTERNAL_IPS', None)
return remote_ip in (internal_ips or [])
except (KeyError, AttributeError):
return False
def disable_html(html, service):
"""
Disable HTML code by commenting it out.
The `service` argument is used to display a friendly message.
"""
return HTML_COMMENT % {'html': html, 'service': service}
class AnalyticalException(Exception):
"""
Raised when an exception occurs in any django-analytical code that should
be silenced in templates.
"""
silent_variable_failure = True
django-analytical-3.0.0/docs/ 0000775 0000000 0000000 00000000000 13763253701 0016032 5 ustar 00root root 0000000 0000000 django-analytical-3.0.0/docs/_ext/ 0000775 0000000 0000000 00000000000 13763253701 0016771 5 ustar 00root root 0000000 0000000 django-analytical-3.0.0/docs/_ext/local.py 0000664 0000000 0000000 00000001124 13763253701 0020433 0 ustar 00root root 0000000 0000000 def setup(app):
app.add_crossref_type(
directivename="setting",
rolename="setting",
indextemplate="pair: %s; setting",
)
app.add_crossref_type(
directivename="templatetag",
rolename="ttag",
indextemplate="pair: %s; template tag"
)
app.add_crossref_type(
directivename="templatefilter",
rolename="tfilter",
indextemplate="pair: %s; template filter"
)
app.add_crossref_type(
directivename="fieldlookup",
rolename="lookup",
indextemplate="pair: %s; field lookup type",
)
django-analytical-3.0.0/docs/conf.py 0000664 0000000 0000000 00000002340 13763253701 0017330 0 ustar 00root root 0000000 0000000 #
# This file is execfile()d with the current directory set to its containing
# directory.
import os
import sys
sys.path.append(os.path.join(os.path.abspath('.'), '_ext'))
sys.path.append(os.path.dirname(os.path.abspath('.')))
import analytical # noqa
# -- General configuration --------------------------------------------------
project = 'django-analytical'
copyright = '2011-2020, Joost Cassee '
release = analytical.__version__
# The short X.Y version.
version = release.rsplit('.', 1)[0]
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'local']
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
add_function_parentheses = True
pygments_style = 'sphinx'
intersphinx_mapping = {
'http://docs.python.org/2.7': None,
'http://docs.djangoproject.com/en/1.9':
'http://docs.djangoproject.com/en/1.9/_objects/',
}
# -- Options for HTML output ------------------------------------------------
html_theme = 'default'
htmlhelp_basename = 'analyticaldoc'
# -- Options for LaTeX output -----------------------------------------------
latex_documents = [
('index', 'django-analytical.tex', 'Documentation for django-analytical',
'Joost Cassee', 'manual'),
]
django-analytical-3.0.0/docs/features.rst 0000664 0000000 0000000 00000004632 13763253701 0020407 0 ustar 00root root 0000000 0000000 ==========================
Features and customization
==========================
The django-analytical application sets up basic tracking without any
further configuration. This page describes extra features and ways in
which behavior can be customized.
.. _internal-ips:
Internal IP addresses
=====================
Visits by the website developers or internal users are usually not
interesting. The django-analytical will comment out the service
initialization code if the client IP address is detected as one from the
:data:`ANALYTICAL_INTERNAL_IPS` setting. The default value for this
setting is :data:`INTERNAL_IPS`.
Example:
.. code-block:: python
ANALYTICAL_INTERNAL_IPS = ['192.168.1.45', '192.168.1.57']
.. note::
The template tags can only access the visitor IP address if the
HTTP request is present in the template context as the
``request`` variable. For this reason, the
:data:`ANALYTICAL_INTERNAL_IPS` setting only works if you add this
variable to the context yourself when you render the template, or
you use the ``RequestContext`` and add
``'django.core.context_processors.request'`` to the list of
context processors in the ``TEMPLATE_CONTEXT_PROCESSORS``
setting.
.. _identifying-visitors:
Identifying authenticated users
===============================
Some analytics services can track individual users. If the visitor is
logged in through the standard Django authentication system and the
current user is accessible in the template context, the username can be
passed to the analytics services that support identifying users. This
feature is configured by the :data:`ANALYTICAL_AUTO_IDENTIFY` setting
and is enabled by default. To disable:
.. code-block:: python
ANALYTICAL_AUTO_IDENTIFY = False
.. note::
The template tags can only access the visitor username if the
Django user is present in the template context either as the
``user`` variable, or as an attribute on the HTTP request in the
``request`` variable. Use a
:class:`~django.template.RequestContext` to render your
templates and add
``'django.contrib.auth.context_processors.auth'`` or
``'django.core.context_processors.request'`` to the list of
context processors in the :data:`TEMPLATE_CONTEXT_PROCESSORS`
setting. (The first of these is added by default.)
Alternatively, add one of the variables to the context yourself
when you render the template.
django-analytical-3.0.0/docs/history.rst 0000664 0000000 0000000 00000001577 13763253701 0020277 0 ustar 00root root 0000000 0000000 ===================
History and credits
===================
Changelog
=========
The project follows the `Semantic Versioning`_ specification for its
version numbers. Patch-level increments indicate bug fixes, minor
version increments indicate new functionality and major version
increments indicate backwards incompatible changes.
Version 1.0.0 is the last to support Django < 1.7. Users of older Django
versions should stick to 1.0.0, and are encouraged to upgrade their setups.
Starting with 2.0.0, dropping support for obsolete Django versions is not
considered to be a backward-incompatible change.
.. _`Semantic Versioning`: http://semver.org/
.. include:: ../CHANGELOG.rst
Credits
=======
.. include:: ../AUTHORS.rst
.. _helping-out:
Helping out
===========
.. include:: ../README.rst
:start-after: .. start contribute include
:end-before: .. end contribute include
django-analytical-3.0.0/docs/index.rst 0000664 0000000 0000000 00000001205 13763253701 0017671 0 ustar 00root root 0000000 0000000 =================
django-analytical
=================
The django-analytical application integrates analytics services into a
Django_ project.
.. _Django: https://www.djangoproject.com/
:Package: https://pypi.python.org/pypi/django-analytical/
:Source: https://github.com/jazzband/django-analytical
Overview
========
.. include:: ../README.rst
:start-after: .. start docs include
:end-before: .. end docs include
To get a feel of how django-analytical works, check out the
:doc:`tutorial`.
Contents
========
.. toctree::
:maxdepth: 2
tutorial
install
features
services
settings
history
license
django-analytical-3.0.0/docs/install.rst 0000664 0000000 0000000 00000012712 13763253701 0020235 0 ustar 00root root 0000000 0000000 ==============================
Installation and configuration
==============================
Integration of your analytics service is very simple. There are four
steps: installing the package, adding it to the list of installed Django
applications, adding the template tags to your base template, and
configuring the services you use in the project settings.
#. `Installing the Python package`_
#. `Installing the Django application`_
#. `Adding the template tags to the base template`_
#. `Enabling the services`_
.. _installing-the-package:
Installing the Python package
=============================
To install django-analytical the ``analytical`` package must be added to
the Python path. You can install it directly from PyPI using
``easy_install``:
.. code-block:: bash
$ easy_install django-analytical
You can also install directly from source. Download either the latest
stable version from PyPI_ or any release from GitHub_, or use Git to
get the development code:
.. code-block:: bash
$ git clone https://github.com/jazzband/django-analytical.git
.. _PyPI: http://pypi.python.org/pypi/django-analytical/
.. _GitHub: http://github.com/jazzband/django-analytical
Then install the package by running the setup script:
.. code-block:: bash
$ cd django-analytical
$ python setup.py install
.. _installing-the-application:
Installing the Django application
=================================
After you installed django-analytical, add the ``analytical`` Django
application to the list of installed applications in the ``settings.py``
file of your project:
.. code-block:: python
INSTALLED_APPS = [
...
'analytical',
...
]
.. _adding-the-template-tags:
Adding the template tags to the base template
=============================================
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
base template should look like this:
.. code-block:: django
{% load analytical %}
{% analytical_head_top %}
...
{% analytical_head_bottom %}
{% analytical_body_top %}
...
{% analytical_body_bottom %}
Instead of using the generic tags, you can also just use tags specific
for the analytics service(s) you are using. See :ref:`services` for
documentation on using individual services.
.. _enabling-services:
Enabling the services
=====================
Without configuration, the template tags all render the empty string.
Services are configured in the project ``settings.py`` file. The
settings required to enable each service are listed here:
* :doc:`Chartbeat `::
CHARTBEAT_USER_ID = '12345'
* :doc:`Clickmap `::
CLICKMAP_TRACKER_CODE = '12345678....912'
* :doc:`Clicky `::
CLICKY_SITE_ID = '12345678'
* :doc:`Crazy Egg `::
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
* :doc:`Facebook Pixel `::
FACEBOOK_PIXEL_ID = '1234567890'
* :doc:`Gaug.es `::
GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef'
* :doc:`Google Analytics (legacy) `::
GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8'
* :doc:`Google Analytics (gtag.js) `::
GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = 'UA-1234567-8'
* :doc:`Google Analytics (analytics.js) `::
GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-12345678-9'
* :doc:`HubSpot `::
HUBSPOT_PORTAL_ID = '1234'
HUBSPOT_DOMAIN = 'somedomain.web101.hubspot.com'
* :doc:`Intercom `::
INTERCOM_APP_ID = '0123456789abcdef0123456789abcdef01234567'
* :doc:`KISSinsights `::
KISS_INSIGHTS_ACCOUNT_NUMBER = '12345'
KISS_INSIGHTS_SITE_CODE = 'abc'
* :doc:`KISSmetrics `::
KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567'
* :doc:`Lucky Orange `::
LUCKYORANGE_SITE_ID = '123456'
* :doc:`Matomo (formerly Piwik) `::
MATOMO_DOMAIN_PATH = 'your.matomo.server/optional/path'
MATOMO_SITE_ID = '123'
* :doc:`Mixpanel `::
MIXPANEL_API_TOKEN = '0123456789abcdef0123456789abcdef'
* :doc:`Olark `::
OLARK_SITE_ID = '1234-567-89-0123'
* :doc:`Optimizely `::
OPTIMIZELY_ACCOUNT_NUMBER = '1234567'
* :doc:`Performable `::
PERFORMABLE_API_KEY = '123abc'
* :doc:`Piwik (deprecated, see Matomo) `::
PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path'
PIWIK_SITE_ID = '123'
* :doc:`Rating\@Mail.ru `::
RATING_MAILRU_COUNTER_ID = '1234567'
* :doc:`SnapEngage `::
SNAPENGAGE_WIDGET_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
* :doc:`Woopra `::
WOOPRA_DOMAIN = 'abcde.com'
* :doc:`Yandex.Metrica `::
YANDEX_METRICA_COUNTER_ID = '12345678'
----
The django-analytical application is now set-up to track visitors. For
information about identifying users, further configuration and
customization, see :doc:`features`.
django-analytical-3.0.0/docs/license.rst 0000664 0000000 0000000 00000000600 13763253701 0020202 0 ustar 00root root 0000000 0000000 =======
License
=======
The django-analytical package is distributed under the `MIT License`_.
The complete license term are included below. The copyright of the
integration code snippets of individual services rest solely with the
respective service providers.
.. _`MIT License`: http://en.wikipedia.org/wiki/MIT_License
License terms
=============
.. include:: ../LICENSE.txt
django-analytical-3.0.0/docs/services.rst 0000664 0000000 0000000 00000001154 13763253701 0020410 0 ustar 00root root 0000000 0000000 .. _services:
========
Services
========
This section describes what features are supported by the different
analytics services. To start using a service, you can either use the
generic installation instructions (see :doc:`install`), or add
service-specific tags to your templates.
If you would like to have another analytics service supported by
django-analytical, please create an issue on the project
`issue tracker`_. See also :ref:`helping-out`.
.. _`issue tracker`: http://github.com/jazzband/django-analytical/issues
Currently supported services:
.. toctree::
:maxdepth: 1
:glob:
services/*
django-analytical-3.0.0/docs/services/ 0000775 0000000 0000000 00000000000 13763253701 0017655 5 ustar 00root root 0000000 0000000 django-analytical-3.0.0/docs/services/chartbeat.rst 0000664 0000000 0000000 00000007746 13763253701 0022362 0 ustar 00root root 0000000 0000000 =============================
Chartbeat -- traffic analysis
=============================
Chartbeat_ provides real-time analytics to websites and blogs. It shows
visitors, load times, and referring sites on a minute-by-minute basis.
The service also provides alerts the second your website crashes or
slows to a crawl.
.. _Chartbeat: http://www.chartbeat.com/
.. chartbeat-installation:
Installation
============
To start using the Chartbeat 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 Chartbeat template tags to your templates. This
step is only needed if you are not using the generic
:ttag:`analytical.*` tags. If you are, skip to
:ref:`chartbeat-configuration`.
The Chartbeat tracking code is inserted into templates using template
tags. At the top of the template, load the :mod:`chartbeat` template
tag library. Then insert the :ttag:`chartbeat_top` tag at the top of
the head section, and the :ttag:`chartbeat_bottom` tag at the bottom of
the body section::
{% load chartbeat %}
{% chartbeat_top %}
...
{% chartbeat_bottom %}
Because these tags are used to measure page loading time, it is
important to place them as close as possible to the start and end of the
document.
.. _chartbeat-configuration:
Configuration
=============
Before you can use the Chartbeat integration, you must first set your
User ID.
.. _chartbeat-user-id:
Setting the User ID
-------------------
Your Chartbeat account has a unique User ID. You can find your User ID
by visiting the Chartbeat `Add New Site`_ page. The second code snippet
contains a line that looks like this::
var _sf_async_config={uid:XXXXX,domain:"YYYYYYYYYY"};
Here, ``XXXXX`` is your User ID. Set :const:`CHARTBEAT_USER_ID` in the
project :file:`settings.py` file::
CHARTBEAT_USER_ID = 'XXXXX'
If you do not set a User ID, the tracking code will not be rendered.
.. _`Add New Site`: http://chartbeat.com/code/
.. _chartbeat-internal-ips:
Internal IP addresses
---------------------
Usually you do not want to track clicks from your development or
internal IP addresses. By default, if the tags detect that the client
comes from any address in the :const:`CHARTBEAT_INTERNAL_IPS` setting,
the tracking code is commented out. It takes the value of
:const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is
:const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for
important information about detecting the visitor IP address.
.. _chartbeat-domain:
Setting the domain
------------------
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`
will be passed to Chartbeat automatically. You can modify this behavior
using the :const:`CHARTBEAT_AUTO_DOMAIN` setting::
CHARTBEAT_AUTO_DOMAIN = False
Alternatively, you set the domain through the ``chartbeat_domain``
context variable when you render the template::
context = RequestContext({'chartbeat_domain': 'example.com'})
return some_template.render(context)
It is annoying to do this for every view, so you may want to set it in
a context processor that you add to the
:data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`::
def chartbeat(request):
return {'chartbeat_domain': 'example.com'}
The context domain overrides the domain from the current site. If no
domain is set, either explicitly or implicitly through the sites
framework, then no domain is sent, and Chartbeat will detect the domain
name from the URL. If your website uses just one domain, this will work
just fine.
----
Thanks go to Chartbeat for their support with the development of this
application.
django-analytical-3.0.0/docs/services/clickmap.rst 0000664 0000000 0000000 00000004644 13763253701 0022202 0 ustar 00root root 0000000 0000000 ==================================
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`: http://www.getclickmap.com/
.. clickmap-installation:
Installation
============
To start using the Clickmap 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 Clickmap 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:`clickmap-configuration`.
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
the tag at the bottom of the HTML body::
{% load clickmap %}
...
{% clickmap %}