pax_global_header00006660000000000000000000000064137632537010014521gustar00rootroot0000000000000052 comment=896a19196f0ecca146e4cb8fb3b26e71e15642c8 django-analytical-3.0.0/000077500000000000000000000000001376325370100151025ustar00rootroot00000000000000django-analytical-3.0.0/.bandit000077700000000000000000000000001376325370100176522tox.iniustar00rootroot00000000000000django-analytical-3.0.0/.coveragerc000066400000000000000000000000321376325370100172160ustar00rootroot00000000000000[run] source = analytical django-analytical-3.0.0/.github/000077500000000000000000000000001376325370100164425ustar00rootroot00000000000000django-analytical-3.0.0/.github/workflows/000077500000000000000000000000001376325370100204775ustar00rootroot00000000000000django-analytical-3.0.0/.github/workflows/release.yml000066400000000000000000000025101376325370100226400ustar00rootroot00000000000000name: 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.yml000066400000000000000000000021101376325370100221730ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000002221376325370100170660ustar00rootroot00000000000000.*.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.rst000066400000000000000000000046561376325370100167740ustar00rootroot00000000000000The 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.rst000066400000000000000000000132211376325370100171220ustar00rootroot00000000000000Version 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.py000066400000000000000000000036431376325370100240600ustar00rootroot00000000000000""" 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.py000066400000000000000000000045071376325370100235530ustar00rootroot00000000000000""" 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.py000066400000000000000000000037771376325370100242570ustar00rootroot00000000000000""" 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.py000066400000000000000000000047441376325370100252520ustar00rootroot00000000000000""" 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.py000066400000000000000000000031321376325370100235410ustar00rootroot00000000000000""" 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.py000066400000000000000000000155021376325370100256150ustar00rootroot00000000000000""" 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.py000066400000000000000000000043121376325370100266140ustar00rootroot00000000000000""" 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.py000066400000000000000000000125461376325370100263160ustar00rootroot00000000000000""" 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.py000066400000000000000000000043601376325370100242640ustar00rootroot00000000000000""" 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.py000066400000000000000000000027711376325370100235650ustar00rootroot00000000000000""" 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.py000066400000000000000000000033061376325370100237550ustar00rootroot00000000000000""" 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.py000066400000000000000000000104741376325370100241150ustar00rootroot00000000000000""" 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.py000066400000000000000000000042011376325370100251450ustar00rootroot00000000000000""" 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.py000066400000000000000000000060541376325370100247730ustar00rootroot00000000000000""" 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.py000066400000000000000000000030311376325370100246070ustar00rootroot00000000000000""" 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.py000066400000000000000000000076661376325370100236020ustar00rootroot00000000000000""" 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.py000066400000000000000000000065521376325370100241140ustar00rootroot00000000000000""" 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.py000066400000000000000000000107701376325370100234040ustar00rootroot00000000000000""" 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.py000066400000000000000000000024771376325370100245060ustar00rootroot00000000000000""" 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.py000066400000000000000000000043641376325370100245740ustar00rootroot00000000000000""" 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.py000066400000000000000000000100061376325370100234070ustar00rootroot00000000000000""" 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.py000066400000000000000000000043411376325370100251260ustar00rootroot00000000000000""" 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.py000066400000000000000000000165371376325370100244130ustar00rootroot00000000000000""" 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.py000066400000000000000000000054051376325370100253230ustar00rootroot00000000000000""" 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.py000066400000000000000000000054141376325370100242770ustar00rootroot00000000000000""" 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.py000066400000000000000000000062621376325370100236040ustar00rootroot00000000000000""" 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.py000066400000000000000000000056101376325370100252650ustar00rootroot00000000000000""" 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.py000066400000000000000000000123401376325370100207350ustar00rootroot00000000000000""" 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/000077500000000000000000000000001376325370100160325ustar00rootroot00000000000000django-analytical-3.0.0/docs/_ext/000077500000000000000000000000001376325370100167715ustar00rootroot00000000000000django-analytical-3.0.0/docs/_ext/local.py000066400000000000000000000011241376325370100204330ustar00rootroot00000000000000def 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.py000066400000000000000000000023401376325370100173300ustar00rootroot00000000000000# # 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.rst000066400000000000000000000046321376325370100204070ustar00rootroot00000000000000========================== 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.rst000066400000000000000000000015771376325370100202770ustar00rootroot00000000000000=================== 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.rst000066400000000000000000000012051376325370100176710ustar00rootroot00000000000000================= 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.rst000066400000000000000000000127121376325370100202350ustar00rootroot00000000000000============================== 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.rst000066400000000000000000000006001376325370100202020ustar00rootroot00000000000000======= 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.rst000066400000000000000000000011541376325370100204100ustar00rootroot00000000000000.. _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/000077500000000000000000000000001376325370100176555ustar00rootroot00000000000000django-analytical-3.0.0/docs/services/chartbeat.rst000066400000000000000000000077461376325370100223620ustar00rootroot00000000000000============================= 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.rst000066400000000000000000000046441376325370100222020ustar00rootroot00000000000000================================== 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 %} .. _clickmap-configuration: Configuration ============= Before you can use the Clickmap integration, you must first set your Clickmap Tracker ID. If you don't have a Clickmap account yet, `sign up`_ to get your Tracker ID. .. _`sign up`: http://www.getclickmap.com/ .. _clickmap-tracker-id: Setting the Tracker ID ---------------------- Clickmap gives you a unique Tracker ID, and the :ttag:`clickmap` tag will include it in the rendered Javascript code. You can find your Tracker ID clicking the link named "Tracker" in the dashboard of your Clickmap account. Set :const:`CLICKMAP_TRACKER_ID` in the project :file:`settings.py` file:: CLICKMAP_TRACKER_ID = 'XXXXXXXX' If you do not set an Tracker ID, the tracking code will not be rendered. .. _clickmap-internal-ips: Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` setting (which is :const:`INTERNAL_IPS` by default,) the tracking code is commented out. See :ref:`identifying-visitors` for important information about detecting the visitor IP address. django-analytical-3.0.0/docs/services/clicky.rst000066400000000000000000000133201376325370100216640ustar00rootroot00000000000000========================== Clicky -- traffic analysis ========================== Clicky_ is an online web analytics tool. It is similar to Google Analytics in that it provides statistics on who is visiting your website and what they are doing. Clicky provides its data in real time and is designed to be very easy to use. .. _Clicky: http://getclicky.com/ .. clicky-installation: Installation ============ To start using the Clicky 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 Clicky 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:`clicky-configuration`. The Clicky tracking code is inserted into templates using a template tag. Load the :mod:`clicky` template tag library and insert the :ttag:`clicky` 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 clicky %} ... {% clicky %} .. _clicky-configuration: Configuration ============= Before you can use the Clicky integration, you must first set your website Site ID. You can also customize the data that Clicky tracks. .. _clicky-site-id: Setting the Site ID ------------------- Every website you track with Clicky gets its own Site ID, and the :ttag:`clicky` tag will include it in the rendered Javascript code. You can find the Site ID in the *Info* tab of the website *Preferences* page, in your Clicky account. Set :const:`CLICKY_SITE_ID` in the project :file:`settings.py` file:: CLICKY_SITE_ID = 'XXXXXXXX' If you do not set a Site ID, the tracking code will not be rendered. .. _clicky-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:`CLICKY_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. .. _clicky-custom-data: Custom data ----------- As described in the Clicky `customized tracking`_ documentation page, the data that is tracked by Clicky can be customized by setting the :data:`clicky_custom` Javascript variable before loading the tracking code. Using template context variables, you can let the :ttag:`clicky` tag pass custom data to Clicky automatically. You can set the context variables in your view when you render a template containing the tracking code:: context = RequestContext({'clicky_title': 'A better page title'}) return some_template.render(context) It is annoying to do this for every view, so you may want to set custom properties in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def clicky_global_properties(request): return {'clicky_timeout': 10} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. Here is a table with the most important variables. All variables listed on the `customized tracking`_ documentation page can be set by replacing ``clicky_custom.`` with ``clicky_``. ================== =============== =================================== Context variable Clicky property Description ================== =============== =================================== ``clicky_session`` session_ Session data. A dictionary containing ``username`` and/or ``group`` keys. ------------------ --------------- ----------------------------------- ``clicky_goal`` goal_ A succeeded goal. A dictionary containing ``id`` and optionally ``revenue`` keys. ------------------ --------------- ----------------------------------- ``clicky_split`` split_ Split testing page version. A dictionary containing ``name``, ``version`` and optionally ``goal`` keys. ------------------ --------------- ----------------------------------- ``clicky_href`` href_ The URL as tracked by Clicky. Default is the page URL. ------------------ --------------- ----------------------------------- ``clicky_title`` title_ The page title as tracked by Clicky. Default is the HTML title. ================== =============== =================================== .. _`customized tracking`: http://getclicky.com/help/customization .. _session: http://getclicky.com/help/customization#session .. _goal: http://getclicky.com/help/customization#goal .. _href: http://getclicky.com/help/customization#href .. _title: http://getclicky.com/help/customization#title .. _split: http://getclicky.com/help/customization#split .. _clicky-identify-user: Identifying authenticated users ------------------------------- If you have not set the session_ property explicitly, the username of an authenticated user is passed to Clicky automatically. See :ref:`identifying-visitors`. ---- Thanks go to Clicky for their support with the development of this application. django-analytical-3.0.0/docs/services/crazy_egg.rst000066400000000000000000000075331376325370100223710ustar00rootroot00000000000000================================== Crazy Egg -- visual click tracking ================================== `Crazy Egg`_ is an easy to use hosted web application that visualizes website clicks using heatmaps. It allows you to discover the areas of web pages that are most important to your visitors. .. _`Crazy Egg`: http://www.crazyegg.com/ .. crazy-egg-installation: Installation ============ To start using the Crazy Egg 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 Crazy Egg 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:`crazy-egg-configuration`. The Crazy Egg tracking code is inserted into templates using a template tag. Load the :mod:`crazy_egg` template tag library and insert the :ttag:`crazy_egg` 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 crazy_egg %} ... {% crazy_egg %} .. _crazy-egg-configuration: Configuration ============= Before you can use the Crazy Egg integration, you must first set your account number. You can also segment the click analysis through user variables. .. _crazy-egg-account-number: Setting the account number -------------------------- Crazy Egg gives you a unique account number, and the :ttag:`crazy egg` tag will include it in the rendered Javascript code. You can find your account number by clicking the link named "What's my code?" in the dashboard of your Crazy Egg account. Set :const:`CRAZY_EGG_ACCOUNT_NUMBER` in the project :file:`settings.py` file:: CRAZY_EGG_ACCOUNT_NUMBER = 'XXXXXXXX' If you do not set an account number, the tracking code will not be rendered. .. _crazy-egg-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:`CRAZY_EGG_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. .. _crazy-egg-uservars: User variables -------------- Crazy Egg can segment clicks based on `user variables`_. If you want to set a user variable, use the context variables ``crazy_egg_var1`` through ``crazy_egg_var5`` when you render your template:: context = RequestContext({'crazy_egg_var1': 'red', 'crazy_egg_var2': 'male'}) return some_template.render(context) If you use the same user variables in different views and its value can be computed from the HTTP request, you can also set them in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def track_admin_role(request): if request.user.is_staff(): role = 'staff' else: role = 'visitor' return {'crazy_egg_var3': role} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. .. _`user variables`: https://www.crazyegg.com/help/Setting_Up_A_Page_to_Track/How_do_I_set_the_values_of_User_Var_1_User_Var_2_etc_in_the_confetti_and_overlay_views/ ---- The work on Crazy Egg was made possible by `Bateau Knowledge`_. Thanks go to Crazy Egg for their support with the development of this application. .. _`Bateau Knowledge`: http://www.bateauknowledge.nl/ django-analytical-3.0.0/docs/services/facebook_pixel.rst000066400000000000000000000053101376325370100233600ustar00rootroot00000000000000======================================= Facebook Pixel -- advertising analytics ======================================= `Facebook Pixel`_ is Facebook's tool for conversion tracking, optimisation and remarketing. .. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/ .. facebook-pixel-installation: Installation ============ To start using the Facebook Pixel 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 Facebook Pixel 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:`facebook-pixel-configuration`. The Facebook Pixel code is inserted into templates using template tags. Because every page that you want to track must have the tag, it is useful to add it to your base template. At the top of the template, load the :mod:`facebook_pixel` template tag library. Then insert the :ttag:`facebook_pixel_head` tag at the bottom of the head section, and optionally insert the :ttag:`facebook_pixel_body` tag at the bottom of the body section:: {% load facebook_pixel %} ... {% facebook_pixel_head %} ... {% facebook_pixel_body %} .. note:: The :ttag:`facebook_pixel_body` tag code will only be used for browsers with JavaScript disabled. It can be omitted if you don't need to support them. .. _facebook-pixel-configuration: Configuration ============= Before you can use the Facebook Pixel integration, you must first set your Pixel ID. .. _facebook-pixel-id: Setting the Pixel ID -------------------- Each Facebook Adverts account you have can have a Pixel ID, and the :mod:`facebook_pixel` tags will include it in the rendered page. You can find the Pixel ID on the "Pixels" section of your Facebook Adverts account. Set :const:`FACEBOOK_PIXEL_ID` in the project :file:`settings.py` file:: FACEBOOK_PIXEL_ID = 'XXXXXXXXXX' If you do not set a Pixel ID, the code will not be rendered. .. _facebook-pixel-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:`FACEBOOK_PIXEL_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. django-analytical-3.0.0/docs/services/gauges.rst000066400000000000000000000053101376325370100216610ustar00rootroot00000000000000============================= Gaug.es -- Real-time tracking ============================= Gaug.es_ is an easy way to implement real-time tracking for multiple websites. .. _Gaug.es: http://www.gaug.es/ .. gauges-installation: Installation ============ To start using the Gaug.es 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 Gaug.es 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:`gauges-configuration`. The Gaug.es Javascript code is inserted into templates using a template tag. Load the :mod:`gauges` template tag library and insert the :ttag:`gauges` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the top of the HTML head:: {% load gauges %} {% gauges %} ... .. _gauges-configuration: Configuration ============= Before you can use the Gaug.es integration, you must first set your site id. .. _gauges-site-id: Setting the site id -------------------------- Gaug.es gives you a unique site id, and the :ttag:`gauges` tag will include it in the rendered Javascript code. You can find your site id by clicking the *Tracking Code* link when logged into the on the gaug.es website. A page will display containing HTML code looking like this:: The code ``XXXXXXXXXXXXXXXXXXXXXXX`` is your site id. Set :const:`GAUGES_SITE_ID` in the project :file:`settings.py` file:: GAUGES_SITE_ID = 'XXXXXXXXXXXXXXXXXXXXXXX' If you do not set an site id, the Javascript code will not be rendered. .. _gauges-internal-ips: Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` setting (which is :const:`INTERNAL_IPS` by default,) the tracking code is commented out. See :ref:`identifying-visitors` for important information about detecting the visitor IP address. django-analytical-3.0.0/docs/services/google_analytics.rst000066400000000000000000000236161376325370100237420ustar00rootroot00000000000000============================================== Google Analytics (legacy) -- traffic analysis ============================================== `Google Analytics`_ is the well-known web analytics service from Google. The product is aimed more at marketers than webmasters or technologists, supporting integration with AdWords and other e-commence features. .. _`Google Analytics`: http://www.google.com/analytics/ .. google-analytics-installation: Installation ============ To start using the Google Analytics (legacy) integration, you must have installed the django-analytical package and have added the ``analytical`` application to :const:`INSTALLED_APPS` in your project :file:`settings.py` file. See :doc:`../install` for details. Next you need to add the Google Analytics template tag to your templates. This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`google-analytics-configuration`. The Google Analytics tracking code is inserted into templates using a template tag. Load the :mod:`google_analytics` template tag library and insert the :ttag:`google_analytics` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load google_analytics %} ... {% google_analytics %} ... .. _google-analytics-configuration: Configuration ============= Before you can use the Google Analytics integration, you must first set your website property ID. If you track multiple domains with the same code, you also need to set-up the domain. Finally, you can add custom segments for Google Analytics to track. .. _google-analytics-property-id: Setting the property ID ----------------------- Every website you track with Google Analytics gets its own property ID, and the :ttag:`google_analytics` tag will include it in the rendered Javascript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_PROPERTY_ID` in the project :file:`settings.py` file:: GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-XXXXXX-X' If you do not set a property ID, the tracking code will not be rendered. Tracking multiple domains ------------------------- The default code is suitable for tracking a single domain. If you track multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE` setting to one of the :const:`analytical.templatetags.google_analytics.TRACK_*` constants: ============================= ===== ============================================= Constant Value Description ============================= ===== ============================================= ``TRACK_SINGLE_DOMAIN`` 1 Track one domain. ``TRACK_MULTIPLE_SUBDOMAINS`` 2 Track multiple subdomains of the same top domain (e.g. `fr.example.com` and `nl.example.com`). ``TRACK_MULTIPLE_DOMAINS`` 3 Track multiple top domains (e.g. `example.fr` and `example.nl`). ============================= ===== ============================================= As noted, the default tracking style is :const:`~analytical.templatetags.google_analytics.TRACK_SINGLE_DOMAIN`. When you track multiple (sub)domains, django-analytical needs to know what domain name to pass to Google Analytics. If you use the contrib sites app, the domain is automatically picked up from the current :const:`~django.contrib.sites.models.Site` instance. Otherwise, you may either pass the domain to the template tag through the context variable :const:`google_analytics_domain` (fallback: :const:`analytical_domain`) or set it in the project :file:`settings.py` file using :const:`GOOGLE_ANALYTICS_DOMAIN` (fallback: :const:`ANALYTICAL_DOMAIN`). Display Advertising ------------------- Display Advertising allows you to view Demographics and Interests reports, add Remarketing Lists and support DoubleClick Campain Manager integration. You can enable `Display Advertising features`_ by setting the :const:`GOOGLE_ANALYTICS_DISPLAY_ADVERTISING` configuration setting:: GOOGLE_ANALYTICS_DISPLAY_ADVERTISING = True By default, display advertising features are disabled. .. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482 Tracking site speed ------------------- You can view page load times in the `Site Speed report`_ by setting the :const:`GOOGLE_ANALYTICS_SITE_SPEED` configuration setting:: GOOGLE_ANALYTICS_SITE_SPEED = True By default, page load times are not tracked. .. _`Site Speed report`: https://support.google.com/analytics/answer/1205784 .. _google-analytics-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:`GOOGLE_ANALYTICS_INTERNAL_IPS` setting, the tracking code is commented out. It takes the value of :const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is :const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for important information about detecting the visitor IP address. .. _google-analytics-custom-variables: Custom variables ---------------- As described in the Google Analytics `custom variables`_ documentation page, you can define custom segments. Using template context variables ``google_analytics_var1`` through ``google_analytics_var5``, you can let the :ttag:`google_analytics` tag pass custom variables to Google Analytics automatically. You can set the context variables in your view when your render a template containing the tracking code:: context = RequestContext({'google_analytics_var1': ('gender', 'female'), 'google_analytics_var2': ('visit', '1', SCOPE_SESSION)}) return some_template.render(context) The value of the context variable is a tuple *(name, value, [scope])*. The scope parameter is one of the :const:`analytical.templatetags.google_analytics.SCOPE_*` constants: ================= ====== ============================================= Constant Value Description ================= ====== ============================================= ``SCOPE_VISITOR`` 1 Distinguishes categories of visitors across multiple sessions. ``SCOPE_SESSION`` 2 Distinguishes different visitor experiences across sessions. ``SCOPE_PAGE`` 3 Defines page-level activity. ================= ====== ============================================= The default scope is :const:`~analytical.templatetags.google_analytics.SCOPE_PAGE`. You may want to set custom variables in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def google_analytics_segment_language(request): try: return {'google_analytics_var3': request.LANGUAGE_CODE} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. .. _`custom variables`: http://code.google.com/apis/analytics/docs/tracking/gaTrackingCustomVariables.html .. _google-analytics-anonimyze-ips: Anonymize IPs ------------- You can enable the `IP anonymization`_ feature by setting the :const:`GOOGLE_ANALYTICS_ANONYMIZE_IP` configuration setting:: GOOGLE_ANALYTICS_ANONYMIZE_IP = True This may be mandatory for deployments in countries that have a firm policies concerning data privacy (e.g. Germany). By default, IPs are not anonymized. .. _`IP anonymization`: https://support.google.com/analytics/bin/answer.py?hl=en&answer=2763052 .. _google-analytics-sample-rate: Sample Rate ----------- You can configure the `Sample Rate`_ feature by setting the :const:`GOOGLE_ANALYTICS_SAMPLE_RATE` configuration setting:: GOOGLE_ANALYTICS_SAMPLE_RATE = 10 The value is a percentage and can be between 0 and 100 and can be a string or decimal value of with up to two decimal places. .. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsamplerate .. _google-analytics-site-speed-sample-rate: Site Speed Sample Rate ---------------------- You can configure the `Site Speed Sample Rate`_ feature by setting the :const:`GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE` configuration setting:: GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE = 10 The value is a percentage and can be between 0 and 100 and can be a string or decimal value of with up to two decimal places. .. _`Site Speed Sample Rate`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsitespeedsamplerate .. _google-analytics-session-cookie-timeout: Session Cookie Timeout ---------------------- You can configure the `Session Cookie Timeout`_ feature by setting the :const:`GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT` configuration setting:: GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT = 3600000 The value is the session cookie timeout in milliseconds or 0 to delete the cookie when the browser is closed. .. _`Session Cookie Timeout`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout .. _google-analytics-visitor-cookie-timeout: Visitor Cookie Timeout ---------------------- You can configure the `Visitor Cookie Timeout`_ feature by setting the :const:`GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT` configuration setting:: GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT = 3600000 The value is the visitor cookie timeout in milliseconds or 0 to delete the cookie when the browser is closed. .. _`Visitor Cookie Timeout`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setvisitorcookietimeout django-analytical-3.0.0/docs/services/google_analytics_gtag.rst000066400000000000000000000065721376325370100247460ustar00rootroot00000000000000=============================================== Google Analytics (gtag.js) -- traffic analysis =============================================== `Google Analytics`_ is the well-known web analytics service from Google. The product is aimed more at marketers than webmasters or technologists, supporting integration with AdWords and other e-commence features. The global site tag (`gtag.js`_) is a JavaScript tagging framework and API that allows you to send event data to Google Analytics, Google Ads, and Google Marketing Platform. .. _`Google Analytics`: http://www.google.com/analytics/ .. _`gtag.js`: https://developers.google.com/analytics/devguides/collection/gtagjs/ .. google-analytics-installation: Installation ============ To start using the Google Analytics integration, you must have installed the django-analytical package and have added the ``analytical`` application to :const:`INSTALLED_APPS` in your project :file:`settings.py` file. See :doc:`../install` for details. Next you need to add the Google Analytics template tag to your templates. This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`google-analytics-configuration`. The Google Analytics tracking code is inserted into templates using a template tag. Load the :mod:`google_analytics_gtag` template tag library and insert the :ttag:`google_analytics_gtag` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load google_analytics_gtag %} {% google_analytics_gtag %} ... ... .. _google-analytics-configuration: Configuration ============= Before you can use the Google Analytics integration, you must first set your website property ID. If you track multiple domains with the same code, you also need to set-up the domain. Finally, you can add custom segments for Google Analytics to track. .. _google-analytics-property-id: Setting the property ID ----------------------- Every website you track with Google Analytics gets its own property ID, and the :ttag:`google_analytics_gtag` tag will include it in the rendered Javascript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_GTAG_PROPERTY_ID` in the project :file:`settings.py` file:: GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = 'UA-XXXXXX-X' If you do not set a property ID, the tracking code will not be rendered. Please node that the accepted Property IDs should be one of the following formats: - 'UA-XXXXXX-Y' - 'AW-XXXXXXXXXX' - 'G-XXXXXXXX' - 'DC-XXXXXXXX' Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`GOOGLE_ANALYTICS_INTERNAL_IPS` setting, the tracking code is commented out. It takes the value of :const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is :const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for important information about detecting the visitor IP address. .. _google-analytics-identify-user: Identifying authenticated users ------------------------------- The username of an authenticated user is passed to Google Analytics automatically as the `user_id`. See :ref:`identifying-visitors`. django-analytical-3.0.0/docs/services/google_analytics_js.rst000066400000000000000000000206741376325370100244370ustar00rootroot00000000000000==================================================== Google Analytics (analytics.js) -- traffic analysis ==================================================== `Google Analytics`_ is the well-known web analytics service from Google. The product is aimed more at marketers than webmasters or technologists, supporting integration with AdWords and other e-commence features. The `analytics.js`_ library (also known as "the Google Analytics tag") is a JavaScript library for measuring how users interact with your website. .. _`Google Analytics`: http://www.google.com/analytics/ .. _`analytics.js`: https://developers.google.com/analytics/devguides/collection/analyticsjs/ .. google-analytics-installation: Installation ============ To start using the Google Analytics integration, you must have installed the django-analytical package and have added the ``analytical`` application to :const:`INSTALLED_APPS` in your project :file:`settings.py` file. See :doc:`../install` for details. Next you need to add the Google Analytics template tag to your templates. This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`google-analytics-configuration`. The Google Analytics tracking code is inserted into templates using a template tag. Load the :mod:`google_analytics_js` template tag library and insert the :ttag:`google_analytics_js` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load google_analytics_js %} ... {% google_analytics_js %} ... .. _google-analytics-configuration: Configuration ============= Before you can use the Google Analytics integration, you must first set your website property ID. If you track multiple domains with the same code, you also need to set-up the domain. Finally, you can add custom segments for Google Analytics to track. .. _google-analytics-property-id: Setting the property ID ----------------------- Every website you track with Google Analytics gets its own property ID, and the :ttag:`google_analytics_js` tag will include it in the rendered Javascript code. You can find the web property ID on the overview page of your account. Set :const:`GOOGLE_ANALYTICS_JS_PROPERTY_ID` in the project :file:`settings.py` file:: GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-XXXXXXXX-X' If you do not set a property ID, the tracking code will not be rendered. Tracking multiple domains ------------------------- The default code is suitable for tracking a single domain. If you track multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE` setting to one of the :const:`analytical.templatetags.google_analytics_js.TRACK_*` constants: ============================= ===== ============================================= Constant Value Description ============================= ===== ============================================= ``TRACK_SINGLE_DOMAIN`` 1 Track one domain. ``TRACK_MULTIPLE_SUBDOMAINS`` 2 Track multiple subdomains of the same top domain (e.g. `fr.example.com` and `nl.example.com`). ``TRACK_MULTIPLE_DOMAINS`` 3 Track multiple top domains (e.g. `example.fr` and `example.nl`). ============================= ===== ============================================= As noted, the default tracking style is :const:`~analytical.templatetags.google_analytics_js.TRACK_SINGLE_DOMAIN`. When you track multiple (sub)domains, django-analytical needs to know what domain name to pass to Google Analytics. If you use the contrib sites app, the domain is automatically picked up from the current :const:`~django.contrib.sites.models.Site` instance. Otherwise, you may either pass the domain to the template tag through the context variable :const:`google_analytics_domain` (fallback: :const:`analytical_domain`) or set it in the project :file:`settings.py` file using :const:`GOOGLE_ANALYTICS_DOMAIN` (fallback: :const:`ANALYTICAL_DOMAIN`). Display Advertising ------------------- Display Advertising allows you to view Demographics and Interests reports, add Remarketing Lists and support DoubleClick Campain Manager integration. You can enable `Display Advertising features`_ by setting the :const:`GOOGLE_ANALYTICS_DISPLAY_ADVERTISING` configuration setting:: GOOGLE_ANALYTICS_DISPLAY_ADVERTISING = True By default, display advertising features are disabled. .. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482 .. _google-analytics-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:`GOOGLE_ANALYTICS_INTERNAL_IPS` setting, the tracking code is commented out. It takes the value of :const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is :const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for important information about detecting the visitor IP address. .. _google-analytics-custom-variables: Custom variables ---------------- As described in the Google Analytics `custom variables`_ documentation page, you can define custom segments. Using template context variables ``google_analytics_var1`` through ``google_analytics_var5``, you can let the :ttag:`google_analytics_js` tag pass custom variables to Google Analytics automatically. You can set the context variables in your view when your render a template containing the tracking code:: context = RequestContext({'google_analytics_var1': ('gender', 'female'), 'google_analytics_var2': ('visit', 1)}) return some_template.render(context) The value of the context variable is a tuple *(name, value)*. You may want to set custom variables in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def google_analytics_segment_language(request): try: return {'google_analytics_var3': request.LANGUAGE_CODE} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. .. _`custom variables`: https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#custom-vars .. _google-analytics-anonimyze-ips: Anonymize IPs ------------- You can enable the `IP anonymization`_ feature by setting the :const:`GOOGLE_ANALYTICS_ANONYMIZE_IP` configuration setting:: GOOGLE_ANALYTICS_ANONYMIZE_IP = True This may be mandatory for deployments in countries that have a firm policies concerning data privacy (e.g. Germany). By default, IPs are not anonymized. .. _`IP anonymization`: https://support.google.com/analytics/bin/answer.py?hl=en&answer=2763052 .. _google-analytics-sample-rate: Sample Rate ----------- You can configure the `Sample Rate`_ feature by setting the :const:`GOOGLE_ANALYTICS_SAMPLE_RATE` configuration setting:: GOOGLE_ANALYTICS_SAMPLE_RATE = 10 The value is a percentage and can be between 0 and 100 and can be a string or integer value. .. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#sampleRate .. _google-analytics-site-speed-sample-rate: Site Speed Sample Rate ---------------------- You can configure the `Site Speed Sample Rate`_ feature by setting the :const:`GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE` configuration setting:: GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE = 10 The value is a percentage and can be between 0 and 100 and can be a string or integer value. .. _`Site Speed Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#siteSpeedSampleRate .. _google-analytics-cookie-expiration: Cookie Expiration ---------------------- You can configure the `Cookie Expiration`_ feature by setting the :const:`GOOGLE_ANALYTICS_COOKIE_EXPIRATION` configuration setting:: GOOGLE_ANALYTICS_COOKIE_EXPIRATION = 3600000 The value is the cookie expiration in seconds or 0 to delete the cookie when the browser is closed. .. _`Cookie Expiration`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout django-analytical-3.0.0/docs/services/gosquared.rst000066400000000000000000000065041376325370100224060ustar00rootroot00000000000000=============================== GoSquared -- traffic monitoring =============================== GoSquared_ provides both real-time traffic monitoring and and trends. It tells you what is currently happening at your website, what is popular, locate and identify visitors and track twitter. .. _GoSquared: http://www.gosquared.com/ Installation ============ To start using the GoSquared 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 GoSquared 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:`gosquared-configuration`. The GoSquared tracking code is inserted into templates using a template tag. Load the :mod:`gosquared` template tag library and insert the :ttag:`gosquared` 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 gosquared %} ... {% gosquared %} .. _gosquared-configuration: Configuration ============= When you set up a website to be tracked by GoSquared, it assigns the site a token. You can find the token on the *Tracking Code* tab of your website settings page. Set :const:`GOSQUARED_SITE_TOKEN` in the project :file:`settings.py` file:: GOSQUARED_SITE_TOKEN = 'XXX-XXXXXX-X' If you do not set a site token, the tracking code will not be rendered. Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`GOSQUARED_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. Identifying authenticated users ------------------------------- If your websites identifies visitors, you can pass this information on to GoSquared to display on the LiveStats dashboard. By default, the name of an authenticated user is passed to GoSquared automatically. See :ref:`identifying-visitors`. You can also send the visitor identity yourself by adding either the ``gosquared_identity`` or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'gosquared_identity': identity}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def identify(request): try: return {'gosquared_identity': request.user.username} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. ---- Thanks go to GoSquared for their support with the development of this application. django-analytical-3.0.0/docs/services/hotjar.rst000066400000000000000000000041241376325370100216770ustar00rootroot00000000000000===================================== Hotjar -- analytics and user feedback ===================================== `Hotjar`_ is a website analytics and user feedback tool. .. _`Hotjar`: https://www.hotjar.com/ .. hotjar-installation: Installation ============ To start using the Hotjar 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 Hotjar 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:`hotjar-configuration`. The Hotjar code is inserted into templates using template tags. Because every page that you want to track must have the tag, it is useful to add it to your base template. At the top of the template, load the :mod:`hotjar` template tag library. Then insert the :ttag:`hotjar` tag at the bottom of the head section:: {% load hotjar %} ... {% hotjar %} ... .. _hotjar-configuration: Configuration ============= Before you can use the Hotjar integration, you must first set your Site ID. .. _hotjar-id: Setting the Hotjar Site ID -------------------------- You can find the Hotjar Site ID in the "Sites & Organizations" section of your Hotjar account. Set :const:`HOTJAR_SITE_ID` in the project :file:`settings.py` file:: HOTJAR_SITE_ID = 'XXXXXXXXX' If you do not set a Hotjar Site ID, the code will not be rendered. .. _hotjar-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:`HOTJAR_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. django-analytical-3.0.0/docs/services/hubspot.rst000066400000000000000000000047441376325370100221040ustar00rootroot00000000000000============================ HubSpot -- inbound marketing ============================ HubSpot_ helps you get found by customers. It provides tools for content creation, conversion and marketing analysis. HubSpot uses tracking on your website to measure effect of your marketing efforts. .. _HubSpot: http://www.hubspot.com/ .. hubspot-installation: Installation ============ To start using the HubSpot 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 HubSpot 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:`hubspot-configuration`. The HubSpot tracking code is inserted into templates using a template tag. Load the :mod:`hubspot` template tag library and insert the :ttag:`hubspot` 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 hubspot %} ... {% hubspot %} .. _hubspot-configuration: Configuration ============= Before you can use the HubSpot integration, you must first set your portal ID, also known as your Hub ID. .. _hubspot-portal-id: Setting the portal ID --------------------- Your HubSpot account has its own portal ID, the :ttag:`hubspot` tag will include them in the rendered JavaScript code. You can find the portal ID by accessing your dashboard. Alternatively, read this `Quick Answer page `_. Set :const:`HUBSPOT_PORTAL_ID` in the project :file:`settings.py` file:: HUBSPOT_PORTAL_ID = 'XXXX' If you do not set the portal ID, the tracking code will not be rendered. .. deprecated:: 0.18.0 `HUBSPOT_DOMAIN` is no longer required. .. _hubspot-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:`HUBSPOT_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. django-analytical-3.0.0/docs/services/intercom.rst000066400000000000000000000146511376325370100222360ustar00rootroot00000000000000================================= Intercom.io -- Real-time tracking ================================= Intercom.io_ is an easy way to implement real-chat and individual support for a website .. _Intercom.io: http://www.intercom.io/ .. intercom-installation: Installation ============ To start using the Intercom.io 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 Intercom.io 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:`intercom-configuration`. The Intercom.io Javascript code is inserted into templates using a template tag. Load the :mod:`intercom` template tag library and insert the :ttag:`intercom` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body:: {% load intercom %} {% intercom %} ... .. _intercom-configuration: Configuration ============= Before you can use the Intercom.io integration, you must first set your app id. .. _intercom-site-id: Setting the app id -------------------------- Intercom.io gives you a unique app id, and the :ttag:`intercom` tag will include it in the rendered Javascript code. You can find your app id by clicking the *Tracking Code* link when logged into the on the intercom.io website. A page will display containing HTML code looking like this:: The code ``XXXXXXXXXXXXXXXXXXXXXXX`` is your app id. Set :const:`INTERCOM_APP_ID` in the project :file:`settings.py` file:: INTERCOM_APP_ID = 'XXXXXXXXXXXXXXXXXXXXXXX' If you do not set an app id, the Javascript code will not be rendered. Custom data ----------- As described in the Intercom documentation on `custom visitor data`_, the data that is tracked by Intercom can be customized. Using template context variables, you can let the :ttag:`intercom` tag pass custom data to Intercom automatically. You can set the context variables in your view when your render a template containing the tracking code:: context = RequestContext({'intercom_cart_value': cart.total_price}) return some_template.render(context) For some data, it is annoying to do this for every view, so you may want to set variables in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: from django.utils.hashcompat import md5_constructor as md5 GRAVATAR_URL = 'http://www.gravatar.com/avatar/' def intercom_custom_data(request): try: email = request.user.email except AttributeError: return {} email_hash = md5(email).hexdigest() avatar_url = "%s%s" % (GRAVATAR_URL, email_hash) return {'intercom_avatar': avatar_url} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. Standard variables that will be displayed in the Intercom live visitor data are listed in the table below, but you can define any ``intercom_*`` variable you like and have that detail passed from within the visitor live stream data when viewing Intercom. ==================== =========================================== Context variable Description ==================== =========================================== ``intercom_name`` The visitor's full name. -------------------- ------------------------------------------- ``intercom_email`` The visitor's email address. -------------------- ------------------------------------------- ``intercom_user_id`` The visitor's user id. -------------------- ------------------------------------------- ``created_at`` The date the visitor created an account ==================== =========================================== .. _`custom visitor data`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/customize-intercom-to-be-about-your-users/send-custom-user-attributes-to-intercom Identifying authenticated users ------------------------------- If you have not set the ``intercom_name``, ``intercom_email``, or ``intercom_user_id`` variables explicitly, the username and email address of an authenticated user are passed to Intercom automatically. See :ref:`identifying-visitors`. .. _intercom-internal-ips: Verifying identified users -------------------------- Intercom supports HMAC authentication of users identified by user ID or email, in order to prevent impersonation. For more information, see `Enable identity verification on your web product`_ in the Intercom documentation. To enable this, configure your Intercom account's HMAC secret key:: INTERCOM_HMAC_SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXX' (You can find this secret key under the "Identity verification" section of your Intercom account settings page.) .. _`Enable identity verification on your web product`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` setting (which is :const:`INTERNAL_IPS` by default,) the tracking code is commented out. See :ref:`identifying-visitors` for important information about detecting the visitor IP address. django-analytical-3.0.0/docs/services/kiss_insights.rst000066400000000000000000000103771376325370100233000ustar00rootroot00000000000000================================ KISSinsights -- feedback surveys ================================ KISSinsights_ provides unobtrusive surveys that pop up from the bottom right-hand corner of your website. Asking specific questions gets you the targeted, actionable feedback you need to make your site better. .. _KISSinsights: http://www.kissinsights.com/ .. kiss-insights-installation: Installation ============ To start using the KISSinsights 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 KISSinsights 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:`kiss-insights-configuration`. The KISSinsights survey code is inserted into templates using a template tag. Load the :mod:`kiss_insights` template tag library and insert the :ttag:`kiss_insights` 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 top of the HTML body:: {% load kiss_insights %} ... {% kiss_insights %} ... .. _kiss-insights-configuration: Configuration ============= Before you can use the KISSinsights integration, you must first set your account number and site code. .. _kiss-insights-account-number: Setting the account number and site code ---------------------------------------- In order to install the survey code, you need to set your KISSinsights account number and website code. The :ttag:`kiss_insights` tag will include it in the rendered Javascript code. You can find the account number and website code by visiting the code installation page of the website you want to place the surveys on. You will see some HTML code with a Javascript tag with a ``src`` attribute containing ``//s3.amazonaws.com/ki.js/XXXXX/YYY.js``. Here ``XXXXX`` is the account number and ``YYY`` the website code. Set :const:`KISS_INSIGHTS_ACCOUNT_NUMBER` and :const:`KISS_INSIGHTS_WEBSITE_CODE` in the project :file:`settings.py` file:: KISSINSIGHTS_ACCOUNT_NUMBER = 'XXXXX' KISSINSIGHTS_SITE_CODE = 'XXX' If you do not set the account number and website code, the survey code will not be rendered. .. _kiss-insights-identity-user: Identifying authenticated users ------------------------------- If your websites identifies visitors, you can pass this information on to KISSinsights so that you can tie survey submissions to customers. By default, the username of an authenticated user is passed to KISSinsights automatically. See :ref:`identifying-visitors`. You can also send the visitor identity yourself by adding either the ``kiss_insights_identity`` or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'kiss_insights_identity': identity}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def identify(request): try: return {'kiss_insights_identity': request.user.email} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. .. _kiss-insights-show-survey: Showing a specific survey ------------------------- KISSinsights can also be told to show a specific survey. You can let the :ttag:`kiss_insights` tag include the code to select a survey by passing the survey ID to the template in the ``kiss_insights_show_survey`` context variable:: context = RequestContext({'kiss_insights_show_survey': 1234}) return some_template.render(context) For information about how to find the survey ID, see the explanation on `"How can I show a survey after a custom trigger condition?"`_ on the KISSinsights help page. .. _`"How can I show a survey after a custom trigger condition?"`: http://www.kissinsights.com/help#customer-trigger django-analytical-3.0.0/docs/services/kiss_metrics.rst000066400000000000000000000124231376325370100231100ustar00rootroot00000000000000============================== KISSmetrics -- funnel analysis ============================== KISSmetrics_ is an easy to implement analytics solution that provides a powerful visual representation of your customer lifecycle. Discover how many visitors go from your landing page to pricing to sign up, and how many drop out at each stage. .. _KISSmetrics: http://www.kissmetrics.com/ .. kiss-metrics-installation: Installation ============ To start using the KISSmetrics 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 KISSmetrics 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:`kiss-metrics-configuration`. The KISSmetrics Javascript code is inserted into templates using a template tag. Load the :mod:`kiss_metrics` template tag library and insert the :ttag:`kiss_metrics` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the top of the HTML head:: {% load kiss_metrics %} {% kiss_metrics %} ... .. _kiss-metrics-configuration: Configuration ============= Before you can use the KISSmetrics integration, you must first set your API key. .. _kiss-metrics-api-key: Setting the API key ------------------- Every website you track events for with KISSmetrics gets its own API key, and the :ttag:`kiss_metrics` tag will include it in the rendered Javascript code. You can find the website API key by visiting the website *Product center* on your KISSmetrics dashboard. Set :const:`KISS_METRICS_API_KEY` in the project :file:`settings.py` file:: KISS_METRICS_API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' If you do not set an API key, the tracking code will not be rendered. .. _kiss-metrics-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:`KISS_METRICS_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. .. _kiss-metrics-identify-user: Identifying users ----------------- If your websites identifies visitors, you can pass this information on to KISSmetrics so that you can tie events to users. By default, the username of an authenticated user is passed to KISSmetrics automatically. See :ref:`identifying-visitors`. You can also send the visitor identity yourself by adding either the ``kiss_metrics_identity`` or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'kiss_metrics_identity': identity}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def identify(request): try: return {'kiss_metrics_identity': request.user.email} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. .. _kiss-metrics-alias: Alias ----- Alias is used to associate one identity with another. This most likely will occur if a user is not signed in yet, you assign them an anonymous identity and record activity for them and they later sign in and you get a named identity. For example:: context = RequestContext({ 'kiss_metrics_alias': {'my_registered@email' : 'my_user_id'}, }) return some_template.render(context) The output script tag will then include the corresponding properties as documented in the `KISSmetrics alias API`_ docs. .. _`KISSmetrics alias API`: http://support.kissmetrics.com/apis/common-methods#alias Recording events ---------------- You may tell KISSmetrics about an event by setting a variable in the context. For example:: context = RequestContext({ 'kiss_metrics_event': ['Signed Up', {'Plan' : 'Pro', 'Amount' : 9.99}], }) return some_template.render(context) The output script tag will then include the corresponding Javascript event as documented in the `KISSmetrics record API`_ docs. .. _kiss-metrics-properties: Recording properties -------------------- You may also set KISSmetrics properties without a corresponding event. For example:: context = RequestContext({ 'kiss_metrics_properties': {'gender': 'Male'}, }) return some_template.render(context) The output script tag will then include the corresponding properties as documented in the `KISSmetrics set API`_ docs. .. _`KISSmetrics set API`: http://support.kissmetrics.com/apis/common-methods#record .. _`KISSmetrics record API`: http://support.kissmetrics.com/apis/common-methods#set django-analytical-3.0.0/docs/services/luckyorange.rst000066400000000000000000000044471376325370100227430ustar00rootroot00000000000000================================================== Lucky Orange -- All-in-one conversion optimization ================================================== `Lucky Orange`_ is a website analytics and user feedback tool. .. _`Lucky Orange`: https://www.luckyorange.com/ .. luckyorange-installation: Installation ============ To start using the Lucky Orange integration, you must have installed the django-analytical package and have added the ``analytical`` application to :const:`INSTALLED_APPS` in your project :file:`settings.py` file. See :doc:`../install` for details. Next you need to add the Lucky Orange template tag to your templates. This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`luckyorange-configuration`. The Lucky Orange tracking code is inserted into templates using template tags. Because every page that you want to track must have the tag, it is useful to add it to your base template. At the top of the template, load the :mod:`luckyorange` template tag library. Then insert the :ttag:`luckyorange` tag at the bottom of the head section:: {% load luckyorange %} ... {% luckyorange %} ... .. _luckyorange-configuration: Configuration ============= Before you can use the Lucky Orange integration, you must first set your Site ID. .. _luckyorange-id: Setting the Lucky Orange Site ID -------------------------------- You can find the Lucky Orange Site ID in the "Settings" of your Lucky Orange account, reachable via the gear icon on the top right corner. Set :const:`LUCKYORANGE_SITE_ID` in the project :file:`settings.py` file:: LUCKYORANGE_SITE_ID = 'XXXXXX' If you do not set a Lucky Orange Site ID, the code will not be rendered. .. _luckyorange-internal-ips: Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`LUCKYORANGE_INTERNAL_IPS` setting, the tracking code is commented out. It takes the value of :const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is :const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for important information about detecting the visitor IP address. django-analytical-3.0.0/docs/services/matomo.rst000066400000000000000000000132631376325370100217100ustar00rootroot00000000000000================================== Matomo (formerly Piwik) -- open source web analytics ================================== Matomo_ is an open analytics platform currently used by individuals, companies and governments all over the world. .. _Matomo: http://matomo.org/ Installation ============ To start using the Matomo 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 Matomo 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:`matomo-configuration`. The Matomo tracking code is inserted into templates using a template tag. Load the :mod:`matomo` template tag library and insert the :ttag:`matomo` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body as recommended by the `Matomo best practice for Integration Plugins`_:: {% load matomo %} ... {% matomo %} .. _`Matomo best practice for Integration Plugins`: http://matomo.org/integrate/how-to/ .. _matomo-configuration: Configuration ============= Before you can use the Matomo integration, you must first define domain name and optional URI path to your Matomo server, as well as the Matomo ID of the website you're tracking with your Matomo server, in your project settings. Setting the domain ------------------ Your Django project needs to know where your Matomo server is located. Typically, you'll have Matomo installed on a subdomain of its own (e.g. ``matomo.example.com``), otherwise it runs in a subdirectory of a website of yours (e.g. ``www.example.com/matomo``). Set :const:`MATOMO_DOMAIN_PATH` in the project :file:`settings.py` file accordingly:: MATOMO_DOMAIN_PATH = 'matomo.example.com' If you do not set a domain the tracking code will not be rendered. Setting the site ID ------------------- Your Matomo server can track several websites. Each website has its site ID (this is the ``idSite`` parameter in the query string of your browser's address bar when you visit the Matomo Dashboard). Set :const:`MATOMO_SITE_ID` in the project :file:`settings.py` file to the value corresponding to the website you're tracking:: MATOMO_SITE_ID = '4' If you do not set the site ID the tracking code will not be rendered. .. _matomo-uservars: User variables -------------- Matomo supports sending `custom variables`_ along with default statistics. If you want to set a custom variable, use the context variable ``matomo_vars`` when you render your template. It should be an iterable of custom variables represented by tuples like: ``(index, name, value[, scope])``, where scope may be ``'page'`` (default) or ``'visit'``. ``index`` should be an integer and the other parameters should be strings. :: context = Context({ 'matomo_vars': [(1, 'foo', 'Sir Lancelot of Camelot'), (2, 'bar', 'To seek the Holy Grail', 'page'), (3, 'spam', 'Blue', 'visit')] }) return some_template.render(context) Matomo default settings allow up to 5 custom variables for both scope. Variable mapping between index and name must stay constant, or the latest name override the previous one. If you use the same user variables in different views and its value can be computed from the HTTP request, you can also set them in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`. .. _`custom variables`: http://developer.matomo.org/guides/tracking-javascript-guide#custom-variables .. _matomo-user-tracking: User tracking ------------- If you use the standard Django authentication system, you can allow Matomo to `track individual users`_ by setting the :data:`ANALYTICAL_AUTO_IDENTIFY` setting to :const:`True`. This is enabled by default. Matomo will identify users based on their ``username``. If you disable this settings, or want to customize what user id to use, you can set the context variable ``analytical_identity`` (for global configuration) or ``matomo_identity`` (for Matomo specific configuration). Setting one to :const:`None` will disable the user tracking feature:: # Matomo will identify this user as 'BDFL' if ANALYTICAL_AUTO_IDENTIFY is True or unset request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') # Matomo will identify this user as 'Guido van Rossum' request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') context = Context({ 'matomo_identity': request.user.get_full_name() }) # Matomo will not identify this user (but will still collect statistics) request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') context = Context({ 'matomo_identity': None }) .. _`track individual users`: http://developer.matomo.org/guides/tracking-javascript-guide#user-id Disabling cookies ----------------- If you want to `disable cookies`_, set :data:`MATOMO_DISABLE_COOKIES` to :const:`True`. This is disabled by default. .. _`disable cookies`: https://matomo.org/faq/general/faq_157/ Internal IP addresses --------------------- Usually, you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` (which takes the value of :const:`INTERNAL_IPS` by default) the tracking code is commented out. See :ref:`identifying-visitors` for important information about detecting the visitor IP address. django-analytical-3.0.0/docs/services/mixpanel.rst000066400000000000000000000117121376325370100222260ustar00rootroot00000000000000========================== Mixpanel -- event tracking ========================== Mixpanel_ tracks events and actions to see what features users are using the most and how they are trending. You could use it for real-time analysis of visitor retention or funnels. .. _Mixpanel: http://www.mixpanel.com/ .. mixpanel-installation: Installation ============ To start using the Mixpanel 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 Mixpanel 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:`mixpanel-configuration`. The Mixpanel Javascript code is inserted into templates using a template tag. Load the :mod:`mixpanel` template tag library and insert the :ttag:`mixpanel` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load mixpanel %} ... {% mixpanel %} ... .. _mixpanel-configuration: Configuration ============= Before you can use the Mixpanel integration, you must first set your token. .. _mixpanel-api-key: Setting the token ----------------- Every website you track events for with Mixpanel gets its own token, and the :ttag:`mixpanel` tag will include it in the rendered Javascript code. You can find the project token on the Mixpanel *projects* page. Set :const:`MIXPANEL_API_TOKEN` in the project :file:`settings.py` file:: MIXPANEL_API_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' If you do not set a token, the tracking code will not be rendered. .. _mixpanel-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:`MIXPANEL_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. .. _mixpanel-identify-user: Identifying users ----------------- If your websites identifies visitors, you can pass this information on to Mixpanel so that you can tie events to users. By default, the username of an authenticated user is passed to Mixpanel automatically. See :ref:`identifying-visitors`. You can also send the visitor identity yourself by adding either the ``mixpanel_identity`` or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'mixpanel_identity': identity}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def identify(request): try: return {'mixpanel_identity': request.user.email} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. Mixpanel can also receive properties for your identified user, using `mixpanel.people.set`_. If want to send extra properties, just set a dictionary instead of a string in the ``mixpanel_identity`` context variable. The key ``id`` or ``username`` will be used as the user unique id, and any other key-value pair will be passed as *people properties*. For example:: def identify(request): try: return { 'mixpanel_identity': { 'id': request.user.id, 'last_login': str(request.user.last_login), 'date_joined': str(request.user.date_joined), } } except AttributeError: return {} .. _`mixpanel.people.set`: https://mixpanel.com/help/reference/javascript-full-api-reference#mixpanel.people.set .. mixpanel-events: Tracking events =============== The django-analytical app integrates the Mixpanel Javascript API in templates. To tracking events in views or other parts of Django, you can use Wes Winham's `mixpanel-celery`_ package. If you want to track an event in Javascript, use the asynchronous notation, as described in the section titled `"Asynchronous Tracking with Javascript"`_ in the Mixpanel documentation. For example:: mixpanel.track("play-game", {"level": "12", "weapon": "sword", "character": "knight"}); .. _mixpanel-celery: http://github.com/winhamwr/mixpanel-celery .. _`"Asynchronous Tracking with Javascript"`: http://mixpanel.com/api/docs/guides/integration/js#async django-analytical-3.0.0/docs/services/olark.rst000066400000000000000000000232051376325370100215210ustar00rootroot00000000000000===================== Olark -- visitor chat ===================== Olark_ is a lightweight tool to chat with visitors to your website using your existing instant messaging client. Chat with your website visitors while they browse, using your mobile device or instant messenger. Olark is fully customizable, supports multiple operators and keeps chat transcripts. .. _Olark: http://www.olark.com/ Installation ============ To start using the Olark 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 Olark 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:`olark-configuration`. The Olark Javascript code is inserted into templates using a template tag. Load the :mod:`olark` template tag library and insert the :ttag:`olark` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body:: {% load olark %} ... {% olark %} .. _olark-configuration: Configuration ============= Before you can use the Olark integration, you must first set your site ID. You can customize the visitor nickname and add information to their status in the operator buddy list, and customize the text used in the chat window. Setting the site ID ------------------- In order to install the chat code, you need to set your Olark site ID. The :ttag:`olark` tag will include it in the rendered Javascript code. You can find the site ID on `installation page`_ of you Olark account. Set :const:`OLARK_SITE_ID` in the project :file:`settings.py` file:: OLARK_SITE_ID = 'XXXX-XXX-XX-XXXX' If you do not set the site ID, the chat window will not be rendered. .. _`installation page`: https://www.olark.com/install Setting the visitor nickname ---------------------------- If your website identifies visitors, you can use that to set their nickname in the operator buddy list. By default, the name and username of an authenticated user are automatically used to set the nickname. See :ref:`identifying-visitors`. You can also set the visitor nickname yourself by adding either the ``olark_nickname`` (alias: ``olark_identity``) or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'olark_nickname': nick}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def set_olark_nickname(request): try: return {'olark_nickname': request.user.email} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. See also `api.chat.updateVisitorNickname`_ in the Olark Javascript API documentation. .. _`api.chat.updateVisitorNickname`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorNickname Adding status information ------------------------- If you want to send more information about the visitor to the operators, you can add text snippets to the status field in the buddy list. Set the ``olark_status`` context variable to a string or a list of strings and the :ttag:`olark` tag will pass them to Olark as status messages:: context = RequestContext({'olark_status': [ 'has %d items in cart' % cart.item_count, 'value of items is $%0.2f' % cart.total_value, ]}) return some_template.render(context) See also `api.chat.updateVisitorStatus`_ in the Olark Javascript API documentation. .. _`api.chat.updateVisitorStatus`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorStatus Customizing the chat window messages ------------------------------------ Olark lets you customize the appearance of the Chat window by changing location, colors and messages text. While you can configure these on the Olark website, sometimes one set of messages is not enough. For example, if you want to localize your website, you want to address every visitor in their own language. Olark allows you to set the messages on a per-page basis, and the :ttag:`olark` tag supports this feature by way of the following context variables: ========================================== ============================= Context variable Example message ========================================== ============================= ``olark_welcome_title`` Click to Chat ------------------------------------------ ----------------------------- ``olark_chatting_title`` Live Help: Now Chatting ------------------------------------------ ----------------------------- ``olark_unavailable_title`` Live Help: Offline ------------------------------------------ ----------------------------- ``olark_busy_title`` Live Help: Busy ------------------------------------------ ----------------------------- ``olark_away_message`` Our live support feature is currently offline, Please try again later. ------------------------------------------ ----------------------------- ``olark_loading_title`` Loading Olark... ------------------------------------------ ----------------------------- ``olark_welcome_message`` Welcome to my website. You can use this chat window to chat with me. ------------------------------------------ ----------------------------- ``olark_busy_message`` All of our representatives are with other customers at this time. We will be with you shortly. ------------------------------------------ ----------------------------- ``olark_chat_input_text`` Type here and hit to chat ------------------------------------------ ----------------------------- ``olark_name_input_text`` and type your Name ------------------------------------------ ----------------------------- ``olark_email_input_text`` and type your Email ------------------------------------------ ----------------------------- ``olark_offline_note_message`` We are offline, send us a message ------------------------------------------ ----------------------------- ``olark_send_button_text`` Send ------------------------------------------ ----------------------------- ``olark_offline_note_thankyou_text`` Thank you for your message. We will get back to you as soon as we can. ------------------------------------------ ----------------------------- ``olark_offline_note_error_text`` You must complete all fields and specify a valid email address ------------------------------------------ ----------------------------- ``olark_offline_note_sending_text`` Sending... ------------------------------------------ ----------------------------- ``olark_operator_is_typing_text`` is typing... ------------------------------------------ ----------------------------- ``olark_operator_has_stopped_typing_text`` has stopped typing ------------------------------------------ ----------------------------- ``olark_introduction_error_text`` Please leave a name and email address so we can contact you in case we get disconnected ------------------------------------------ ----------------------------- ``olark_introduction_messages`` Welcome, just fill out some brief information and click 'Start chat' to talk to us ------------------------------------------ ----------------------------- ``olark_introduction_submit_button_text`` Start chat ========================================== ============================= As an example, you could set the texts site-wide base on the current language using a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: OLARK_TEXTS = { 'en': { 'welcome title': "Click for Live Help", 'chatting_title': "Live Help: Now chatting", ... }, 'nl': { 'welcome title': "Klik voor online hulp", 'chatting_title': "Online hulp: in gesprek", ... }, ... } def set_olark_texts(request): lang = request.LANGUAGE_CODE.split('-', 1)[0] texts = OLARK_TEXTS.get(lang) if texts is None: texts = OLARK_TEXTS.get('en') return dict(('olark_%s' % k, v) for k, v in texts.items()) See also the Olark blog post on `supporting multiple languages`_. .. _`supporting multiple languages`: http://www.olark.com/blog/2010/olark-in-your-favorite-language/ ---- Thanks go to Olark for their support with the development of this application. django-analytical-3.0.0/docs/services/optimizely.rst000066400000000000000000000051661376325370100226240ustar00rootroot00000000000000========================= Optimizely -- A/B testing ========================= Optimizely_ is an easy way to implement A/B testing. Try different decisions, images, layouts, and copy without touching your website code and see exactly how your experiments are affecting pageviews, retention and sales. .. _Optimizely: http://www.optimizely.com/ .. optimizely-installation: Installation ============ To start using the Optimizely 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 Optimizely 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:`optimizely-configuration`. The Optimizely Javascript code is inserted into templates using a template tag. Load the :mod:`optimizely` template tag library and insert the :ttag:`optimizely` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the top of the HTML head:: {% load optimizely %} {% optimizely %} ... .. _optimizely-configuration: Configuration ============= Before you can use the Optimizely integration, you must first set your account number. .. _optimizely-account-number: Setting the account number -------------------------- Optimizely gives you a unique account number, and the :ttag:`optimizely` tag will include it in the rendered Javascript code. You can find your account number by clicking the *Implementation* link in the top right-hand corner of the Optimizely website. A pop-up window will appear containing HTML code looking like this:: The number ``XXXXXXX`` is your account number. Set :const:`OPTIMIZELY_ACCOUNT_NUMBER` in the project :file:`settings.py` file:: OPTIMIZELY_ACCOUNT_NUMBER = 'XXXXXXX' If you do not set an account number, the Javascript code will not be rendered. .. _optimizely-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:`OPTIMIZELY_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. django-analytical-3.0.0/docs/services/performable.rst000066400000000000000000000111341376325370100227050ustar00rootroot00000000000000============================================== Performable -- web analytics and landing pages ============================================== Performable_ provides a platform for inbound marketing, landing pages and web analytics. Its analytics module tracks individual customer interaction, funnel and e-commerce analysis. Landing pages can be created and designed on-line, and integrated with you existing website. .. _Performable: http://www.performable.com/ .. performable-installation: Installation ============ To start using the Performable 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 Performable 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:`performable-configuration`. The Performable Javascript code is inserted into templates using a template tag. Load the :mod:`performable` template tag library and insert the :ttag:`performable` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body:: {% load performable %} ... {% performable %} .. _performable-configuration: Configuration ============= Before you can use the Performable integration, you must first set your API key. .. _performable-account-code: Setting the API key ------------------- You Performable account has its own API key, which :ttag:`performable` tag will include it in the rendered Javascript code. You can find your API key on the *Account Settings* page (click 'Account Settings' in the top right-hand corner of your Performable dashboard). Set :const:`PERFORMABLE_API_KEY` in the project :file:`settings.py` file:: PERFORMABLE_API_KEY = 'XXXXXX' If you do not set an API key, the Javascript code will not be rendered. .. _performable-identity-user: Identifying authenticated users ------------------------------- If your websites identifies visitors, you can pass this information on to Performable so that you can track individual users. By default, the username of an authenticated user is passed to Performable automatically. See :ref:`identifying-visitors`. You can also send the visitor identity yourself by adding either the ``performable_identity`` or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'performable_identity': identity}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def identify(request): try: return {'performable_identity': request.user.email} except AttributeError: return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. .. _performable-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:`PERFORMABLE_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. .. _performable-embed-page: Embedding a landing page ======================== You can embed a Performable landing page in your Django website. The :ttag:`performable_embed` template tag adds the Javascript code to embed the page. It takes two arguments: the hostname and the page ID:: {% performable_embed HOSTNAME PAGE_ID %} To find the hostname and page ID, select :menuselection:`Manage --> Manage Landing Pages` on your Performable dashboard. Select the landing page you want to embed. Look at the URL in your browser address bar; it will look like this:: http://my.performable.com/s/HOSTNAME/page/PAGE_ID/ (If you are placing the hostname and page id values in the template, do not forget to enclose them in quotes or they will be considered context variable names.) ---- Thanks go to Performable for their support with the development of this application. django-analytical-3.0.0/docs/services/piwik.rst000066400000000000000000000142701376325370100215360ustar00rootroot00000000000000================================== Piwik (deprecated) -- open source web analytics ================================== Piwik_ is an open analytics platform currently used by individuals, companies and governments all over the world. With Piwik, your data will always be yours, because you run your own analytics server. .. _Piwik: http://www.piwik.org/ Deprecated ========== Note that Piwik is now known as Matomo. New projects should use the Matomo integration. The Piwik integration in django-analytical is deprecated and eventually will be removed. Installation ============ To start using the Piwik integration, you must have installed the django-analytical package and have added the ``analytical`` application to :const:`INSTALLED_APPS` in your project :file:`settings.py` file. See :doc:`../install` for details. Next you need to add the Piwik template tag to your templates. This step is only needed if you are not using the generic :ttag:`analytical.*` tags. If you are, skip to :ref:`piwik-configuration`. The Piwik tracking code is inserted into templates using a template tag. Load the :mod:`piwik` template tag library and insert the :ttag:`piwik` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body as recommended by the `Piwik best practice for Integration Plugins`_:: {% load piwik %} ... {% piwik %} .. _`Piwik best practice for Integration Plugins`: http://piwik.org/integrate/how-to/ .. _piwik-configuration: Configuration ============= Before you can use the Piwik integration, you must first define domain name and optional URI path to your Piwik server, as well as the Piwik ID of the website you're tracking with your Piwik server, in your project settings. Setting the domain ------------------ Your Django project needs to know where your Piwik server is located. Typically, you'll have Piwik installed on a subdomain of its own (e.g. ``piwik.example.com``), otherwise it runs in a subdirectory of a website of yours (e.g. ``www.example.com/piwik``). Set :const:`PIWIK_DOMAIN_PATH` in the project :file:`settings.py` file accordingly:: PIWIK_DOMAIN_PATH = 'piwik.example.com' If you do not set a domain the tracking code will not be rendered. Setting the site ID ------------------- Your Piwik server can track several websites. Each website has its site ID (this is the ``idSite`` parameter in the query string of your browser's address bar when you visit the Piwik Dashboard). Set :const:`PIWIK_SITE_ID` in the project :file:`settings.py` file to the value corresponding to the website you're tracking:: PIWIK_SITE_ID = '4' If you do not set the site ID the tracking code will not be rendered. .. _piwik-uservars: User variables -------------- Piwik supports sending `custom variables`_ along with default statistics. If you want to set a custom variable, use the context variable ``piwik_vars`` when you render your template. It should be an iterable of custom variables represented by tuples like: ``(index, name, value[, scope])``, where scope may be ``'page'`` (default) or ``'visit'``. ``index`` should be an integer and the other parameters should be strings. :: context = Context({ 'piwik_vars': [(1, 'foo', 'Sir Lancelot of Camelot'), (2, 'bar', 'To seek the Holy Grail', 'page'), (3, 'spam', 'Blue', 'visit')] }) return some_template.render(context) Piwik default settings allow up to 5 custom variables for both scope. Variable mapping between index and name must stay constant, or the latest name override the previous one. If you use the same user variables in different views and its value can be computed from the HTTP request, you can also set them in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`. .. _`custom variables`: http://developer.piwik.org/guides/tracking-javascript-guide#custom-variables .. _piwik-user-tracking: User tracking ------------- If you use the standard Django authentication system, you can allow Piwik to `track individual users`_ by setting the :data:`ANALYTICAL_AUTO_IDENTIFY` setting to :const:`True`. This is enabled by default. Piwik will identify users based on their ``username``. If you disable this settings, or want to customize what user id to use, you can set the context variable ``analytical_identity`` (for global configuration) or ``piwik_identity`` (for Piwik specific configuration). Setting one to :const:`None` will disable the user tracking feature:: # Piwik will identify this user as 'BDFL' if ANALYTICAL_AUTO_IDENTIFY is True or unset request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') # Piwik will identify this user as 'Guido van Rossum' request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') context = Context({ 'piwik_identity': request.user.get_full_name() }) # Piwik will not identify this user (but will still collect statistics) request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum') context = Context({ 'piwik_identity': None }) .. _`track individual users`: http://developer.piwik.org/guides/tracking-javascript-guide#user-id Disabling cookies ----------------- If you want to `disable cookies`_, set :data:`PIWIKI_DISABLE_COOKIES` to :const:`True`. This is disabled by default. .. _`disable cookies`: https://matomo.org/faq/general/faq_157/ Internal IP addresses --------------------- Usually, you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` (which takes the value of :const:`INTERNAL_IPS` by default) the tracking code is commented out. See :ref:`identifying-visitors` for important information about detecting the visitor IP address. ---- Thanks go to Piwik for providing an excellent web analytics platform entirely for free! Consider donating_ to ensure that they continue their development efforts in the spirit of open source and freedom for our personal data. .. _donating: http://piwik.org/donate/ django-analytical-3.0.0/docs/services/rating_mailru.rst000066400000000000000000000044661376325370100232560ustar00rootroot00000000000000=================================== Rating\@Mail.ru -- traffic analysis =================================== `Rating\@Mail.ru`_ is an analytics tool like as google analytics. .. _`Rating\@Mail.ru`: http://top.mail.ru/ .. rating-mailru-installation: Installation ============ To start using the Rating\@Mail.ru 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 Rating\@Mail.ru 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:`rating-mailru-configuration`. The Rating\@Mail.ru counter code is inserted into templates using a template tag. Load the :mod:`rating_mailru` template tag library and insert the :ttag:`rating_mailru` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load rating_mailru %} ... {% rating_mailru %} ... .. _rating-mailru-configuration: Configuration ============= Before you can use the Rating\@Mail.ru integration, you must first set your website counter ID. .. _rating-mailru-counter-id: Setting the counter ID ---------------------- Every website you track with Rating\@Mail.ru gets its own counter ID, and the :ttag:`rating_mailru` tag will include it in the rendered Javascript code. You can find the web counter ID on the overview page of your account. Set :const:`RATING_MAILRU_COUNTER_ID` in the project :file:`settings.py` file:: RATING_MAILRU_COUNTER_ID = '1234567' If you do not set a counter ID, the counter code will not be rendered. Internal IP addresses --------------------- Usually you do not want to track clicks from your development or internal IP addresses. By default, if the tags detect that the client comes from any address in the :const:`RATING_MAILRU_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. django-analytical-3.0.0/docs/services/snapengage.rst000066400000000000000000000201321376325370100225150ustar00rootroot00000000000000======================= SnapEngage -- live chat ======================= SnapEngage_ is a live chat widget for your site which integrates with your existing chat client. It integrates with many online applications and even allows you to make a remote screenshot of the webpage. SnapEngage can be customized to fit your website look and feel, offers reports and statistics and is available in many languages. .. _SnapEngage: http://www.snapengage.com/ Installation ============ To start using the SnapEngage 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 SnapEngage 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:`snapengage-configuration`. The SnapEngage Javascript code is inserted into templates using a template tag. Load the :mod:`SnapEngage` template tag library and insert the :ttag:`SnapEngage` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body:: {% load snapengage %} ... {% snapengage %} .. _snapengage-configuration: Configuration ============= Before you can use the SnapEngage integration, you must first set the widget ID. You can customize the visitor nickname and add information to their status in the operator buddy list, and customize the text used in the chat window. Setting the widget ID --------------------- In order to install the chat code, you need to set the ID of the SnapEngage widget. You can find the site ID on the `Your Widget ID page`_ of your SnapEngage account. Set :const:`SNAPENGAGE_WIDGET_ID` in the project :file:`settings.py` file:: SNAPENGAGE_WIDGET_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' If you do not set the widget ID, the chat window will not be rendered. .. _`Your Widget ID page`: https://secure.snapengage.com/getwidgetid Customizing the widget ---------------------- The SnapEngage widget can be customized in various ways using either context variables or settings. More information about controlling the widget can be found on the `customization FAQ section`_ of the SnapEngage website. ===================================== ===================================== ================================================================== Setting Context variable Description ===================================== ===================================== ================================================================== ``SNAPENGAGE_DOMAIN`` ``snapengage_domain`` Manually set the domain name to follow users across subdomains. ``SNAPENGAGE_SECURE_CONNECTION`` ``snapengage_secure_connection`` Force the use of SSL for the chat connection, even on unencrypted pages. (Default: ``False``) ``SNAPENGAGE_BUTTON_EFFECT`` ``snapengage_button_effect`` An effect applied when the mouse hovers over the button. (Example: ``"-4px"``) ``SNAPENGAGE_BUTTON_STYLE`` ``snapengage_button_style`` What the chat button should look like. Use any of the :const:`BUTTON_STYLE_*` constants, or a URL to a custom button image. ``SNAPENGAGE_BUTTON_LOCATION`` ``snapengage_button_location`` The location of the chat button. Use any of the :const:`BUTTON_LOCATION_*` constants. ``SNAPENGAGE_BUTTON_LOCATION_OFFSET`` ``snapengage_button_location_offset`` The offset of the button from the top or left side of the page. (Default: ``"55%"``) ``SNAPENGAGE_FORM_POSITION`` ``snapengage_form_position`` Configure the location of the chat window. Use any of the :const:`FORM_POSITION_*` constants. ``SNAPENGAGE_FORM_TOP_POSITION`` ``snapengage_form_top_position`` The chat window offset in pixels from the top of the page. ``SNAPENGAGE_READONLY_EMAIL`` ``snapengage_readonly_email`` Whether a preset e-mail address can be changed by the visitor. (Default: ``False``) ``SNAPENGAGE_SHOW_OFFLINE`` ``snapengage_show_offline`` Whether to show the chat button when all operators are offline. (Default: ``True``) ``SNAPENGAGE_SCREENSHOTS`` ``snapengage_screenshots`` Whether to allow the user to take a screenshot. (Default: ``True``) ``SNAPENGAGE_OFFLINE_SCREENSHOTS`` ``snapengage_offline_screenshots`` Whether to allow the user to take a screenshot when all operators are offline. (Default: ``True``) ``SNAPENGAGE_SOUNDS`` ``snapengage_sounds`` Whether to play chat sound notifications. (Default: ``True``) ===================================== ===================================== ================================================================== There are also two customizations that can only be used with context variables. ============================= ========================================= Context variable Description ============================= ========================================= ``snapengage_proactive_chat`` Set to ``False`` to disable proactive chat, for example for users who are already converted. ``snapengage_email`` Set the e-mail address of the website visitor. (See :ref:`snapengage-email`) ============================= ========================================= .. _`customization FAQ section`: http://www.snapengage.com/faq#customization .. _snapengage-email: Setting the visitor e-mail address ---------------------------------- If your website identifies visitors, you can use that to pass their e-mail address to the support agent. By default, the e-mail address of an authenticated user is automatically used. See :ref:`identifying-visitors`. You can also set the visitor e-mail address yourself by adding either the ``snapengage_email`` (alias: ``snapengage_identity``) or the ``analytical_identity`` variable to the template context. If both variables are set, the former takes precedence. For example:: context = RequestContext({'snapengage_email': email}) return some_template.render(context) If you can derive the e-mail address from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: from django.core.exceptions import ObjectDoesNotExist def set_snapengage_email(request): try: profile = request.user.get_profile() return {'snapengage_email': profile.business_email} except (AttributeError, ObjectDoesNotExist): return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. If the user should not be able to edit the pre-set e-mail address, you can set either the ``snapengage_readonly_email`` context variable or the :setting:`SNAPENGAGE_READONLY_EMAIL` setting to ``True``. ---- Thanks go to SnapEngage for their support with the development of this application. django-analytical-3.0.0/docs/services/spring_metrics.rst000066400000000000000000000125611376325370100234440ustar00rootroot00000000000000===================================== Spring Metrics -- conversion tracking ===================================== `Spring Metrics`_ is a convesions analysis tool. It shows you the top converting sources, search keywords and landing pages. The real-time dashboard shows you how customers interact with your website and how to increase conversion. .. _`Spring Metrics`: http://www.springmetrics.com/ Installation ============ To start using the Spring Metrics 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 Spring Metrics 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:`spring-metrics-configuration`. The Spring Metrics tracking code is inserted into templates using a template tag. Load the :mod:`spring_metrics` template tag library and insert the :ttag:`spring_metrics` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load spring_metrics %} ... {% spring_metrics %} ... .. _spring-metrics-configuration: Configuration ============= Before you can use the Spring Metrics integration, you must first set your website Tracking ID and tag a page for conversion. You can also customize the data that Spring Metrics tracks. Setting the Tracking ID ----------------------- Every website you track with Spring Metrics gets its own Tracking ID, and the :ttag:`spring_metrics` tag will include it in the rendered Javascript code. You can find the Tracking ID in the `Site Settings`_ of your Spring Metrics account. Set :const:`SPRING_METRICS_TRACKING_ID` in the project :file:`settings.py` file:: SPRING_METRICS_TRACKING_ID = 'XXXXXXXXXX' If you do not set a Tracking ID, the tracking code will not be rendered. .. _`manage page`: https://app.springmetrics.com/manage/ .. _`Convertion Tagging`: Tagging conversion ------------------ In order to make use of Spring Metrics, you must tell it when visitors become customers. This is called conversion. Usually, it marked by the client requesting a specific page, such as the "thank you" page of a webshop checkout. You tag these pages in the `Site Settings`_ of your Spring Metrics account. Alternatively, you can mark conversion pages using the :data:`spring_metrics_convert` template context variable:: context = RequestContext({'spring_metrics_convert': 'mailinglist signup'}) return some_template.render(context) .. _`Site Settings`: https://app.springmetrics.com/manage Tracking revenue ---------------- Spring Metrics allows you to track the value of conversions. Using the :data:`spring_metrics_revenue` template context variable, you can let the :ttag:`spring_metrics` tag pass earned revenue to Spring Metrics. You can set the context variable in your view when you render a template containing the tracking code:: context = RequestContext({ 'spring_metrics_convert': 'sale', 'spring_metrics_revenue': '30.53', }) return some_template.render(context) (You would not need to use the :data:`spring_metrics_convert` variable if you already tagged the page in Spring Metrics.) Custom data ----------- Spring Metrics can also track other data. Interesting examples could be transaction IDs or the e-mail addresses from logged in users. By setting any :data:`spring_metrics_X` template context variable, Spring Metrics will track a variable named :data:`X`. For example:: context = RequestContext({ 'spring_metrics_revenue': '30.53', 'spring_metrics_order_id': '15445', }) return some_template.render(context) Some variables should be passed on every page and can be computed from the request object. In such cases you will want to set custom variables in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def spring_metrics_global_variables(request): try: profile = request.user.get_profile() return {'spring_metrics_city': profile.address.city} except (AttributeError, ObjectDoesNotExist): return {} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. Identifying authenticated users ------------------------------- If you have not set the :data:`spring_metrics_email` property explicitly, the e-mail address of an authenticated user is passed to Spring Metrics automatically. See :ref:`identifying-visitors`. 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:`SPRING_METRICS_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. ---- Thanks go to Spring Metrics for their support with the development of this application. django-analytical-3.0.0/docs/services/uservoice.rst000066400000000000000000000162441376325370100224220ustar00rootroot00000000000000.. After updating this file, remember to upload to the UserVoice knowledge base. ======================================= UserVoice -- user feedback and helpdesk ======================================= UserVoice_ makes it simple for your customers to give, discuss, and vote for feedback. An unobtrusive feedback tab allows visitors to easily submit and discuss ideas without having to sign up for a new account. The best ideas are delivered to you based on customer votes. .. _UserVoice: http://www.uservoice.com/ .. _uservoice-installation: Installation ============ To start using the UserVoice 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 UserVoice 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:`uservoice-configuration`. The UserVoice Javascript code is inserted into templates using a template tag. Load the :mod:`uservoice` template tag library and insert the :ttag:`uservoice` tag. Because every page that you want to have the feedback tab to appear on must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML body:: {% load uservoice %} ... {% uservoice %} .. _uservoice-configuration: Configuration ============= Before you can use the UserVoice integration, you must first set the widget key. Setting the widget key ---------------------- In order to use the feedback widget, you need to configure which widget you want to show. You can find the widget keys in the *Channels* tab on your UserVoice *Settings* page. Under the *Javascript Widget* heading, find the Javascript embed code of the widget. The widget key is the alphanumerical string contained in the URL of the script imported by the embed code:: (The widget key is shown as ``XXXXXXXXXXXXXXXXXXXX``.) The default widget .................. Often you will use the same widget throughout your website. The default widget key is configured by setting :const:`USERVOICE_WIDGET_KEY` in the project :file:`settings.py` file:: USERVOICE_WIDGET_KEY = 'XXXXXXXXXXXXXXXXXXXX' If the setting is present but empty, no widget is shown by default. This is useful if you want to set a widget using a template context variable, as the setting must be present for the generic :ttag:`analytical.*` tags to work. Widget options .............. You can set :const:`USERVOICE_WIDGET_OPTIONS` to customize your widget with UserVoice's options. .. tip:: See the `JS SDK Overview `_ and the `reference `_ for the details of available options. For example, to override the default icon style with a tab and on the left, you could define: .. code-block:: python USERVOICE_WIDGET_OPTIONS = {"trigger_position": "left", "trigger_style": "tab"} Per-view widget ............... The widget configuration can be overriden in a view using ``uservoice_widget_options`` template context variable. For example: .. code-block:: python context = RequestContext({'uservoice_widget_options': 'mode': 'satisfaction'}) return some_template.render(context) It's also possible to set a different widget key for a particular view with ``uservoice_widget_key``: .. code-block:: python context = RequestContext({'uservoice_widget_key': 'XXXXXXXXXXXXXXXXXXXX'}) return some_template.render(context) These variable passed in the context overrides the default widget configuration. .. _uservoice-link: Using a custom link ------------------- Instead of showing the default feedback icon or tab, you can make the UserVoice widget launch when a visitor clicks a link or when some other event occurs. As the `documentation describe `_, simply add the ``data-uv-trigger`` HTML attribute to the element. For example:: Contact us In order to hidden the default trigger, you should disable it putting ``uservoice_add_trigger`` to ``False``:: context = RequestContext({'uservoice_add_trigger': False}) return your_template_with_custom_uservoice_link.render(context) If you want to disable the automatic trigger globally, set in :file:`settings.py`:: USERVOICE_ADD_TRIGGER = False Setting the widget key in a context processor ............................................. You can also set the widget keys in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`. For example, to show a specific widget to logged in users:: def uservoice_widget_key(request): try: if request.user.is_authenticated(): return {'uservoice_widget_key': 'XXXXXXXXXXXXXXXXXXXX'} except AttributeError: pass return {} The widget key passed in the context variable overrides both the default and the per-view widget key. Identifying users ----------------- If your websites identifies visitors, you can pass this information on to Uservoice. By default, the name and email of an authenticated user is passed to Uservoice automatically. See :ref:`identifying-visitors`. You can also send the visitor identity yourself by adding either the ``uservoice_identity`` or the ``analytical_identity`` variable to the template context. (If both are set, the former takes precedence.) This should be a dictionary with the desired user traits as its keys. Check the `documentation on identifying users`_ to see valid traits. For example:: context = RequestContext({'uservoice_identity': {'email': user_email, 'name': username }}) return some_template.render(context) If you can derive the identity from the HTTP request, you can also use a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: def identify(request): try: return {'uservoice_identity': { email: request.user.username, name: request.user.get_full_name(), id: request.user.id, type: 'vip', account: { name: 'Acme, Co.', monthly_rate: 9.99, ltv: 1495.00, plan: 'Enhanced' } } } except AttributeError: return {} .. _`documentation on identifying users`: https://developer.uservoice.com/docs/widgets/identify/ ---- Thanks go to UserVoice for their support with the development of this application. django-analytical-3.0.0/docs/services/woopra.rst000066400000000000000000000140201376325370100217130ustar00rootroot00000000000000=========================== Woopra -- website analytics =========================== Woopra_ generates live detailed statistics about the visitors to your website. You can watch your visitors navigate live and interact with them via chat. The service features notifications, campaigns, funnels and more. .. _Woopra: http://www.woopra.com/ Installation ============ To start using the Woopra 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 Woopra 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:`woopra-configuration`. The Woopra tracking code is inserted into templates using a template tag. Load the :mod:`woopra` template tag library and insert the :ttag:`woopra` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load woopra %} ... {% woopra %} ... Because Javascript code is asynchronous, putting the tag in the head section increases the chances that a page view is going to be tracked before the visitor leaves the page. See for details the `Asynchronous JavaScript Developer’s Guide`_ on the Woopra website. .. _`Asynchronous JavaScript Developer’s Guide`: http://www.woopra.com/docs/async/ .. _woopra-configuration: Configuration ============= Before you can use the Woopra integration, you must first set the website domain. You can also customize the data that Woopra tracks and identify authenticated users. Setting the domain ------------------ A Woopra account is tied to a website domain. Set :const:`WOOPRA_DOMAIN` in the project :file:`settings.py` file:: WOOPRA_DOMAIN = 'XXXXXXXX.XXX' If you do not set a domain, the tracking code will not be rendered. (In theory, the django-analytical application could get the website domain from the current ``Site`` or the ``request`` object, but this setting also works as a sign that the Woopra integration should be enabled for the :ttag:`analytical.*` template tags.) Visitor timeout --------------- The default Woopra visitor timeout -- the time after which Woopra ignores inactive visitors on a website -- is set at 4 minutes. This means that if a user opens your Web page and then leaves it open in another browser window, Woopra will report that the visitor has gone away after 4 minutes of inactivity on that page (no page scrolling, clicking or other action). If you would like to increase or decrease the idle timeout setting you can set :const:`WOOPRA_IDLE_TIMEOUT` to a time in milliseconds. For example, to set the default timout to 10 minutes:: WOOPRA_IDLE_TIMEOUT = 10 * 60 * 1000 Keep in mind that increasing this number will not only show you more visitors on your site at a time, but will also skew your average time on a page reporting. So it’s important to keep the number reasonable in order to accurately make predictions. 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:`WOOPRA_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. Custom data ----------- As described in the Woopra documentation on `custom visitor data`_, the data that is tracked by Woopra can be customized. Using template context variables, you can let the :ttag:`woopra` tag pass custom data to Woopra automatically. You can set the context variables in your view when your render a template containing the tracking code:: context = RequestContext({'woopra_cart_value': cart.total_price}) return some_template.render(context) For some data, it is annoying to do this for every view, so you may want to set variables in a context processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: from django.utils.hashcompat import md5_constructor as md5 GRAVATAR_URL = 'http://www.gravatar.com/avatar/' def woopra_custom_data(request): try: email = request.user.email except AttributeError: return {} email_hash = md5(email).hexdigest() avatar_url = "%s%s" % (GRAVATAR_URL, email_hash) return {'woopra_avatar': avatar_url} Just remember that if you set the same context variable in the :class:`~django.template.context.RequestContext` constructor and in a context processor, the latter clobbers the former. Standard variables that will be displayed in the Woopra live visitor data are listed in the table below, but you can define any ``woopra_*`` variable you like and have that detail passed from within the visitor live stream data when viewing Woopra. ==================== =================================== Context variable Description ==================== =================================== ``woopra_name`` The visitor's full name. -------------------- ----------------------------------- ``woopra_email`` The visitor's email address. -------------------- ----------------------------------- ``woopra_avatar`` A URL link to a visitor avatar. ==================== =================================== .. _`custom visitor data`: http://www.woopra.com/docs/tracking/custom-visitor-data/ Identifying authenticated users ------------------------------- If you have not set the ``woopra_name`` or ``woopra_email`` variables explicitly, the username and email address of an authenticated user are passed to Woopra automatically. See :ref:`identifying-visitors`. ---- Thanks go to Woopra for their support with the development of this application. django-analytical-3.0.0/docs/services/yandex_metrica.rst000066400000000000000000000060061376325370100234050ustar00rootroot00000000000000================================== Yandex.Metrica -- traffic analysis ================================== `Yandex.Metrica`_ is an analytics tool like as google analytics. .. _`Yandex.Metrica`: http://metrica.yandex.com/ .. yandex-metrica-installation: Installation ============ To start using the Yandex.Metrica 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 Yandex.Metrica 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:`yandex-metrica-configuration`. The Yandex.Metrica counter code is inserted into templates using a template tag. Load the :mod:`yandex_metrica` template tag library and insert the :ttag:`yandex_metrica` tag. Because every page that you want to track must have the tag, it is useful to add it to your base template. Insert the tag at the bottom of the HTML head:: {% load yandex_metrica %} ... {% yandex_metrica %} ... .. _yandex-metrica-configuration: Configuration ============= Before you can use the Yandex.Metrica integration, you must first set your website counter ID. .. _yandex-metrica-counter-id: Setting the counter ID ---------------------- Every website you track with Yandex.Metrica gets its own counter ID, and the :ttag:`yandex_metrica` tag will include it in the rendered Javascript code. You can find the web counter ID on the overview page of your account. Set :const:`YANDEX_METRICA_COUNTER_ID` in the project :file:`settings.py` file:: YANDEX_METRICA_COUNTER_ID = '12345678' If you do not set a counter ID, the counter code will not be rendered. You can set additional options to tune your counter: ============================ ============= ============================================= Constant Default Value Description ============================ ============= ============================================= ``YANDEX_METRICA_WEBVISOR`` False Webvisor, scroll map, form analysis. ``YANDEX_METRICA_TRACKHASH`` False Hash tracking in the browser address bar. ``YANDEX_METRICA_NOINDEX`` False Stop automatic page indexing. ``YANDEX_METRICA_ECOMMERCE`` False Dispatch ecommerce data to Metrica. ============================ ============= ============================================= 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:`YANDEX_METRICA_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. django-analytical-3.0.0/docs/settings.rst000066400000000000000000000013451376325370100204270ustar00rootroot00000000000000======== Settings ======== Here's a full list of all available settings, in alphabetical order, and their default values. .. data:: ANALYTICAL_AUTO_IDENTIFY Default: ``True`` Automatically identify logged in users by their username. See :ref:`identifying-visitors`. .. data:: ANALYTICAL_INTERNAL_IPS Default: :data:`INTERNAL_IPS` A list or tuple of internal IP addresses. Tracking code will be commented out for visitors from any of these addresses. You can configure this setting for each service individually by substituting ``ANALYTICAL`` for the upper-case service name. For example, set ``GOOGLE_ANALYTICS_INTERNAL_IPS`` to configure for Google Analytics. See :ref:`internal-ips`. django-analytical-3.0.0/docs/tutorial.rst000066400000000000000000000113021376325370100204240ustar00rootroot00000000000000.. _tutorial: ======== Tutorial ======== This tutorial will show you how to install and configure django-analytical for basic tracking, and then briefly touch on two common customization issues: visitor identification and custom data tracking. Suppose your Django website provides information about the IPv4 to IPv6 transition. Visitors can discuss their problems and help each other make the necessary changes to their network infrastructure. You want to use two different analytics services: * :doc:`Clicky ` for detailed traffic analysis * :doc:`Crazy Egg ` to see where visitors click on your pages At the end of this tutorial, the project will track visitors on both Clicky and Crazy Egg, identify authenticated users and add extra tracking data to segment mouse clicks on Crazy Egg based on whether visitors are using IPv4 or IPv6. Setting up basic tracking ========================= To get started with django-analytical, the package must first be installed. You can download and install the latest stable package from the Python Package Index automatically by using ``easy_install``: .. code-block:: bash $ easy_install django-analytical For more ways to install django-analytical, see :ref:`installing-the-package`. After you install django-analytical, you need to add it to the list of installed applications in the ``settings.py`` file of your project: .. code-block:: python INSTALLED_APPS = [ ... 'analytical', ... ] Then you have to add the general-purpose django-analytical template tags to your base template: .. code-block:: django {% load analytical %} {% analytical_head_top %} ... {% analytical_head_bottom %} {% analytical_body_top %} ... {% analytical_body_bottom %} Finally, you need to configure the Clicky Site ID and the Crazy Egg account number. Add the following to your project :file:`settings.py` file (replacing the ``x``'s with your own codes): .. code-block:: python CLICKY_SITE_ID = 'xxxxxxxx' CRAZY_EGG_ACCOUNT_NUMBER = 'xxxxxxxx' The analytics services are now installed. If you run Django with these changes, both Clicky and Crazy Egg will be tracking your visitors. Identifying authenticated users =============================== Suppose that when your visitors post questions on IPv6 or tell others about their experience with the transition, they first log in through the standard Django authentication system. Clicky can identify and track individual visitors and you want to use this feature. If django-analytical template tags detect that the current user is authenticated, they will automatically include code to send the username to services that support this feature. This only works if the template context has the current user in the ``user`` or ``request.user`` context variable. If you use a :class:`~django.template.RequestContext` to render templates (which is recommended anyway) and have the :class:`django.contrib.auth.context_processors.auth` context processor in the :data:`TEMPLATE_CONTEXT_PROCESSORS` setting (which is default), then this identification works without having to make any changes. For more detailed information on automatic identification, and how to disable or override it, see :ref:`identifying-visitors`. Adding custom tracking data =========================== Suppose that you think that visitors who already have IPv6 use the website in a different way from those still on IPv4. You want to test this hypothesis by segmenting the Crazy Egg heatmaps based on the IP protocol version. In order to filter on protocol version in Crazy Egg, you need to include the visitor IP protocol version in the Crazy Egg tracking code. The easiest way to do this is by using a context processor: .. code-block:: python def track_ip_proto(request): addr = request.META.get('HTTP_X_FORWARDED_FOR', '') if not addr: addr = request.META.get('REMOTE_ADDR', '') if ':' in addr: proto = 'ipv6' else: proto = 'ipv4' # assume IPv4 if no information return {'crazy_egg_var1': proto} Use a :class:`~django.template.RequestContext` when rendering templates and add the ``'track_ip_proto'`` to :data:`TEMPLATE_CONTEXT_PROCESSORS`. In Crazy Egg, you can now select *User Var1* in the overlay or confetti views to see whether visitors using IPv4 behave differently from those using IPv6. ---- This concludes the tutorial. For information about setting up, configuring and customizing the different analytics services, see :doc:`features` and :doc:`services`. django-analytical-3.0.0/requirements.txt000066400000000000000000000000161376325370100203630ustar00rootroot00000000000000Django>=2.2.* django-analytical-3.0.0/setup.cfg000066400000000000000000000002711376325370100167230ustar00rootroot00000000000000[build_sphinx] source-dir = docs build-dir = build/docs all_files = 1 [upload_sphinx] upload-dir = build/docs/html [tool:pytest] DJANGO_SETTINGS_MODULE = tests.testproject.settings django-analytical-3.0.0/setup.py000066400000000000000000000032541376325370100166200ustar00rootroot00000000000000import os from setuptools import setup import analytical as package def read_file(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name='django-analytical', version=package.__version__, license=package.__license__, description=package.__doc__.strip(), long_description=read_file('README.rst'), long_description_content_type='text/x-rst', author=package.__author__, author_email=package.__email__, packages=[ 'analytical', 'analytical.templatetags', ], keywords=[ 'django', 'analytics', ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries :: Python Modules', ], python_requires='>=3.6', platforms=['any'], url='https://github.com/jazzband/django-analytical', download_url='https://github.com/jazzband/django-analytical/archive/master.zip', project_urls={ 'Documentation': 'https://django-analytical.readthedocs.io/', }, ) django-analytical-3.0.0/tests/000077500000000000000000000000001376325370100162445ustar00rootroot00000000000000django-analytical-3.0.0/tests/testproject/000077500000000000000000000000001376325370100206125ustar00rootroot00000000000000django-analytical-3.0.0/tests/testproject/settings.py000066400000000000000000000010641376325370100230250ustar00rootroot00000000000000""" django-analytical testing settings. """ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', } } INSTALLED_APPS = [ 'django.contrib.sites', 'django.contrib.contenttypes', 'django.contrib.auth', 'analytical', ] SECRET_KEY = 'testing' MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', ) TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, }, ] django-analytical-3.0.0/tests/testproject/templatetags/000077500000000000000000000000001376325370100233045ustar00rootroot00000000000000django-analytical-3.0.0/tests/testproject/templatetags/__init__.py000066400000000000000000000000001376325370100254030ustar00rootroot00000000000000django-analytical-3.0.0/tests/testproject/templatetags/dummy.py000066400000000000000000000016371376325370100250200ustar00rootroot00000000000000""" Dummy testing template tags and filters. """ from django.template import Library, Node, TemplateSyntaxError from analytical.templatetags.analytical import TAG_LOCATIONS register = Library() def _location_node(location): class DummyNode(Node): def render(self, context): return "" % location return DummyNode _location_nodes = {loc: _location_node(loc) for loc in TAG_LOCATIONS} def _location_tag(location): def dummy_tag(parser, token): bits = token.split_contents() if len(bits) > 1: raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0]) return _location_nodes[location] return dummy_tag for loc in TAG_LOCATIONS: register.tag('dummy_%s' % loc, _location_tag(loc)) def contribute_to_analytical(add_node_cls): for location in TAG_LOCATIONS: add_node_cls(location, _location_nodes[location]) django-analytical-3.0.0/tests/unit/000077500000000000000000000000001376325370100172235ustar00rootroot00000000000000django-analytical-3.0.0/tests/unit/test_tag_analytical.py000066400000000000000000000021431376325370100236100ustar00rootroot00000000000000""" Tests for the generic template tags and filters. """ from django.template import Context, Template from analytical.templatetags import analytical from utils import TagTestCase class AnalyticsTagTestCase(TagTestCase): """ Tests for the ``analytical`` template tags. """ def setUp(self): super().setUp() self._tag_modules = analytical.TAG_MODULES analytical.TAG_MODULES = ['tests.testproject.dummy'] analytical.template_nodes = analytical._load_template_nodes() def tearDown(self): analytical.TAG_MODULES = self._tag_modules analytical.template_nodes = analytical._load_template_nodes() super().tearDown() def render_location_tag(self, location, vars=None): if vars is None: vars = {} t = Template("{%% load analytical %%}{%% analytical_%s %%}" % location) return t.render(Context(vars)) def test_location_tags(self): for loc in ['head_top', 'head_bottom', 'body_top', 'body_bottom']: r = self.render_location_tag(loc) self.assertTrue('dummy_%s' % loc in r, r) django-analytical-3.0.0/tests/unit/test_tag_chartbeat.py000066400000000000000000000073101376325370100234250ustar00rootroot00000000000000""" Tests for the Chartbeat template tags and filters. """ import re from django.http import HttpRequest from django.template import Context from django.test import TestCase from django.test.utils import override_settings from analytical.templatetags.chartbeat import ChartbeatTopNode, \ ChartbeatBottomNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(CHARTBEAT_USER_ID='12345') class ChartbeatTagTestCaseNoSites(TestCase): def test_rendering_setup_no_site(self): r = ChartbeatBottomNode().render(Context()) self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r) @override_settings(INSTALLED_APPS=( 'analytical', 'django.contrib.sites', 'django.contrib.auth', 'django.contrib.contenttypes', )) @override_settings(CHARTBEAT_USER_ID='12345') class ChartbeatTagTestCaseWithSites(TestCase): def setUp(self): from django.core.management import call_command call_command("migrate", verbosity=0) def test_rendering_setup_site(self): from django.contrib.sites.models import Site site = Site.objects.create(domain="test.com", name="test") with override_settings(SITE_ID=site.id): r = ChartbeatBottomNode().render(Context()) self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r) self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r) @override_settings(CHARTBEAT_AUTO_DOMAIN=False) def test_auto_domain_false(self): """ Even if 'django.contrib.sites' is in INSTALLED_APPS, if CHARTBEAT_AUTO_DOMAIN is False, ensure there is no 'domain' in _sf_async_config. """ r = ChartbeatBottomNode().render(Context()) self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r) @override_settings(CHARTBEAT_USER_ID='12345') class ChartbeatTagTestCase(TagTestCase): """ Tests for the ``chartbeat`` template tag. """ def test_top_tag(self): r = self.render_tag('chartbeat', 'chartbeat_top', {'chartbeat_domain': "test.com"}) self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r) def test_bottom_tag(self): r = self.render_tag('chartbeat', 'chartbeat_bottom', {'chartbeat_domain': "test.com"}) self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r) self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r) def test_top_node(self): r = ChartbeatTopNode().render(Context({ 'chartbeat_domain': "test.com", })) self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r) def test_bottom_node(self): r = ChartbeatBottomNode().render(Context({ 'chartbeat_domain': "test.com", })) self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r) self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r) @override_settings(CHARTBEAT_USER_ID=None) def test_no_user_id(self): self.assertRaises(AnalyticalException, ChartbeatBottomNode) @override_settings(CHARTBEAT_USER_ID='123abc') def test_wrong_user_id(self): self.assertRaises(AnalyticalException, ChartbeatBottomNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = ChartbeatBottomNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_clickmap.py000066400000000000000000000026661376325370100232640ustar00rootroot00000000000000""" Tests for the Clickmap template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.clickmap import ClickmapNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(CLICKMAP_TRACKER_ID='12345ABC') class ClickmapTagTestCase(TagTestCase): """ Tests for the ``clickmap`` template tag. """ def test_tag(self): r = self.render_tag('clickmap', 'clickmap') self.assertTrue("tracker: '12345ABC', version:'2'};" in r, r) def test_node(self): r = ClickmapNode().render(Context({})) self.assertTrue("tracker: '12345ABC', version:'2'};" in r, r) @override_settings(CLICKMAP_TRACKER_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, ClickmapNode) @override_settings(CLICKMAP_TRACKER_ID='ab#c') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, ClickmapNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = ClickmapNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_clicky.py000066400000000000000000000045571376325370100227600ustar00rootroot00000000000000""" Tests for the Clicky template tags and filters. """ import re from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.clicky import ClickyNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(CLICKY_SITE_ID='12345678') class ClickyTagTestCase(TagTestCase): """ Tests for the ``clicky`` template tag. """ def test_tag(self): r = self.render_tag('clicky', 'clicky') self.assertTrue('clicky_site_ids.push(12345678);' in r, r) self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r, r) def test_node(self): r = ClickyNode().render(Context({})) self.assertTrue('clicky_site_ids.push(12345678);' in r, r) self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r, r) @override_settings(CLICKY_SITE_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, ClickyNode) @override_settings(CLICKY_SITE_ID='123abc') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, ClickyNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = ClickyNode().render(Context({'user': User(username='test')})) self.assertTrue('var clicky_custom = {"session": {"username": "test"}};' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = ClickyNode().render(Context({'user': AnonymousUser()})) self.assertFalse('var clicky_custom = {"session": {"username":' in r, r) def test_custom(self): r = ClickyNode().render(Context({ 'clicky_var1': 'val1', 'clicky_var2': 'val2', })) self.assertTrue( re.search(r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r), r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = ClickyNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_crazy_egg.py000066400000000000000000000032621376325370100234440ustar00rootroot00000000000000""" Tests for the Crazy Egg template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.crazy_egg import CrazyEggNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(CRAZY_EGG_ACCOUNT_NUMBER='12345678') class CrazyEggTagTestCase(TagTestCase): """ Tests for the ``crazy_egg`` template tag. """ def test_tag(self): r = self.render_tag('crazy_egg', 'crazy_egg') self.assertTrue('/1234/5678.js' in r, r) def test_node(self): r = CrazyEggNode().render(Context()) self.assertTrue('/1234/5678.js' in r, r) @override_settings(CRAZY_EGG_ACCOUNT_NUMBER=None) def test_no_account_number(self): self.assertRaises(AnalyticalException, CrazyEggNode) @override_settings(CRAZY_EGG_ACCOUNT_NUMBER='123abc') def test_wrong_account_number(self): self.assertRaises(AnalyticalException, CrazyEggNode) def test_uservars(self): context = Context({'crazy_egg_var1': 'foo', 'crazy_egg_var2': 'bar'}) r = CrazyEggNode().render(context) self.assertTrue("CE2.set(1, 'foo');" in r, r) self.assertTrue("CE2.set(2, 'bar');" in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = CrazyEggNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_facebook_pixel.py000066400000000000000000000102011376325370100244330ustar00rootroot00000000000000""" Tests for the Facebook Pixel template tags. """ from django.http import HttpRequest from django.template import Context, Template, TemplateSyntaxError from django.test import override_settings from analytical.templatetags.analytical import _load_template_nodes from analytical.templatetags.facebook_pixel import FacebookPixelHeadNode, FacebookPixelBodyNode from utils import TagTestCase from analytical.utils import AnalyticalException expected_head_html = """\ """ expected_body_html = """\ """ @override_settings(FACEBOOK_PIXEL_ID='1234567890') class FacebookPixelTagTestCase(TagTestCase): maxDiff = None def test_head_tag(self): html = self.render_tag('facebook_pixel', 'facebook_pixel_head') self.assertEqual(expected_head_html, html) def test_head_node(self): html = FacebookPixelHeadNode().render(Context({})) self.assertEqual(expected_head_html, html) def test_body_tag(self): html = self.render_tag('facebook_pixel', 'facebook_pixel_body') self.assertEqual(expected_body_html, html) def test_body_node(self): html = FacebookPixelBodyNode().render(Context({})) self.assertEqual(expected_body_html, html) def test_tags_take_no_args(self): self.assertRaisesRegex( TemplateSyntaxError, r"^'facebook_pixel_head' takes no arguments$", lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_head "arg" %}') .render(Context({}))), ) self.assertRaisesRegex( TemplateSyntaxError, r"^'facebook_pixel_body' takes no arguments$", lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_body "arg" %}') .render(Context({}))), ) @override_settings(FACEBOOK_PIXEL_ID=None) def test_no_id(self): expected_pattern = r'^FACEBOOK_PIXEL_ID setting is not set$' self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelHeadNode) self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelBodyNode) @override_settings(FACEBOOK_PIXEL_ID='invalid') def test_invalid_id(self): expected_pattern = ( r"^FACEBOOK_PIXEL_ID setting: must be \(a string containing\) a number: 'invalid'$") self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelHeadNode) self.assertRaisesRegex(AnalyticalException, expected_pattern, FacebookPixelBodyNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): request = HttpRequest() request.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': request}) def _disabled(html): return '\n'.join([ '', ]) head_html = FacebookPixelHeadNode().render(context) self.assertEqual(_disabled(expected_head_html), head_html) body_html = FacebookPixelBodyNode().render(context) self.assertEqual(_disabled(expected_body_html), body_html) def test_contribute_to_analytical(self): """ `facebook_pixel.contribute_to_analytical` registers the head and body nodes. """ template_nodes = _load_template_nodes() self.assertEqual({ 'head_top': [], 'head_bottom': [FacebookPixelHeadNode], 'body_top': [], 'body_bottom': [FacebookPixelBodyNode], }, template_nodes) django-analytical-3.0.0/tests/unit/test_tag_gauges.py000066400000000000000000000044461376325370100227520ustar00rootroot00000000000000""" Tests for the Gauges template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.gauges import GaugesNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(GAUGES_SITE_ID='1234567890abcdef0123456789') class GaugesTagTestCase(TagTestCase): """ Tests for the ``gauges`` template tag. """ def test_tag(self): self.assertEqual(""" """, self.render_tag('gauges', 'gauges')) def test_node(self): self.assertEqual( """ """, GaugesNode().render(Context())) @override_settings(GAUGES_SITE_ID=None) def test_no_account_number(self): self.assertRaises(AnalyticalException, GaugesNode) @override_settings(GAUGES_SITE_ID='123abQ') def test_wrong_account_number(self): self.assertRaises(AnalyticalException, GaugesNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GaugesNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_google_analytics.py000066400000000000000000000207031376325370100250140ustar00rootroot00000000000000""" Tests for the Google Analytics template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.google_analytics import GoogleAnalyticsNode, \ TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS,\ SCOPE_VISITOR, SCOPE_SESSION, SCOPE_PAGE from utils import TestCase, TagTestCase from analytical.utils import AnalyticalException @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN) class GoogleAnalyticsTagTestCase(TagTestCase): """ Tests for the ``google_analytics`` template tag. """ def test_tag(self): r = self.render_tag('google_analytics', 'google_analytics') self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r) self.assertTrue("_gaq.push(['_trackPageview']);" in r, r) def test_node(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r) self.assertTrue("_gaq.push(['_trackPageview']);" in r, r) @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID=None) def test_no_property_id(self): self.assertRaises(AnalyticalException, GoogleAnalyticsNode) @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='wrong') def test_wrong_property_id(self): self.assertRaises(AnalyticalException, GoogleAnalyticsNode) @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_subdomains(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r) self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r) @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_domains(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r) self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r) self.assertTrue("_gaq.push(['_setAllowLinker', true]);" in r, r) def test_custom_vars(self): context = Context({ 'google_analytics_var1': ('test1', 'foo'), 'google_analytics_var2': ('test2', 'bar', SCOPE_VISITOR), 'google_analytics_var4': ('test4', 'baz', SCOPE_SESSION), 'google_analytics_var5': ('test5', 'qux', SCOPE_PAGE), }) r = GoogleAnalyticsNode().render(context) self.assertTrue("_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);" in r, r) self.assertTrue("_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);" in r, r) self.assertTrue("_gaq.push(['_setCustomVar', 4, 'test4', 'baz', 2]);" in r, r) self.assertTrue("_gaq.push(['_setCustomVar', 5, 'test5', 'qux', 3]);" in r, r) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED=True) def test_track_page_load_time(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_trackPageLoadTime']);" in r, r) def test_display_advertising(self): with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=False): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("google-analytics.com/ga.js" in r, r) with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("stats.g.doubleclick.net/dc.js" in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsNode().render(context) self.assertTrue(r.startswith( ''), r) @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True) def test_anonymize_ip(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_gat._anonymizeIp']);" in r, r) self.assertTrue(r.index('_gat._anonymizeIp') < r.index('_trackPageview'), r) @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=False) def test_anonymize_ip_not_present(self): r = GoogleAnalyticsNode().render(Context()) self.assertFalse("_gaq.push(['_gat._anonymizeIp']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=0.0) def test_set_sample_rate_min(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setSampleRate', '0.00']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE='100.00') def test_set_sample_rate_max(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setSampleRate', '100.00']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=-1) def test_exception_whenset_sample_rate_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=101) def test_exception_when_set_sample_rate_too_large(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0) def test_set_site_speed_sample_rate_min(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setSiteSpeedSampleRate', '0.00']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00') def test_set_site_speed_sample_rate_max(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setSiteSpeedSampleRate', '100.00']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1) def test_exception_whenset_site_speed_sample_rate_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=101) def test_exception_when_set_site_speed_sample_rate_too_large(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT=0) def test_set_session_cookie_timeout_min(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setSessionCookieTimeout', '0']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT='10000') def test_set_session_cookie_timeout_as_string(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setSessionCookieTimeout', '10000']);" in r, r) @override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT=-1) def test_exception_when_set_session_cookie_timeout_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) @override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT=0) def test_set_visitor_cookie_timeout_min(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setVisitorCookieTimeout', '0']);" in r, r) @override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT='10000') def test_set_visitor_cookie_timeout_as_string(self): r = GoogleAnalyticsNode().render(Context()) self.assertTrue("_gaq.push(['_setVisitorCookieTimeout', '10000']);" in r, r) @override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT=-1) def test_exception_when_set_visitor_cookie_timeout_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) @override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7', GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, GOOGLE_ANALYTICS_DOMAIN=None, ANALYTICAL_DOMAIN=None) class NoDomainTestCase(TestCase): def test_exception_without_domain(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context) django-analytical-3.0.0/tests/unit/test_tag_google_analytics_gtag.py000066400000000000000000000073461376325370100260260ustar00rootroot00000000000000""" Tests for the Google Analytics template tags and filters, using the new gtag.js library. """ from django.contrib.auth.models import User from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.google_analytics_gtag import GoogleAnalyticsGTagNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='UA-123456-7') class GoogleAnalyticsTagTestCase(TagTestCase): """ Tests for the ``google_analytics_js`` template tag. """ def test_tag(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( '' in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) def test_node(self): r = GoogleAnalyticsGTagNode().render(Context()) self.assertTrue( '' in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'UA-123456-7');" in r, r) @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID=None) def test_no_property_id(self): self.assertRaises(AnalyticalException, GoogleAnalyticsGTagNode) @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='wrong') def test_wrong_property_id(self): self.assertRaises(AnalyticalException, GoogleAnalyticsGTagNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsGTagNode().render(context) self.assertTrue(r.startswith( ''), r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = GoogleAnalyticsGTagNode().render(Context({'user': User(username='test')})) self.assertTrue("gtag('set', {'user_id': 'test'});" in r, r) @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='G-12345678') def test_tag_with_measurement_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'G-12345678');" in r, r) @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='AW-1234567890') def test_tag_with_conversion_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'AW-1234567890');" in r, r) @override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='DC-12345678') def test_tag_with_advertiser_id(self): r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag') self.assertTrue( ('') in r, r) self.assertTrue("gtag('js', new Date());" in r, r) self.assertTrue("gtag('config', 'DC-12345678');" in r, r) django-analytical-3.0.0/tests/unit/test_tag_google_analytics_js.py000066400000000000000000000176451376325370100255230ustar00rootroot00000000000000""" Tests for the Google Analytics template tags and filters, using the new analytics.js library. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.google_analytics_js import GoogleAnalyticsJsNode, \ TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS from utils import TestCase, TagTestCase from analytical.utils import AnalyticalException @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN) class GoogleAnalyticsTagTestCase(TagTestCase): """ Tests for the ``google_analytics_js`` template tag. """ def test_tag(self): r = self.render_tag('google_analytics_js', 'google_analytics_js') self.assertTrue("""(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r, r) self.assertTrue("ga('create', 'UA-123456-7', 'auto', {});" in r, r) self.assertTrue("ga('send', 'pageview');" in r, r) def test_node(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("""(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r, r) self.assertTrue("ga('create', 'UA-123456-7', 'auto', {});" in r, r) self.assertTrue("ga('send', 'pageview');" in r, r) @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID=None) def test_no_property_id(self): self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode) @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='wrong') def test_wrong_property_id(self): self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode) @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_subdomains(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue( """ga('create', 'UA-123456-7', 'auto', {"legacyCookieDomain": "example.com"}""" in r, r) @override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, GOOGLE_ANALYTICS_DOMAIN='example.com') def test_track_multiple_domains(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("ga('create', 'UA-123456-7', 'auto', {" in r, r) self.assertTrue('"legacyCookieDomain": "example.com"' in r, r) self.assertTrue('"allowLinker\": true' in r, r) def test_custom_vars(self): context = Context({ 'google_analytics_var1': ('test1', 'foo'), 'google_analytics_var2': ('test2', 'bar'), 'google_analytics_var4': ('test4', 1), 'google_analytics_var5': ('test5', 2.2), }) r = GoogleAnalyticsJsNode().render(context) self.assertTrue("ga('set', 'test1', 'foo');" in r, r) self.assertTrue("ga('set', 'test2', 'bar');" in r, r) self.assertTrue("ga('set', 'test4', 1);" in r, r) self.assertTrue("ga('set', 'test5', 2.2);" in r, r) def test_display_advertising(self): with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {}); ga('require', 'displayfeatures'); ga('send', 'pageview');""" in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoogleAnalyticsJsNode().render(context) self.assertTrue(r.startswith( ''), r) @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True) def test_anonymize_ip(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("ga('set', 'anonymizeIp', true);" in r, r) @override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=False) def test_anonymize_ip_not_present(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertFalse("ga('set', 'anonymizeIp', true);" in r, r) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=0.0) def test_set_sample_rate_min(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"sampleRate": 0});""" in r, r) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE='100.00') def test_set_sample_rate_max(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"sampleRate": 100});""" in r, r) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=-1) def test_exception_whenset_sample_rate_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=101) def test_exception_when_set_sample_rate_too_large(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0) def test_set_site_speed_sample_rate_min(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue( """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 0});""" in r, r) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00') def test_set_site_speed_sample_rate_max(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue( """ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 100});""" in r, r) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1) def test_exception_whenset_site_speed_sample_rate_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) @override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=101) def test_exception_when_set_site_speed_sample_rate_too_large(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) @override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION=0) def test_set_cookie_expiration_min(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 0});""" in r, r) @override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION='10000') def test_set_cookie_expiration_as_string(self): r = GoogleAnalyticsJsNode().render(Context()) self.assertTrue( """ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 10000});""" in r, r) @override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION=-1) def test_exception_when_set_cookie_expiration_too_small(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) @override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7', GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS, GOOGLE_ANALYTICS_DOMAIN=None, ANALYTICAL_DOMAIN=None) class NoDomainTestCase(TestCase): def test_exception_without_domain(self): context = Context() self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context) django-analytical-3.0.0/tests/unit/test_tag_gosquared.py000066400000000000000000000045661376325370100234740ustar00rootroot00000000000000""" Tests for the GoSquared template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.gosquared import GoSquaredNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(GOSQUARED_SITE_TOKEN='ABC-123456-D') class GoSquaredTagTestCase(TagTestCase): """ Tests for the ``gosquared`` template tag. """ def test_tag(self): r = self.render_tag('gosquared', 'gosquared') self.assertTrue('GoSquared.acct = "ABC-123456-D";' in r, r) def test_node(self): r = GoSquaredNode().render(Context({})) self.assertTrue('GoSquared.acct = "ABC-123456-D";' in r, r) @override_settings(GOSQUARED_SITE_TOKEN=None) def test_no_token(self): self.assertRaises(AnalyticalException, GoSquaredNode) @override_settings(GOSQUARED_SITE_TOKEN='this is not a token') def test_wrong_token(self): self.assertRaises(AnalyticalException, GoSquaredNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_auto_identify(self): r = GoSquaredNode().render(Context({ 'user': User(username='test', first_name='Test', last_name='User'), })) self.assertTrue('GoSquared.UserName = "Test User";' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_manual_identify(self): r = GoSquaredNode().render(Context({ 'user': User(username='test', first_name='Test', last_name='User'), 'gosquared_identity': 'test_identity', })) self.assertTrue('GoSquared.UserName = "test_identity";' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = GoSquaredNode().render(Context({'user': AnonymousUser()})) self.assertFalse('GoSquared.UserName = ' in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = GoSquaredNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_hotjar.py000066400000000000000000000053611376325370100227630ustar00rootroot00000000000000""" Tests for the Hotjar template tags. """ from django.http import HttpRequest from django.template import Context, Template, TemplateSyntaxError from django.test import override_settings from analytical.templatetags.analytical import _load_template_nodes from analytical.templatetags.hotjar import HotjarNode from utils import TagTestCase from analytical.utils import AnalyticalException expected_html = """\ """ @override_settings(HOTJAR_SITE_ID='123456789') class HotjarTagTestCase(TagTestCase): maxDiff = None def test_tag(self): html = self.render_tag('hotjar', 'hotjar') self.assertEqual(expected_html, html) def test_node(self): html = HotjarNode().render(Context({})) self.assertEqual(expected_html, html) def test_tags_take_no_args(self): self.assertRaisesRegex( TemplateSyntaxError, r"^'hotjar' takes no arguments$", lambda: (Template('{% load hotjar %}{% hotjar "arg" %}') .render(Context({}))), ) @override_settings(HOTJAR_SITE_ID=None) def test_no_id(self): expected_pattern = r'^HOTJAR_SITE_ID setting is not set$' self.assertRaisesRegex(AnalyticalException, expected_pattern, HotjarNode) @override_settings(HOTJAR_SITE_ID='invalid') def test_invalid_id(self): expected_pattern = ( r"^HOTJAR_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") self.assertRaisesRegex(AnalyticalException, expected_pattern, HotjarNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): request = HttpRequest() request.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': request}) actual_html = HotjarNode().render(context) disabled_html = '\n'.join([ '', ]) self.assertEqual(disabled_html, actual_html) def test_contribute_to_analytical(self): """ `hotjar.contribute_to_analytical` registers the head and body nodes. """ template_nodes = _load_template_nodes() self.assertEqual({ 'head_top': [], 'head_bottom': [HotjarNode], 'body_top': [], 'body_bottom': [], }, template_nodes) django-analytical-3.0.0/tests/unit/test_tag_hubspot.py000066400000000000000000000030621376325370100231540ustar00rootroot00000000000000""" Tests for the HubSpot template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.hubspot import HubSpotNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(HUBSPOT_PORTAL_ID='1234') class HubSpotTagTestCase(TagTestCase): """ Tests for the ``hubspot`` template tag. """ def test_tag(self): r = self.render_tag('hubspot', 'hubspot') self.assertTrue("n.id=i;n.src='//js.hs-analytics.net/analytics/'" "+(Math.ceil(new Date()/r)*r)+'/1234.js';" in r, r) def test_node(self): r = HubSpotNode().render(Context()) self.assertTrue("n.id=i;n.src='//js.hs-analytics.net/analytics/'" "+(Math.ceil(new Date()/r)*r)+'/1234.js';" in r, r) @override_settings(HUBSPOT_PORTAL_ID=None) def test_no_portal_id(self): self.assertRaises(AnalyticalException, HubSpotNode) @override_settings(HUBSPOT_PORTAL_ID='wrong') def test_wrong_portal_id(self): self.assertRaises(AnalyticalException, HubSpotNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = HubSpotNode().render(context) self.assertTrue(r.startswith(''), r) django-analytical-3.0.0/tests/unit/test_tag_intercom.py000066400000000000000000000153041376325370100233120ustar00rootroot00000000000000""" Tests for the intercom template tags and filters. """ import datetime from django.contrib.auth.models import User from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.intercom import IntercomNode, intercom_user_hash from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(INTERCOM_APP_ID="abc123xyz") class IntercomTagTestCase(TagTestCase): """ Tests for the ``intercom`` template tag. """ def test_tag(self): rendered_tag = self.render_tag('intercom', 'intercom') self.assertTrue(rendered_tag.strip().startswith(' """ % {'user_id': user.pk}, rendered_tag) # noqa @override_settings(INTERCOM_APP_ID=None) def test_no_account_number(self): self.assertRaises(AnalyticalException, IntercomNode) @override_settings(INTERCOM_APP_ID='123abQ') def test_wrong_account_number(self): self.assertRaises(AnalyticalException, IntercomNode) def test_identify_name_email_and_created_at(self): now = datetime.datetime(2014, 4, 9, 15, 15, 0) user = User.objects.create( username='test', first_name='Firstname', last_name='Lastname', email="test@example.com", date_joined=now, ) r = IntercomNode().render(Context({ 'user': user, })) self.assertTrue('window.intercomSettings = {' '"app_id": "abc123xyz", "created_at": 1397074500, ' '"email": "test@example.com", "name": "Firstname Lastname", ' '"user_id": %(user_id)s' '};' % {'user_id': user.pk} in r, msg=r) def test_custom(self): r = IntercomNode().render(Context({ 'intercom_var1': 'val1', 'intercom_var2': 'val2' })) self.assertTrue('var1": "val1", "var2": "val2"' in r) def test_identify_name_and_email(self): r = IntercomNode().render(Context({ 'user': User( username='test', first_name='Firstname', last_name='Lastname', email="test@example.com"), })) self.assertTrue('"email": "test@example.com", "name": "Firstname Lastname"' in r) def test_identify_username_no_email(self): r = IntercomNode().render(Context({'user': User(username='test')})) self.assertTrue('"name": "test"' in r, r) def test_no_identify_when_explicit_name(self): r = IntercomNode().render(Context({ 'intercom_name': 'explicit', 'user': User(username='implicit'), })) self.assertTrue('"name": "explicit"' in r, r) def test_no_identify_when_explicit_email(self): r = IntercomNode().render(Context({ 'intercom_email': 'explicit', 'user': User(username='implicit'), })) self.assertTrue('"email": "explicit"' in r, r) @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__without_user_details(self): """ No `user_hash` without `user_id` or `email`. """ attrs = IntercomNode()._get_custom_attrs(Context()) self.assertEqual({ 'created_at': None, }, attrs) @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__with_user(self): """ 'user_hash' of default `user_id`. """ user = User.objects.create( email='test@example.com', ) # type: User attrs = IntercomNode()._get_custom_attrs(Context({'user': user})) self.assertEqual({ 'created_at': int(user.date_joined.timestamp()), 'email': 'test@example.com', 'name': '', 'user_hash': intercom_user_hash(str(user.pk)), 'user_id': user.pk, }, attrs) @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__with_explicit_user_id(self): """ 'user_hash' of context-provided `user_id`. """ attrs = IntercomNode()._get_custom_attrs(Context({ 'intercom_email': 'test@example.com', 'intercom_user_id': '5', })) self.assertEqual({ 'created_at': None, 'email': 'test@example.com', # HMAC for user_id: 'user_hash': 'd3123a7052b42272d9b520235008c248a5aff3221cc0c530b754702ad91ab102', 'user_id': '5', }, attrs) @override_settings(INTERCOM_HMAC_SECRET_KEY='secret') def test_user_hash__with_explicit_email(self): """ 'user_hash' of context-provided `email`. """ attrs = IntercomNode()._get_custom_attrs(Context({ 'intercom_email': 'test@example.com', })) self.assertEqual({ 'created_at': None, 'email': 'test@example.com', # HMAC for email: 'user_hash': '49e43229ee99dca2565241719b8341b04e71dd4de0628f991b5bea30a526e153', }, attrs) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = IntercomNode().render(context) self.assertTrue(r.startswith(''), r) django-analytical-3.0.0/tests/unit/test_tag_kiss_insights.py000066400000000000000000000041471376325370100243560ustar00rootroot00000000000000""" Tests for the KISSinsights template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.template import Context from django.test.utils import override_settings from analytical.templatetags.kiss_insights import KissInsightsNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER='12345', KISS_INSIGHTS_SITE_CODE='abc') class KissInsightsTagTestCase(TagTestCase): """ Tests for the ``kiss_insights`` template tag. """ def test_tag(self): r = self.render_tag('kiss_insights', 'kiss_insights') self.assertTrue("//s3.amazonaws.com/ki.js/12345/abc.js" in r, r) def test_node(self): r = KissInsightsNode().render(Context()) self.assertTrue("//s3.amazonaws.com/ki.js/12345/abc.js" in r, r) @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER=None) def test_no_account_number(self): self.assertRaises(AnalyticalException, KissInsightsNode) @override_settings(KISS_INSIGHTS_SITE_CODE=None) def test_no_site_code(self): self.assertRaises(AnalyticalException, KissInsightsNode) @override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER='abcde') def test_wrong_account_number(self): self.assertRaises(AnalyticalException, KissInsightsNode) @override_settings(KISS_INSIGHTS_SITE_CODE='abc def') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, KissInsightsNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = KissInsightsNode().render(Context({'user': User(username='test')})) self.assertTrue("_kiq.push(['identify', 'test']);" in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = KissInsightsNode().render(Context({'user': AnonymousUser()})) self.assertFalse("_kiq.push(['identify', " in r, r) def test_show_survey(self): r = KissInsightsNode().render(Context({'kiss_insights_show_survey': 1234})) self.assertTrue("_kiq.push(['showSurvey', 1234]);" in r, r) django-analytical-3.0.0/tests/unit/test_tag_kiss_metrics.py000066400000000000000000000062361376325370100241750ustar00rootroot00000000000000""" Tests for the KISSmetrics tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.kiss_metrics import KissMetricsNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef01234567') class KissMetricsTagTestCase(TagTestCase): """ Tests for the ``kiss_metrics`` template tag. """ def test_tag(self): r = self.render_tag('kiss_metrics', 'kiss_metrics') self.assertTrue("//doug1izaerwt3.cloudfront.net/" "0123456789abcdef0123456789abcdef01234567.1.js" in r, r) def test_node(self): r = KissMetricsNode().render(Context()) self.assertTrue("//doug1izaerwt3.cloudfront.net/" "0123456789abcdef0123456789abcdef01234567.1.js" in r, r) @override_settings(KISS_METRICS_API_KEY=None) def test_no_api_key(self): self.assertRaises(AnalyticalException, KissMetricsNode) @override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef0123456') def test_api_key_too_short(self): self.assertRaises(AnalyticalException, KissMetricsNode) @override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef012345678') def test_api_key_too_long(self): self.assertRaises(AnalyticalException, KissMetricsNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = KissMetricsNode().render(Context({'user': User(username='test')})) self.assertTrue("_kmq.push(['identify', 'test']);" in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = KissMetricsNode().render(Context({'user': AnonymousUser()})) self.assertFalse("_kmq.push(['identify', " in r, r) def test_event(self): r = KissMetricsNode().render(Context({ 'kiss_metrics_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}), })) self.assertTrue("_kmq.push(['record', 'test_event', " '{"prop1": "val1", "prop2": "val2"}]);' in r, r) def test_property(self): r = KissMetricsNode().render(Context({ 'kiss_metrics_properties': {'prop1': 'val1', 'prop2': 'val2'}, })) self.assertTrue("_kmq.push([\'set\', " '{"prop1": "val1", "prop2": "val2"}]);' in r, r) def test_alias(self): r = KissMetricsNode().render(Context({ 'kiss_metrics_alias': {'test': 'test_alias'}, })) self.assertTrue("_kmq.push(['alias', 'test', 'test_alias']);" in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = KissMetricsNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_luckyorange.py000066400000000000000000000054551376325370100240230ustar00rootroot00000000000000""" Tests for the Lucky Orange template tags. """ from django.http import HttpRequest from django.template import Context, Template, TemplateSyntaxError from django.test import override_settings from analytical.templatetags.analytical import _load_template_nodes from analytical.templatetags.luckyorange import LuckyOrangeNode from utils import TagTestCase from analytical.utils import AnalyticalException expected_html = """\ """ @override_settings(LUCKYORANGE_SITE_ID='123456') class LuckyOrangeTagTestCase(TagTestCase): maxDiff = None def test_tag(self): html = self.render_tag('luckyorange', 'luckyorange') self.assertEqual(expected_html, html) def test_node(self): html = LuckyOrangeNode().render(Context({})) self.assertEqual(expected_html, html) def test_tags_take_no_args(self): self.assertRaisesRegex( TemplateSyntaxError, r"^'luckyorange' takes no arguments$", lambda: (Template('{% load luckyorange %}{% luckyorange "arg" %}') .render(Context({}))), ) @override_settings(LUCKYORANGE_SITE_ID=None) def test_no_id(self): expected_pattern = r'^LUCKYORANGE_SITE_ID setting is not set$' self.assertRaisesRegex(AnalyticalException, expected_pattern, LuckyOrangeNode) @override_settings(LUCKYORANGE_SITE_ID='invalid') def test_invalid_id(self): expected_pattern = ( r"^LUCKYORANGE_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$") self.assertRaisesRegex(AnalyticalException, expected_pattern, LuckyOrangeNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): request = HttpRequest() request.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': request}) actual_html = LuckyOrangeNode().render(context) disabled_html = '\n'.join([ '', ]) self.assertEqual(disabled_html, actual_html) def test_contribute_to_analytical(self): """ `luckyorange.contribute_to_analytical` registers the head and body nodes. """ template_nodes = _load_template_nodes() self.assertEqual({ 'head_top': [], 'head_bottom': [LuckyOrangeNode], 'body_top': [], 'body_bottom': [], }, template_nodes) django-analytical-3.0.0/tests/unit/test_tag_matomo.py000066400000000000000000000141671376325370100227740ustar00rootroot00000000000000""" Tests for the Matomo template tags and filters. """ from django.contrib.auth.models import User from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.matomo import MatomoNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(MATOMO_DOMAIN_PATH='example.com', MATOMO_SITE_ID='345') class MatomoTagTestCase(TagTestCase): """ Tests for the ``matomo`` template tag. """ def test_tag(self): r = self.render_tag('matomo', 'matomo') self.assertTrue('"//example.com/"' in r, r) self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) self.assertTrue('img src="//example.com/piwik.php?idsite=345"' in r, r) def test_node(self): r = MatomoNode().render(Context({})) self.assertTrue('"//example.com/";' in r, r) self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) self.assertTrue('img src="//example.com/piwik.php?idsite=345"' in r, r) @override_settings(MATOMO_DOMAIN_PATH='example.com/matomo', MATOMO_SITE_ID='345') def test_domain_path_valid(self): r = self.render_tag('matomo', 'matomo') self.assertTrue('"//example.com/matomo/"' in r, r) @override_settings(MATOMO_DOMAIN_PATH='example.com:1234', MATOMO_SITE_ID='345') def test_domain_port_valid(self): r = self.render_tag('matomo', 'matomo') self.assertTrue('"//example.com:1234/";' in r, r) @override_settings(MATOMO_DOMAIN_PATH='example.com:1234/matomo', MATOMO_SITE_ID='345') def test_domain_port_path_valid(self): r = self.render_tag('matomo', 'matomo') self.assertTrue('"//example.com:1234/matomo/"' in r, r) @override_settings(MATOMO_DOMAIN_PATH=None) def test_no_domain(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_SITE_ID=None) def test_no_siteid(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_SITE_ID='x') def test_siteid_not_a_number(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_DOMAIN_PATH='http://www.example.com') def test_domain_protocol_invalid(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_DOMAIN_PATH='example.com/') def test_domain_slash_invalid(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_DOMAIN_PATH='example.com:123:456') def test_domain_multi_port(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_DOMAIN_PATH='example.com:') def test_domain_incomplete_port(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_DOMAIN_PATH='example.com:/matomo') def test_domain_uri_incomplete_port(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(MATOMO_DOMAIN_PATH='example.com:12df') def test_domain_port_invalid(self): self.assertRaises(AnalyticalException, MatomoNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = MatomoNode().render(context) self.assertTrue(r.startswith( ''), r) def test_uservars(self): context = Context({'matomo_vars': [(1, 'foo', 'foo_val'), (2, 'bar', 'bar_val', 'page'), (3, 'spam', 'spam_val', 'visit')]}) r = MatomoNode().render(context) msg = 'Incorrect Matomo custom variable rendering. Expected:\n%s\nIn:\n%s' for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', '_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', '_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']: self.assertIn(var_code, r, msg % (var_code, r)) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_default_usertrack(self): context = Context({ 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum') }) r = MatomoNode().render(context) msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertIn(var_code, r, msg % (var_code, r)) def test_matomo_usertrack(self): context = Context({ 'matomo_identity': 'BDFL' }) r = MatomoNode().render(context) msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertIn(var_code, r, msg % (var_code, r)) def test_analytical_usertrack(self): context = Context({ 'analytical_identity': 'BDFL' }) r = MatomoNode().render(context) msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertIn(var_code, r, msg % (var_code, r)) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_disable_usertrack(self): context = Context({ 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'), 'matomo_identity': None }) r = MatomoNode().render(context) msg = 'Incorrect Matomo user tracking rendering.\nFound:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertNotIn(var_code, r, msg % (var_code, r)) @override_settings(MATOMO_DISABLE_COOKIES=True) def test_disable_cookies(self): r = MatomoNode().render(Context({})) self.assertTrue("_paq.push(['disableCookies']);" in r, r) django-analytical-3.0.0/tests/unit/test_tag_mixpanel.py000066400000000000000000000047061376325370100233130ustar00rootroot00000000000000""" Tests for the Mixpanel tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.mixpanel import MixpanelNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(MIXPANEL_API_TOKEN='0123456789abcdef0123456789abcdef') class MixpanelTagTestCase(TagTestCase): """ Tests for the ``mixpanel`` template tag. """ def test_tag(self): r = self.render_tag('mixpanel', 'mixpanel') self.assertIn("mixpanel.init('0123456789abcdef0123456789abcdef');", r) def test_node(self): r = MixpanelNode().render(Context()) self.assertIn("mixpanel.init('0123456789abcdef0123456789abcdef');", r) @override_settings(MIXPANEL_API_TOKEN=None) def test_no_token(self): self.assertRaises(AnalyticalException, MixpanelNode) @override_settings(MIXPANEL_API_TOKEN='0123456789abcdef0123456789abcdef0') def test_token_too_long(self): self.assertRaises(AnalyticalException, MixpanelNode) @override_settings(MIXPANEL_API_TOKEN='0123456789abcdef0123456789abcde') def test_token_too_short(self): self.assertRaises(AnalyticalException, MixpanelNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = MixpanelNode().render(Context({'user': User(username='test')})) self.assertIn("mixpanel.identify('test');", r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = MixpanelNode().render(Context({'user': AnonymousUser()})) self.assertFalse("mixpanel.register_once({distinct_id:" in r, r) def test_event(self): r = MixpanelNode().render(Context({ 'mixpanel_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}), })) self.assertTrue("mixpanel.track('test_event', " '{"prop1": "val1", "prop2": "val2"});' in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = MixpanelNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_olark.py000066400000000000000000000065031376325370100226030ustar00rootroot00000000000000""" Tests for the Olark template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.template import Context from django.test.utils import override_settings from analytical.templatetags.olark import OlarkNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(OLARK_SITE_ID='1234-567-89-0123') class OlarkTestCase(TagTestCase): """ Tests for the ``olark`` template tag. """ def test_tag(self): r = self.render_tag('olark', 'olark') self.assertTrue("olark.identify('1234-567-89-0123');" in r, r) def test_node(self): r = OlarkNode().render(Context()) self.assertTrue("olark.identify('1234-567-89-0123');" in r, r) @override_settings(OLARK_SITE_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, OlarkNode) @override_settings(OLARK_SITE_ID='1234-567-8901234') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, OlarkNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = OlarkNode().render(Context({ 'user': User(username='test', first_name='Test', last_name='User'), })) self.assertTrue("olark('api.chat.updateVisitorNickname', " "{snippet: 'Test User (test)'});" in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = OlarkNode().render(Context({'user': AnonymousUser()})) self.assertFalse("olark('api.chat.updateVisitorNickname', " in r, r) def test_nickname(self): r = OlarkNode().render(Context({'olark_nickname': 'testnick'})) self.assertTrue("olark('api.chat.updateVisitorNickname', " "{snippet: 'testnick'});" in r, r) def test_status_string(self): r = OlarkNode().render(Context({'olark_status': 'teststatus'})) self.assertTrue("olark('api.chat.updateVisitorStatus', " '{snippet: "teststatus"});' in r, r) def test_status_string_list(self): r = OlarkNode().render(Context({ 'olark_status': ['teststatus1', 'teststatus2'], })) self.assertTrue("olark('api.chat.updateVisitorStatus', " '{snippet: ["teststatus1", "teststatus2"]});' in r, r) def test_messages(self): messages = [ "welcome_title", "chatting_title", "unavailable_title", "busy_title", "away_message", "loading_title", "welcome_message", "busy_message", "chat_input_text", "name_input_text", "email_input_text", "offline_note_message", "send_button_text", "offline_note_thankyou_text", "offline_note_error_text", "offline_note_sending_text", "operator_is_typing_text", "operator_has_stopped_typing_text", "introduction_error_text", "introduction_messages", "introduction_submit_button_text", ] vars = {'olark_%s' % m: m for m in messages} r = OlarkNode().render(Context(vars)) for m in messages: self.assertTrue("olark.configure('locale.%s', \"%s\");" % (m, m) in r, r) django-analytical-3.0.0/tests/unit/test_tag_optimizely.py000066400000000000000000000030731376325370100236770ustar00rootroot00000000000000""" Tests for the Optimizely template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.optimizely import OptimizelyNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(OPTIMIZELY_ACCOUNT_NUMBER='1234567') class OptimizelyTagTestCase(TagTestCase): """ Tests for the ``optimizely`` template tag. """ def test_tag(self): self.assertEqual( '', self.render_tag('optimizely', 'optimizely')) def test_node(self): self.assertEqual( '', OptimizelyNode().render(Context())) @override_settings(OPTIMIZELY_ACCOUNT_NUMBER=None) def test_no_account_number(self): self.assertRaises(AnalyticalException, OptimizelyNode) @override_settings(OPTIMIZELY_ACCOUNT_NUMBER='123abc') def test_wrong_account_number(self): self.assertRaises(AnalyticalException, OptimizelyNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = OptimizelyNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_performable.py000066400000000000000000000046131376325370100237710ustar00rootroot00000000000000""" Tests for the Performable template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.performable import PerformableNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(PERFORMABLE_API_KEY='123ABC') class PerformableTagTestCase(TagTestCase): """ Tests for the ``performable`` template tag. """ def test_tag(self): r = self.render_tag('performable', 'performable') self.assertTrue('/performable/pax/123ABC.js' in r, r) def test_node(self): r = PerformableNode().render(Context()) self.assertTrue('/performable/pax/123ABC.js' in r, r) @override_settings(PERFORMABLE_API_KEY=None) def test_no_api_key(self): self.assertRaises(AnalyticalException, PerformableNode) @override_settings(PERFORMABLE_API_KEY='123 ABC') def test_wrong_account_number(self): self.assertRaises(AnalyticalException, PerformableNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = PerformableNode().render(context) self.assertTrue(r.startswith( ''), r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = PerformableNode().render(Context({'user': User(username='test')})) self.assertTrue('_paq.push(["identify", {identity: "test"}]);' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = PerformableNode().render(Context({'user': AnonymousUser()})) self.assertFalse('_paq.push(["identify", ' in r, r) class PerformableEmbedTagTestCase(TagTestCase): """ Tests for the ``performable_embed`` template tag. """ def test_tag(self): domain = 'example.com' page = 'test' tag = self.render_tag( 'performable', 'performable_embed "%s" "%s"' % (domain, page) ) self.assertIn( "$f.initialize({'host': 'example.com', 'page': 'test'});", tag ) django-analytical-3.0.0/tests/unit/test_tag_piwik.py000066400000000000000000000140661376325370100226210ustar00rootroot00000000000000""" Tests for the Piwik template tags and filters. """ from django.contrib.auth.models import User from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.piwik import PiwikNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(PIWIK_DOMAIN_PATH='example.com', PIWIK_SITE_ID='345') class PiwikTagTestCase(TagTestCase): """ Tests for the ``piwik`` template tag. """ def test_tag(self): r = self.render_tag('piwik', 'piwik') self.assertTrue('"//example.com/"' in r, r) self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) self.assertTrue('img src="//example.com/piwik.php?idsite=345"' in r, r) def test_node(self): r = PiwikNode().render(Context({})) self.assertTrue('"//example.com/";' in r, r) self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) self.assertTrue('img src="//example.com/piwik.php?idsite=345"' in r, r) @override_settings(PIWIK_DOMAIN_PATH='example.com/piwik', PIWIK_SITE_ID='345') def test_domain_path_valid(self): r = self.render_tag('piwik', 'piwik') self.assertTrue('"//example.com/piwik/"' in r, r) @override_settings(PIWIK_DOMAIN_PATH='example.com:1234', PIWIK_SITE_ID='345') def test_domain_port_valid(self): r = self.render_tag('piwik', 'piwik') self.assertTrue('"//example.com:1234/";' in r, r) @override_settings(PIWIK_DOMAIN_PATH='example.com:1234/piwik', PIWIK_SITE_ID='345') def test_domain_port_path_valid(self): r = self.render_tag('piwik', 'piwik') self.assertTrue('"//example.com:1234/piwik/"' in r, r) @override_settings(PIWIK_DOMAIN_PATH=None) def test_no_domain(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_SITE_ID=None) def test_no_siteid(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_SITE_ID='x') def test_siteid_not_a_number(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_DOMAIN_PATH='http://www.example.com') def test_domain_protocol_invalid(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_DOMAIN_PATH='example.com/') def test_domain_slash_invalid(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_DOMAIN_PATH='example.com:123:456') def test_domain_multi_port(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_DOMAIN_PATH='example.com:') def test_domain_incomplete_port(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_DOMAIN_PATH='example.com:/piwik') def test_domain_uri_incomplete_port(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(PIWIK_DOMAIN_PATH='example.com:12df') def test_domain_port_invalid(self): self.assertRaises(AnalyticalException, PiwikNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = PiwikNode().render(context) self.assertTrue(r.startswith( ''), r) def test_uservars(self): context = Context({'piwik_vars': [(1, 'foo', 'foo_val'), (2, 'bar', 'bar_val', 'page'), (3, 'spam', 'spam_val', 'visit')]}) r = PiwikNode().render(context) msg = 'Incorrect Piwik custom variable rendering. Expected:\n%s\nIn:\n%s' for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', '_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', '_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']: self.assertIn(var_code, r, msg % (var_code, r)) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_default_usertrack(self): context = Context({ 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum') }) r = PiwikNode().render(context) msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertIn(var_code, r, msg % (var_code, r)) def test_piwik_usertrack(self): context = Context({ 'piwik_identity': 'BDFL' }) r = PiwikNode().render(context) msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertIn(var_code, r, msg % (var_code, r)) def test_analytical_usertrack(self): context = Context({ 'analytical_identity': 'BDFL' }) r = PiwikNode().render(context) msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertIn(var_code, r, msg % (var_code, r)) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_disable_usertrack(self): context = Context({ 'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'), 'piwik_identity': None }) r = PiwikNode().render(context) msg = 'Incorrect Piwik user tracking rendering.\nFound:\n%s\nIn:\n%s' var_code = '_paq.push(["setUserId", "BDFL"]);' self.assertNotIn(var_code, r, msg % (var_code, r)) @override_settings(PIWIK_DISABLE_COOKIES=True) def test_disable_cookies(self): r = PiwikNode().render(Context({})) self.assertTrue("_paq.push(['disableCookies']);" in r, r) django-analytical-3.0.0/tests/unit/test_tag_rating_mailru.py000066400000000000000000000027531376325370100243330ustar00rootroot00000000000000""" Tests for the Rating@Mail.ru template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.rating_mailru import RatingMailruNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(RATING_MAILRU_COUNTER_ID='1234567') class RatingMailruTagTestCase(TagTestCase): """ Tests for the ``rating_mailru`` template tag. """ def test_tag(self): r = self.render_tag('rating_mailru', 'rating_mailru') self.assertTrue("counter?id=1234567;js=na" in r, r) def test_node(self): r = RatingMailruNode().render(Context({})) self.assertTrue("counter?id=1234567;js=na" in r, r) @override_settings(RATING_MAILRU_COUNTER_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, RatingMailruNode) @override_settings(RATING_MAILRU_COUNTER_ID='1234abc') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, RatingMailruNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = RatingMailruNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_snapengage.py000066400000000000000000000265161376325370100236110ustar00rootroot00000000000000""" Tests for the SnapEngage template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.template import Context from django.test.utils import override_settings from django.utils import translation from analytical.templatetags.snapengage import SnapEngageNode, \ BUTTON_STYLE_LIVE, BUTTON_STYLE_DEFAULT, BUTTON_STYLE_NONE, \ BUTTON_LOCATION_LEFT, BUTTON_LOCATION_RIGHT, BUTTON_LOCATION_TOP, \ BUTTON_LOCATION_BOTTOM, FORM_POSITION_TOP_LEFT from utils import TagTestCase from analytical.utils import AnalyticalException WIDGET_ID = 'ec329c69-0bf0-4db8-9b77-3f8150fb977e' @override_settings( SNAPENGAGE_WIDGET_ID=WIDGET_ID, SNAPENGAGE_BUTTON=BUTTON_STYLE_DEFAULT, SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_LEFT, SNAPENGAGE_BUTTON_OFFSET="55%", ) class SnapEngageTestCase(TagTestCase): """ Tests for the ``snapengage`` template tag. """ def test_tag(self): r = self.render_tag('snapengage', 'snapengage') self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"55%");' in r, r) def test_node(self): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"55%");' in r, r) @override_settings(SNAPENGAGE_WIDGET_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, SnapEngageNode) @override_settings(SNAPENGAGE_WIDGET_ID='abc') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, SnapEngageNode) def test_no_button(self): r = SnapEngageNode().render(Context({ 'snapengage_button': BUTTON_STYLE_NONE, })) self.assertTrue('SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r, r) with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_NONE): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r, r) def test_live_button(self): r = SnapEngageNode().render(Context({ 'snapengage_button': BUTTON_STYLE_LIVE, })) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"55%",true);' in r, r) with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_LIVE): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"55%",true);' in r, r) def test_custom_button(self): r = SnapEngageNode().render(Context({ 'snapengage_button': "http://www.example.com/button.png", })) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"55%");' in r, r) self.assertTrue( 'SnapABug.setButton("http://www.example.com/button.png");' in r, r) with override_settings( SNAPENGAGE_BUTTON="http://www.example.com/button.png"): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"55%");' in r, r) self.assertTrue( 'SnapABug.setButton("http://www.example.com/button.png");' in r, r) def test_button_location_right(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location': BUTTON_LOCATION_RIGHT, })) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",' '"55%");' in r, r) with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_RIGHT): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",' '"55%");' in r, r) def test_button_location_top(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location': BUTTON_LOCATION_TOP, })) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",' '"55%");' in r, r) with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_TOP): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",' '"55%");' in r, r) def test_button_location_bottom(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location': BUTTON_LOCATION_BOTTOM, })) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",' '"55%");' in r, r) with override_settings( SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_BOTTOM): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",' '"55%");' in r, r) def test_button_offset(self): r = SnapEngageNode().render(Context({ 'snapengage_button_location_offset': "30%", })) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"30%");' in r, r) with override_settings(SNAPENGAGE_BUTTON_LOCATION_OFFSET="30%"): r = SnapEngageNode().render(Context()) self.assertTrue( 'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",' '"30%");' in r, r) def test_button_effect(self): r = SnapEngageNode().render(Context({ 'snapengage_button_effect': "-4px", })) self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r) with override_settings(SNAPENGAGE_BUTTON_EFFECT="-4px"): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r) def test_form_position(self): r = SnapEngageNode().render(Context({ 'snapengage_form_position': FORM_POSITION_TOP_LEFT, })) self.assertTrue('SnapABug.setChatFormPosition("tl");' in r, r) with override_settings(SNAPENGAGE_FORM_POSITION=FORM_POSITION_TOP_LEFT): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setChatFormPosition("tl");' in r, r) def test_form_top_position(self): r = SnapEngageNode().render(Context({ 'snapengage_form_top_position': 40, })) self.assertTrue('SnapABug.setFormTopPosition(40);' in r, r) with override_settings(SNAPENGAGE_FORM_TOP_POSITION=40): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setFormTopPosition(40);' in r, r) def test_domain(self): r = SnapEngageNode().render(Context({ 'snapengage_domain': "example.com"})) self.assertTrue('SnapABug.setDomain("example.com");' in r, r) with override_settings(SNAPENGAGE_DOMAIN="example.com"): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setDomain("example.com");' in r, r) def test_secure_connection(self): r = SnapEngageNode().render(Context({ 'snapengage_secure_connection': True})) self.assertTrue('SnapABug.setSecureConnexion();' in r, r) with override_settings(SNAPENGAGE_SECURE_CONNECTION=True): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setSecureConnexion();' in r, r) def test_show_offline(self): r = SnapEngageNode().render(Context({ 'snapengage_show_offline': False, })) self.assertTrue('SnapABug.allowOffline(false);' in r, r) with override_settings(SNAPENGAGE_SHOW_OFFLINE=False): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.allowOffline(false);' in r, r) def test_proactive_chat(self): r = SnapEngageNode().render(Context({ 'snapengage_proactive_chat': False})) self.assertTrue('SnapABug.allowProactiveChat(false);' in r, r) def test_screenshot(self): r = SnapEngageNode().render(Context({ 'snapengage_screenshots': False, })) self.assertTrue('SnapABug.allowScreenshot(false);' in r, r) with override_settings(SNAPENGAGE_SCREENSHOTS=False): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.allowScreenshot(false);' in r, r) def test_offline_screenshots(self): r = SnapEngageNode().render(Context({ 'snapengage_offline_screenshots': False, })) self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r) with override_settings(SNAPENGAGE_OFFLINE_SCREENSHOTS=False): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r) def test_sounds(self): r = SnapEngageNode().render(Context({'snapengage_sounds': False})) self.assertTrue('SnapABug.allowChatSound(false);' in r, r) with override_settings(SNAPENGAGE_SOUNDS=False): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.allowChatSound(false);' in r, r) @override_settings(SNAPENGAGE_READONLY_EMAIL=False) def test_email(self): r = SnapEngageNode().render(Context({ 'snapengage_email': 'test@example.com', })) self.assertTrue('SnapABug.setUserEmail("test@example.com");' in r, r) def test_email_readonly(self): r = SnapEngageNode().render(Context({ 'snapengage_email': 'test@example.com', 'snapengage_readonly_email': True, })) self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r, r) with override_settings(SNAPENGAGE_READONLY_EMAIL=True): r = SnapEngageNode().render(Context({ 'snapengage_email': 'test@example.com', })) self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = SnapEngageNode().render(Context({ 'user': User(username='test', email='test@example.com'), })) self.assertTrue('SnapABug.setUserEmail("test@example.com");' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = SnapEngageNode().render(Context({ 'user': AnonymousUser(), })) self.assertFalse('SnapABug.setUserEmail(' in r, r) def test_language(self): r = SnapEngageNode().render(Context({'snapengage_locale': 'fr'})) self.assertTrue('SnapABug.setLocale("fr");' in r, r) with override_settings(SNAPENGAGE_LOCALE='fr'): r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setLocale("fr");' in r, r) def test_automatic_language(self): real_get_language = translation.get_language try: translation.get_language = lambda: 'fr-ca' r = SnapEngageNode().render(Context()) self.assertTrue('SnapABug.setLocale("fr_CA");' in r, r) finally: translation.get_language = real_get_language django-analytical-3.0.0/tests/unit/test_tag_spring_metrics.py000066400000000000000000000047001376325370100245200ustar00rootroot00000000000000""" Tests for the Spring Metrics template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.spring_metrics import SpringMetricsNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(SPRING_METRICS_TRACKING_ID='12345678') class SpringMetricsTagTestCase(TagTestCase): """ Tests for the ``spring_metrics`` template tag. """ def test_tag(self): r = self.render_tag('spring_metrics', 'spring_metrics') self.assertTrue("_springMetq.push(['id', '12345678']);" in r, r) def test_node(self): r = SpringMetricsNode().render(Context({})) self.assertTrue("_springMetq.push(['id', '12345678']);" in r, r) @override_settings(SPRING_METRICS_TRACKING_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, SpringMetricsNode) @override_settings(SPRING_METRICS_TRACKING_ID='123xyz') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, SpringMetricsNode) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify(self): r = SpringMetricsNode().render(Context({ 'user': User(email='test@test.com'), })) self.assertTrue("_springMetq.push(['setdata', {'email': 'test@test.com'}]);" in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = SpringMetricsNode().render(Context({'user': AnonymousUser()})) self.assertFalse("_springMetq.push(['setdata', {'email':" in r, r) def test_custom(self): r = SpringMetricsNode().render(Context({ 'spring_metrics_var1': 'val1', 'spring_metrics_var2': 'val2', })) self.assertTrue("_springMetq.push(['setdata', {'var1': 'val1'}]);" in r, r) self.assertTrue("_springMetq.push(['setdata', {'var2': 'val2'}]);" in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = SpringMetricsNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_uservoice.py000066400000000000000000000052511376325370100234760ustar00rootroot00000000000000""" Tests for the UserVoice tags and filters. """ from django.template import Context from django.test.utils import override_settings from analytical.templatetags.uservoice import UserVoiceNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(USERVOICE_WIDGET_KEY='abcdefghijklmnopqrst') class UserVoiceTagTestCase(TagTestCase): """ Tests for the ``uservoice`` template tag. """ def assertIn(self, element, container): try: super(TagTestCase, self).assertIn(element, container) except AttributeError: self.assertTrue(element in container) def test_node(self): r = UserVoiceNode().render(Context()) self.assertIn("widget.uservoice.com/abcdefghijklmnopqrst.js", r) def test_tag(self): r = self.render_tag('uservoice', 'uservoice') self.assertIn("widget.uservoice.com/abcdefghijklmnopqrst.js", r) @override_settings(USERVOICE_WIDGET_KEY=None) def test_no_key(self): self.assertRaises(AnalyticalException, UserVoiceNode) @override_settings(USERVOICE_WIDGET_KEY='abcdefgh ijklmnopqrst') def test_invalid_key(self): self.assertRaises(AnalyticalException, UserVoiceNode) @override_settings(USERVOICE_WIDGET_KEY='') def test_empty_key(self): self.assertRaises(AnalyticalException, UserVoiceNode) def test_overridden_key(self): vars = {'uservoice_widget_key': 'defghijklmnopqrstuvw'} r = UserVoiceNode().render(Context(vars)) self.assertIn("widget.uservoice.com/defghijklmnopqrstuvw.js", r) @override_settings(USERVOICE_WIDGET_OPTIONS={'key1': 'val1'}) def test_options(self): r = UserVoiceNode().render(Context()) self.assertIn("""UserVoice.push(['set', {"key1": "val1"}]);""", r) @override_settings(USERVOICE_WIDGET_OPTIONS={'key1': 'val1'}) def test_override_options(self): data = {'uservoice_widget_options': {'key1': 'val2'}} r = UserVoiceNode().render(Context(data)) self.assertIn("""UserVoice.push(['set', {"key1": "val2"}]);""", r) def test_auto_trigger_default(self): r = UserVoiceNode().render(Context()) self.assertTrue("UserVoice.push(['addTrigger', {}]);" in r, r) @override_settings(USERVOICE_ADD_TRIGGER=False) def test_auto_trigger(self): r = UserVoiceNode().render(Context()) self.assertFalse("UserVoice.push(['addTrigger', {}]);" in r, r) @override_settings(USERVOICE_ADD_TRIGGER=False) def test_auto_trigger_custom_win(self): r = UserVoiceNode().render(Context({'uservoice_add_trigger': True})) self.assertTrue("UserVoice.push(['addTrigger', {}]);" in r, r) django-analytical-3.0.0/tests/unit/test_tag_woopra.py000066400000000000000000000070411376325370100230000ustar00rootroot00000000000000""" Tests for the Woopra template tags and filters. """ from django.contrib.auth.models import User, AnonymousUser from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.woopra import WoopraNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(WOOPRA_DOMAIN='example.com') class WoopraTagTestCase(TagTestCase): """ Tests for the ``woopra`` template tag. """ def test_tag(self): r = self.render_tag('woopra', 'woopra') self.assertTrue('var woo_settings = {"domain": "example.com"};' in r, r) def test_node(self): r = WoopraNode().render(Context({})) self.assertTrue('var woo_settings = {"domain": "example.com"};' in r, r) @override_settings(WOOPRA_DOMAIN=None) def test_no_domain(self): self.assertRaises(AnalyticalException, WoopraNode) @override_settings(WOOPRA_DOMAIN='this is not a domain') def test_wrong_domain(self): self.assertRaises(AnalyticalException, WoopraNode) @override_settings(WOOPRA_IDLE_TIMEOUT=1234) def test_idle_timeout(self): r = WoopraNode().render(Context({})) self.assertTrue('var woo_settings = ' '{"domain": "example.com", "idle_timeout": "1234"};' in r, r) def test_custom(self): r = WoopraNode().render(Context({ 'woopra_var1': 'val1', 'woopra_var2': 'val2', })) self.assertTrue('var woo_visitor = {"var1": "val1", "var2": "val2"};' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_name_and_email(self): r = WoopraNode().render(Context({ 'user': User(username='test', first_name='Firstname', last_name='Lastname', email="test@example.com"), })) self.assertTrue('var woo_visitor = ' '{"email": "test@example.com", "name": "Firstname Lastname"};' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_username_no_email(self): r = WoopraNode().render(Context({'user': User(username='test')})) self.assertTrue('var woo_visitor = {"name": "test"};' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_no_identify_when_explicit_name(self): r = WoopraNode().render(Context({ 'woopra_name': 'explicit', 'user': User(username='implicit'), })) self.assertTrue('var woo_visitor = {"name": "explicit"};' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_no_identify_when_explicit_email(self): r = WoopraNode().render(Context({ 'woopra_email': 'explicit', 'user': User(username='implicit'), })) self.assertTrue('var woo_visitor = {"email": "explicit"};' in r, r) @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) def test_identify_anonymous_user(self): r = WoopraNode().render(Context({'user': AnonymousUser()})) self.assertTrue('var woo_visitor = {};' in r, r) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = WoopraNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_tag_yandex_metrica.py000066400000000000000000000030231376325370100244610ustar00rootroot00000000000000""" Tests for the Yandex.Metrica template tags and filters. """ from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.templatetags.yandex_metrica import YandexMetricaNode from utils import TagTestCase from analytical.utils import AnalyticalException @override_settings(YANDEX_METRICA_COUNTER_ID='12345678') class YandexMetricaTagTestCase(TagTestCase): """ Tests for the ``yandex_metrica`` template tag. """ def test_tag(self): r = self.render_tag('yandex_metrica', 'yandex_metrica') self.assertTrue("w.yaCounter12345678 = new Ya.Metrika" in r, r) def test_node(self): r = YandexMetricaNode().render(Context({})) self.assertTrue("w.yaCounter12345678 = new Ya.Metrika" in r, r) @override_settings(YANDEX_METRICA_COUNTER_ID=None) def test_no_site_id(self): self.assertRaises(AnalyticalException, YandexMetricaNode) @override_settings(YANDEX_METRICA_COUNTER_ID='1234abcd') def test_wrong_site_id(self): self.assertRaises(AnalyticalException, YandexMetricaNode) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) r = YandexMetricaNode().render(context) self.assertTrue(r.startswith( ''), r) django-analytical-3.0.0/tests/unit/test_utils.py000066400000000000000000000105231376325370100217750ustar00rootroot00000000000000""" Tests for the analytical.utils module. """ # import django from django.contrib.auth.models import AbstractBaseUser from django.db import models from django.http import HttpRequest from django.template import Context from django.test.utils import override_settings from analytical.utils import ( AnalyticalException, get_domain, get_identity, get_required_setting, is_internal_ip, ) from utils import TestCase class SettingDeletedTestCase(TestCase): @override_settings(USER_ID=None) def test_get_required_setting(self): """ Make sure using get_required_setting fails in the right place. """ with self.assertRaisesRegex(AnalyticalException, "^USER_ID setting is not set$"): get_required_setting("USER_ID", r"\d+", "invalid USER_ID") class MyUser(AbstractBaseUser): identity = models.CharField(max_length=50) USERNAME_FIELD = 'identity' class Meta: abstract = True app_label = 'testapp' class GetIdentityTestCase(TestCase): def test_custom_username_field(self): get_id = get_identity(Context({}), user=MyUser(identity='fake_id')) self.assertEqual(get_id, 'fake_id') @override_settings(ANALYTICAL_DOMAIN="example.org") class GetDomainTestCase(TestCase): def test_get_service_domain_from_context(self): context = Context({'test_domain': 'example.com'}) self.assertEqual(get_domain(context, 'test'), 'example.com') def test_get_analytical_domain_from_context(self): context = Context({'analytical_domain': 'example.com'}) self.assertEqual(get_domain(context, 'test'), 'example.com') @override_settings(TEST_DOMAIN="example.net") def test_get_service_domain_from_settings(self): context = Context() self.assertEqual(get_domain(context, 'test'), 'example.net') def test_get_analytical_domain_from_settings(self): context = Context() self.assertEqual(get_domain(context, 'test'), 'example.org') # FIXME: enable Django apps dynamically and enable test again # @with_apps('django.contrib.sites') # @override_settings(TEST_DOMAIN=SETTING_DELETED, ANALYTICAL_DOMAIN=SETTING_DELETED) # class GetDomainTestCaseWithSites(TestCase): # def test_get_domain_from_site(self): # site = Site.objects.create(domain="example.com", name="test") # with override_settings(SITE_ID=site.id): # context = Context() # self.assertEqual(get_domain(context, 'test'), 'example.com') class InternalIpTestCase(TestCase): @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_no_internal_ip(self): context = Context() self.assertFalse(is_internal_ip(context)) @override_settings(INTERNAL_IPS=['1.1.1.1']) @override_settings(ANALYTICAL_INTERNAL_IPS=[]) def test_render_analytical_internal_ips_override_when_empty(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) self.assertFalse(is_internal_ip(context)) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) self.assertTrue(is_internal_ip(context)) @override_settings(TEST_INTERNAL_IPS=['1.1.1.1']) def test_render_prefix_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) self.assertTrue(is_internal_ip(context, 'TEST')) @override_settings(INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip_fallback(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '1.1.1.1' context = Context({'request': req}) self.assertTrue(is_internal_ip(context)) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_internal_ip_forwarded_for(self): req = HttpRequest() req.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1' context = Context({'request': req}) self.assertTrue(is_internal_ip(context)) @override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) def test_render_different_internal_ip(self): req = HttpRequest() req.META['REMOTE_ADDR'] = '2.2.2.2' context = Context({'request': req}) self.assertFalse(is_internal_ip(context)) django-analytical-3.0.0/tests/unit/utils.py000066400000000000000000000016321376325370100207370ustar00rootroot00000000000000""" Testing utilities. """ from django.template import Template, Context, RequestContext from django.test.testcases import TestCase class TagTestCase(TestCase): """ Tests for a template tag. Adds support methods for testing template tags. """ def render_tag(self, library, tag, vars=None, request=None): if vars is None: vars = {} t = Template("{%% load %s %%}{%% %s %%}" % (library, tag)) if request is not None: context = RequestContext(request, vars) else: context = Context(vars) return t.render(context) def render_template(self, template, vars=None, request=None): if vars is None: vars = {} t = Template(template) if request is not None: context = RequestContext(request, vars) else: context = Context(vars) return t.render(context) django-analytical-3.0.0/tox.ini000066400000000000000000000022331376325370100164150ustar00rootroot00000000000000[tox] envlist = # Python/Django combinations that are officially supported py{36,37,38,39}-django{22,30,31} py37-{flake8,bandit,readme,docs} [testenv] description = Unit tests deps = coverage pytest-django django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 commands = coverage run -m pytest coverage xml [gh-actions] python = 3.6: py36 3.7: py37 3.8: py38 3.9: py39 [testenv:py37-bandit] description = PyCQA security linter deps = bandit<1.6 commands = - bandit -r --ini tox.ini ignore_errors = true [testenv:py37-docs] description = Build the HTML documentation deps = sphinx commands = sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html whitelist_externals = make [testenv:py37-flake8] description = Static code analysis and code style deps = flake8 commands = flake8 [testenv:py37-readme] description = Ensure README renders on PyPI deps = twine commands = {envpython} setup.py -q sdist bdist_wheel twine check dist/* [bandit] exclude = .cache,.git,.tox,build,dist,docs,tests targets = . [flake8] exclude = .cache,.git,.tox,build,dist max-line-length = 100