diff --git a/.travis.yml b/.travis.yml index d1cc97e..989706d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,14 @@ language: python python: "3.5" install: + # continue to support Python 3.2 (see issue #84) + - pip install "virtualenv<14.0.0" - pip install coveralls tox script: - tox -# NOTE: To generate (update) the env list run -# $ tox -l | sort | xargs -I ITEM echo " - TOXENV="ITEM env: + # NOTE: To generate (update) the env list run + # $ tox -l | sort | xargs -I ITEM echo " - TOXENV="ITEM - TOXENV=py27-django17 - TOXENV=py27-django18 - TOXENV=py27-django19 diff --git a/AUTHORS.rst b/AUTHORS.rst index b9e4f4d..9c8b416 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -3,7 +3,8 @@ contributions from `Eric Davis`_, `Paul Oswald`_, `Uros Trebec`_, `Steven Skoczen`_, `Piet 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`_ and others. +`Alexandre Pocquet`_, `Brad Pitcher`_, `Hugo Osvaldo Barrera`_, +`Nikolay Korotkiy`_, `Steve Schwarz`_, `Aleck Landgraf`_ and others. Included Javascript code snippets for integration of the analytics services were written by the respective service providers. @@ -33,6 +34,9 @@ The work on Intercom was made possible by `GreenKahuna`_. .. _`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 .. _`Analytical`: https://github.com/jkrall/analytical .. _`Bateau Knowledge`: http://www.bateauknowledge.nl/ .. _`GreenKahuna`: http://www.greenkahuna.com/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f08386d..bd88d91 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,14 @@ +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) diff --git a/README.rst b/README.rst index f2a1180..5ce9748 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ django-analytical |latest-version| ================================== -|travis-ci| |coveralls| |health| |downloads| |license| |gitter| +|travis-ci| |coveralls| |health| |python-support| |downloads| |license| |gitter| The django-analytical application integrates analytics services into a Django_ project. @@ -35,6 +35,9 @@ an asynchronous version of the Javascript code if possible. .. |health| image:: https://landscape.io/github/jcassee/django-analytical/master/landscape.svg?style=flat :target: https://landscape.io/github/jcassee/django-analytical/master :alt: Code health +.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg + :target: https://pypi.python.org/pypi/django-analytical + :alt: Python versions .. |downloads| image:: https://img.shields.io/pypi/dm/django-analytical.svg :alt: Monthly downloads from PyPI :target: https://pypi.python.org/pypi/django-analytical @@ -65,11 +68,12 @@ Currently Supported Services * `Optimizely`_ A/B testing * `Performable`_ web analytics and landing pages * `Piwik`_ open source web analytics -* `Reinvigorate`_ visitor tracking +* `Rating\@Mail.ru`_ web analytics * `SnapEngage`_ live chat * `Spring Metrics`_ conversion tracking * `UserVoice`_ user feedback and helpdesk * `Woopra`_ web analytics +* `Yandex.Metrica`_ web analytics .. _`Chartbeat`: http://www.chartbeat.com/ .. _`Clickmap`: http://getclickmap.com/ @@ -87,11 +91,12 @@ Currently Supported Services .. _`Optimizely`: http://www.optimizely.com/ .. _`Performable`: http://www.performable.com/ .. _`Piwik`: http://www.piwik.org/ -.. _`Reinvigorate`: http://www.reinvigorate.net/ +.. _`Rating\@Mail.ru`: http://top.mail.ru/ .. _`SnapEngage`: http://www.snapengage.com/ .. _`Spring Metrics`: http://www.springmetrics.com/ .. _`UserVoice`: http://www.uservoice.com/ .. _`Woopra`: http://www.woopra.com/ +.. _`Yandex.Metrica`: http://metrica.yandex.com Documentation and Support ------------------------- diff --git a/analytical/__init__.py b/analytical/__init__.py index f3a35e8..1c312c3 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -10,6 +10,6 @@ Django_ project. See the ``docs`` directory for more information. __author__ = "Joost Cassee" __email__ = "joost@cassee.net" -__version__ = "2.0.0" +__version__ = "2.2.0" __copyright__ = "Copyright (C) 2011-2016 Joost Cassee and others" __license__ = "MIT License" diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index c6ba6d6..ad4fba7 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -8,10 +8,8 @@ import logging from django import template from django.template import Node, TemplateSyntaxError -try: - from importlib import import_module -except ImportError: # Python 2.6 - from django.utils.importlib import import_module +from importlib import import_module + from analytical.utils import AnalyticalException @@ -34,11 +32,12 @@ TAG_MODULES = [ 'analytical.optimizely', 'analytical.performable', 'analytical.piwik', - 'analytical.reinvigorate', + 'analytical.rating_mailru', 'analytical.snapengage', 'analytical.spring_metrics', 'analytical.uservoice', 'analytical.woopra', + 'analytical.yandex_metrica', ] logger = logging.getLogger(__name__) diff --git a/analytical/templatetags/crazy_egg.py b/analytical/templatetags/crazy_egg.py index 5f0d830..99a58c5 100644 --- a/analytical/templatetags/crazy_egg.py +++ b/analytical/templatetags/crazy_egg.py @@ -12,7 +12,10 @@ from analytical.utils import is_internal_ip, disable_html, get_required_setting ACCOUNT_NUMBER_RE = re.compile(r'^\d+$') -SETUP_CODE = """""" +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');" @@ -36,19 +39,25 @@ def crazy_egg(parser, token): 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") + 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:]} + 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)) - vars = [(i, v) for i, v in enumerate(values, 1) if v is not None] - if vars: - js = " ".join(USERVAR_CODE % {'varnr': varnr, 'value': value} - for (varnr, value) in vars) - html = '%s\n' \ - % (html, js) + 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 diff --git a/analytical/templatetags/google_analytics.py b/analytical/templatetags/google_analytics.py index fb84772..f3a9f57 100644 --- a/analytical/templatetags/google_analytics.py +++ b/analytical/templatetags/google_analytics.py @@ -10,17 +10,13 @@ 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_domain, AnalyticalException - - -def enumerate(sequence, start=0): - """Copy of the Python 2.6 `enumerate` builtin for compatibility.""" - n = start - for elem in sequence: - yield n, elem - n += 1 - +from analytical.utils import ( + AnalyticalException, + disable_html, + get_domain, + get_required_setting, + is_internal_ip, +) TRACK_SINGLE_DOMAIN = 1 TRACK_MULTIPLE_SUBDOMAINS = 2 @@ -50,7 +46,7 @@ DOMAIN_CODE = "_gaq.push(['_setDomainName', '%s']);" NO_ALLOW_HASH_CODE = "_gaq.push(['_setAllowHash', false]);" ALLOW_LINKER_CODE = "_gaq.push(['_setAllowLinker', true]);" CUSTOM_VAR_CODE = "_gaq.push(['_setCustomVar', %(index)s, '%(name)s', " \ - "'%(value)s', %(scope)s]);" + "'%(value)s', %(scope)s]);" SITE_SPEED_CODE = "_gaq.push(['_trackPageLoadTime']);" ANONYMIZE_IP_CODE = "_gaq.push (['_gat._anonymizeIp']);" SAMPLE_RATE_CODE = "_gaq.push (['_gat._setSampleRate', '%s']);" @@ -84,8 +80,8 @@ def google_analytics(parser, token): class GoogleAnalyticsNode(Node): def __init__(self): self.property_id = get_required_setting( - 'GOOGLE_ANALYTICS_PROPERTY_ID', PROPERTY_ID_RE, - "must be a string looking like 'UA-XXXXXX-Y'") + 'GOOGLE_ANALYTICS_PROPERTY_ID', PROPERTY_ID_RE, + "must be a string looking like 'UA-XXXXXX-Y'") def render(self, context): commands = self._get_domain_commands(context) @@ -106,14 +102,15 @@ class GoogleAnalyticsNode(Node): def _get_domain_commands(self, context): commands = [] tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', - TRACK_SINGLE_DOMAIN) + 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") + 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: @@ -121,18 +118,24 @@ class GoogleAnalyticsNode(Node): return commands def _get_custom_var_commands(self, context): - values = (context.get('google_analytics_var%s' % i) - for i in range(1, 6)) - vars = [(i, v) for i, v in enumerate(values, 1) if v is not None] + 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 vars: + 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 % locals()) + commands.append(CUSTOM_VAR_CODE % { + 'index': index, + 'name': name, + 'value': value, + 'scope': scope, + }) return commands def _get_other_commands(self, context): diff --git a/analytical/templatetags/gosquared.py b/analytical/templatetags/gosquared.py index b159d9c..72edf6c 100644 --- a/analytical/templatetags/gosquared.py +++ b/analytical/templatetags/gosquared.py @@ -6,10 +6,9 @@ from __future__ import absolute_import import re -from django.conf import settings from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import get_identity, get_user_from_context, \ +from analytical.utils import get_identity, \ is_internal_ip, disable_html, get_required_setting @@ -51,7 +50,8 @@ def gosquared(parser, token): class GoSquaredNode(Node): def __init__(self): - self.site_token = get_required_setting('GOSQUARED_SITE_TOKEN', TOKEN_RE, + self.site_token = get_required_setting( + 'GOSQUARED_SITE_TOKEN', TOKEN_RE, "must be a string looking like XXX-XXXXXX-X") def render(self, context): diff --git a/analytical/templatetags/intercom.py b/analytical/templatetags/intercom.py index 5b88586..b9e7dd7 100644 --- a/analytical/templatetags/intercom.py +++ b/analytical/templatetags/intercom.py @@ -9,8 +9,8 @@ import re 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 +from analytical.utils import disable_html, get_required_setting, \ + is_internal_ip, get_user_from_context, get_identity APP_ID_RE = re.compile(r'[\da-f]+$') TRACKING_CODE = """ @@ -51,33 +51,37 @@ class IntercomNode(Node): return name def _get_custom_attrs(self, context): - vars = {} + params = {} for dict_ in context: for var, val in dict_.items(): if var.startswith('intercom_'): - vars[var[9:]] = val + params[var[9:]] = val user = get_user_from_context(context) if user is not None and user.is_authenticated(): - if 'name' not in vars: - vars['name'] = get_identity(context, 'intercom', self._identify, user) - if 'email' not in vars and user.email: - vars['email'] = user.email + 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 - vars['created_at'] = int(time.mktime(user.date_joined.timetuple())) + params['created_at'] = int(time.mktime( + user.date_joined.timetuple())) else: - vars['created_at'] = None + params['created_at'] = None - return vars + return params def render(self, context): - html = "" user = get_user_from_context(context) - vars = self._get_custom_attrs(context) - vars["app_id"] = self.app_id - html = TRACKING_CODE % {"settings_json": json.dumps(vars, sort_keys=True)} + 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') or not user or not user.is_authenticated(): + if is_internal_ip(context, 'INTERCOM') \ + or not user or not user.is_authenticated(): # Intercom is disabled for non-logged in users. html = disable_html(html, 'Intercom') return html diff --git a/analytical/templatetags/rating_mailru.py b/analytical/templatetags/rating_mailru.py new file mode 100644 index 0000000..d495824 --- /dev/null +++ b/analytical/templatetags/rating_mailru.py @@ -0,0 +1,71 @@ +""" +Rating@Mail.ru template tags and filters. +""" + +from __future__ import absolute_import + +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{7}$') +COUNTER_CODE = """ + + +""" + + +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) diff --git a/analytical/templatetags/reinvigorate.py b/analytical/templatetags/reinvigorate.py deleted file mode 100644 index 4f577bc..0000000 --- a/analytical/templatetags/reinvigorate.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Reinvigorate template tags and filters. -""" - -from __future__ import absolute_import - -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 - - -TRACKING_ID_RE = re.compile(r'^[\w\d]+-[\w\d]+$') -TRACKING_CODE = """ - - -""" - - -register = Library() - - -@register.tag -def reinvigorate(parser, token): - """ - Reinvigorate tracking template tag. - - Renders Javascript code to track page visits. You must supply - your Reinvigorate tracking ID (as a string) in the - ``REINVIGORATE_TRACKING_ID`` setting. - """ - bits = token.split_contents() - if len(bits) > 1: - raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) - return ReinvigorateNode() - - -class ReinvigorateNode(Node): - def __init__(self): - self.tracking_id = get_required_setting('REINVIGORATE_TRACKING_ID', - TRACKING_ID_RE, - "must be a string looking like XXXXX-XXXXXXXXXX") - - def render(self, context): - re_vars = {} - for dict_ in context: - for var, val in dict_.items(): - if var.startswith('reinvigorate_'): - re_vars[var[13:]] = val - if 'name' not in re_vars: - identity = get_identity(context, 'reinvigorate', - lambda u: u.get_full_name()) - if identity is not None: - re_vars['name'] = identity - if 'context' not in re_vars: - email = get_identity(context, 'reinvigorate', lambda u: u.email) - if email is not None: - re_vars['context'] = email - tags = " ".join("var re_%s_tag = %s;" % (tag, json.dumps(value, sort_keys=True)) - for tag, value in re_vars.items()) - - html = TRACKING_CODE % {'tracking_id': self.tracking_id, - 'tags': tags} - if is_internal_ip(context, 'REINVIGORATE'): - html = disable_html(html, 'Reinvigorate') - return html - - -def contribute_to_analytical(add_node): - ReinvigorateNode() # ensure properly configured - add_node('body_bottom', ReinvigorateNode) diff --git a/analytical/templatetags/spring_metrics.py b/analytical/templatetags/spring_metrics.py index 74e2767..ed2f063 100644 --- a/analytical/templatetags/spring_metrics.py +++ b/analytical/templatetags/spring_metrics.py @@ -52,8 +52,9 @@ def spring_metrics(parser, token): class SpringMetricsNode(Node): def __init__(self): - self.tracking_id = get_required_setting('SPRING_METRICS_TRACKING_ID', - TRACKING_ID_RE, "must be a hexadecimal string") + self.tracking_id = get_required_setting( + 'SPRING_METRICS_TRACKING_ID', + TRACKING_ID_RE, "must be a hexadecimal string") def render(self, context): custom = {} @@ -63,23 +64,25 @@ class SpringMetricsNode(Node): custom[var[15:]] = val if 'email' not in custom: identity = get_identity(context, 'spring_metrics', - lambda u: u.email) + 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)} + 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, vars): + def _generate_custom_javascript(self, params): commands = [] - convert = vars.pop('convert', None) + 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 vars.items()) + % (var, val) for var, val in params.items()) return " ".join(commands) diff --git a/analytical/templatetags/woopra.py b/analytical/templatetags/woopra.py index e5e2ad4..a18a71d 100644 --- a/analytical/templatetags/woopra.py +++ b/analytical/templatetags/woopra.py @@ -10,27 +10,26 @@ import re from django.conf import settings from django.template import Library, Node, TemplateSyntaxError -from analytical.utils import get_identity, get_user_from_context, \ - is_internal_ip, disable_html, get_required_setting - +from analytical.utils import ( + disable_html, + get_identity, + get_required_setting, + get_user_from_context, + is_internal_ip, +) DOMAIN_RE = re.compile(r'^\S+$') TRACKING_CODE = """ """ - register = Library() @@ -50,8 +49,9 @@ def woopra(parser, token): class WoopraNode(Node): def __init__(self): - self.domain = get_required_setting('WOOPRA_DOMAIN', DOMAIN_RE, - "must be a domain name") + self.domain = get_required_setting( + 'WOOPRA_DOMAIN', DOMAIN_RE, + "must be a domain name") def render(self, context): settings = self._get_settings(context) @@ -66,27 +66,27 @@ class WoopraNode(Node): return html def _get_settings(self, context): - vars = {'domain': self.domain} + variables = {'domain': self.domain} try: - vars['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT) + variables['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT) except AttributeError: pass - return vars + return variables def _get_visitor(self, context): - vars = {} + params = {} for dict_ in context: for var, val in dict_.items(): if var.startswith('woopra_'): - vars[var[7:]] = val - if 'name' not in vars and 'email' not in vars: + 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 user.is_authenticated(): - vars['name'] = get_identity(context, 'woopra', - self._identify, user) + params['name'] = get_identity( + context, 'woopra', self._identify, user) if user.email: - vars['email'] = user.email - return vars + params['email'] = user.email + return params def _identify(self, user): name = user.get_full_name() diff --git a/analytical/templatetags/yandex_metrica.py b/analytical/templatetags/yandex_metrica.py new file mode 100644 index 0000000..e2ec20b --- /dev/null +++ b/analytical/templatetags/yandex_metrica.py @@ -0,0 +1,93 @@ +""" +Yandex.Metrica template tags and filters. +""" + +from __future__ import absolute_import + +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 = """ + + +""" + + +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) diff --git a/analytical/tests/test_tag_rating_mailru.py b/analytical/tests/test_tag_rating_mailru.py new file mode 100644 index 0000000..2e91a60 --- /dev/null +++ b/analytical/tests/test_tag_rating_mailru.py @@ -0,0 +1,47 @@ +""" +Tests for the Rating@Mail.ru 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.rating_mailru import RatingMailruNode +from analytical.tests.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) diff --git a/analytical/tests/test_tag_reinvigorate.py b/analytical/tests/test_tag_reinvigorate.py deleted file mode 100644 index c23febd..0000000 --- a/analytical/tests/test_tag_reinvigorate.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Tests for the Reinvigorate 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.reinvigorate import ReinvigorateNode -from analytical.tests.utils import TagTestCase -from analytical.utils import AnalyticalException - - -@override_settings(REINVIGORATE_TRACKING_ID='12345-abcdefghij') -class ReinvigorateTagTestCase(TagTestCase): - """ - Tests for the ``reinvigorate`` template tag. - """ - - def test_tag(self): - r = self.render_tag('reinvigorate', 'reinvigorate') - self.assertTrue('reinvigorate.track("12345-abcdefghij");' in r, r) - - def test_node(self): - r = ReinvigorateNode().render(Context({})) - self.assertTrue('reinvigorate.track("12345-abcdefghij");' in r, r) - - @override_settings(REINVIGORATE_TRACKING_ID=None) - def test_no_tracking_id(self): - self.assertRaises(AnalyticalException, ReinvigorateNode) - - @override_settings(REINVIGORATE_TRACKING_ID='123abc') - def test_wrong_tracking_id(self): - self.assertRaises(AnalyticalException, ReinvigorateNode) - - @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) - def test_identify(self): - r = ReinvigorateNode().render(Context({'user': - User(username='test', first_name='Test', last_name='User', - email='test@example.com')})) - self.assertTrue('var re_name_tag = "Test User";' in r, r) - self.assertTrue('var re_context_tag = "test@example.com";' in r, r) - - @override_settings(ANALYTICAL_AUTO_IDENTIFY=True) - def test_identify_anonymous_user(self): - r = ReinvigorateNode().render(Context({'user': AnonymousUser()})) - self.assertFalse('var re_name_tag = ' in r, r) - self.assertFalse('var re_context_tag = ' in r, r) - - def test_tags(self): - r = ReinvigorateNode().render(Context({'reinvigorate_var1': 'val1', - 'reinvigorate_var2': 2})) - self.assertTrue(re.search('var re_var1_tag = "val1";', r), r) - self.assertTrue(re.search('var re_var2_tag = 2;', 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 = ReinvigorateNode().render(context) - self.assertTrue(r.startswith( - ''), r) diff --git a/analytical/tests/test_tag_yandex_metrica.py b/analytical/tests/test_tag_yandex_metrica.py new file mode 100644 index 0000000..f85924a --- /dev/null +++ b/analytical/tests/test_tag_yandex_metrica.py @@ -0,0 +1,47 @@ +""" +Tests for the Yandex.Metrica 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.yandex_metrica import YandexMetricaNode +from analytical.tests.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) diff --git a/analytical/tests/test_utils.py b/analytical/tests/test_utils.py index 4a4ba6a..0f4ad58 100644 --- a/analytical/tests/test_utils.py +++ b/analytical/tests/test_utils.py @@ -1,29 +1,23 @@ """ Tests for the analytical.utils module. """ -import django +# import django -from django.conf import settings +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 ( - get_domain, get_identity, is_internal_ip, get_required_setting, - AnalyticalException) + AnalyticalException, + get_domain, + get_identity, + get_required_setting, + is_internal_ip, +) from analytical.tests.utils import TestCase -try: - from unittest import skipIf -except ImportError: # Python 2.6 fallback - from unittest2 import skipIf - -try: - from django.contrib.auth.models import AbstractBaseUser -except ImportError: # Django < 1.5 fallback - AbstractBaseUser = models.Model - class SettingDeletedTestCase(TestCase): @@ -52,7 +46,6 @@ class MyUser(AbstractBaseUser): class GetIdentityTestCase(TestCase): - @skipIf(django.VERSION < (1, 5,), 'Custom usernames not supported in Django < 1.5') def test_custom_username_field(self): get_id = get_identity(Context({}), user=MyUser(identity='fake_id')) self.assertEqual(get_id, 'fake_id') diff --git a/analytical/utils.py b/analytical/utils.py index da9413a..d915cf4 100644 --- a/analytical/utils.py +++ b/analytical/utils.py @@ -75,10 +75,7 @@ def get_identity(context, prefix=None, identity_func=None, user=None): if identity_func is not None: return identity_func(user) else: - try: - return user.get_username() - except AttributeError: # Django < 1.5 fallback - return user.username + return user.get_username() except (KeyError, AttributeError): pass return None diff --git a/docs/install.rst b/docs/install.rst index b29fec3..0b25521 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -20,23 +20,29 @@ 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``:: +``easy_install``: - $ easy_install django-analytical +.. 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:: +get the development code: - $ git clone https://github.com/jcassee/django-analytical.git +.. code-block:: bash + + $ git clone https://github.com/jcassee/django-analytical.git .. _PyPI: http://pypi.python.org/pypi/django-analytical/ .. _GitHub: http://github.com/jcassee/django-analytical -Then install the package by running the setup script:: +Then install the package by running the setup script: - $ cd django-analytical - $ python setup.py install +.. code-block:: bash + + $ cd django-analytical + $ python setup.py install .. _installing-the-application: @@ -46,13 +52,15 @@ 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:: +file of your project: - INSTALLED_APPS = [ - ... - 'analytical', - ... - ] +.. code-block:: python + + INSTALLED_APPS = [ + ... + 'analytical', + ... + ] .. _adding-the-template-tags: @@ -64,7 +72,9 @@ 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:: +base template should look like this: + +.. code-block:: html {% load analytical %} @@ -101,27 +111,27 @@ settings required to enable each service are listed here: * :doc:`Chartbeat `:: - CHARTBEAT_USER_ID = '12345' + CHARTBEAT_USER_ID = '12345' * :doc:`Clickmap `:: - CLICKMAP_TRACKER_CODE = '12345678....912' + CLICKMAP_TRACKER_CODE = '12345678....912' * :doc:`Clicky `:: - CLICKY_SITE_ID = '12345678' + CLICKY_SITE_ID = '12345678' * :doc:`Crazy Egg `:: - CRAZY_EGG_ACCOUNT_NUMBER = '12345678' + CRAZY_EGG_ACCOUNT_NUMBER = '12345678' * :doc:`Gaug.es `:: - GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef' + GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef' * :doc:`Google Analytics `:: - GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8' + GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8' * :doc:`HubSpot `:: @@ -134,16 +144,16 @@ settings required to enable each service are listed here: * :doc:`KISSinsights `:: - KISS_INSIGHTS_ACCOUNT_NUMBER = '12345' - KISS_INSIGHTS_SITE_CODE = 'abc' + KISS_INSIGHTS_ACCOUNT_NUMBER = '12345' + KISS_INSIGHTS_SITE_CODE = 'abc' * :doc:`KISSmetrics `:: - KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567' + KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567' * :doc:`Mixpanel `:: - MIXPANEL_API_TOKEN = '0123456789abcdef0123456789abcdef' + MIXPANEL_API_TOKEN = '0123456789abcdef0123456789abcdef' * :doc:`Olark `:: @@ -151,7 +161,7 @@ settings required to enable each service are listed here: * :doc:`Optimizely `:: - OPTIMIZELY_ACCOUNT_NUMBER = '1234567' + OPTIMIZELY_ACCOUNT_NUMBER = '1234567' * :doc:`Performable `:: @@ -162,14 +172,17 @@ settings required to enable each service are listed here: PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path' PIWIK_SITE_ID = '123' -* :doc:`Reinvigorate `:: +* :doc:`Rating\@Mail.ru `:: - REINVIGORATE_TRACKING_ID = '12345-abcdefghij' + RATING_MAILRU_COUNTER_ID = '1234567' * :doc:`Woopra `:: WOOPRA_DOMAIN = 'abcde.com' +* :doc:`Yandex.Metrica `:: + + YANDEX_METRICA_COUNTER_ID = '12345678' ---- diff --git a/docs/services/google_analytics.rst b/docs/services/google_analytics.rst index 7add8b7..c34ba36 100644 --- a/docs/services/google_analytics.rst +++ b/docs/services/google_analytics.rst @@ -220,7 +220,7 @@ You can configure the `Sample Rate`_ feature by setting the 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 +.. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsamplerate .. _google-analytics-site-speed-sample-rate: @@ -236,7 +236,7 @@ You can configure the `Site Speed Sample Rate`_ feature by setting the 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 +.. _`Site Speed Sample Rate`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsitespeedsamplerate .. _google-analytics-session-cookie-timeout: @@ -251,7 +251,7 @@ You can configure the `Session Cookie Timeout`_ feature by setting the 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 +.. _`Session Cookie Timeout`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout .. _google-analytics-visitor-cookie-timeout: @@ -266,4 +266,4 @@ You can configure the `Visitor Cookie Timeout`_ feature by setting the 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 +.. _`Visitor Cookie Timeout`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setvisitorcookietimeout diff --git a/docs/services/rating_mailru.rst b/docs/services/rating_mailru.rst new file mode 100644 index 0000000..e905e4a --- /dev/null +++ b/docs/services/rating_mailru.rst @@ -0,0 +1,73 @@ +=================================== +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. diff --git a/docs/services/reinvigorate.rst b/docs/services/reinvigorate.rst deleted file mode 100644 index 44051d7..0000000 --- a/docs/services/reinvigorate.rst +++ /dev/null @@ -1,157 +0,0 @@ -================================ -Reinvigorate -- visitor tracking -================================ - -Reinvigorate_ gives you real-time traffic analysis, visitor activity, -search and referrer information and click heatmaps. A system tray / -system status bar application for your desktop notifies you when -interesting events occur. - -.. _Reinvigorate: http://www.reinvigorate.net/ - - -.. reinvigorate-installation: - -Installation -============ - -To start using the Reinvigorate 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 Reinvigorate 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:`reinvigorate-configuration`. - -The Reinvigorate tracking code is inserted into templates using a -template tag. Load the :mod:`reinvigorate` template tag library and -insert the :ttag:`reinvigorate` 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 somewhere within the HTML body:: - - {% load reinvigorate %} - ... - {% reinvigorate %} - - - - -.. _reinvigorate-configuration: - -Configuration -============= - -Before you can use the Reinvigorate integration, you must first set your -tracking ID. You can also customize the data that Reinvigorate tracks. - - -.. _reinvigorate-tracking-id: - -Setting the tracking ID ------------------------ - -Every website you track with Reinvigorate gets a tracking ID, and the -:ttag:`reinvigorate` tag will include it in the rendered Javascript -code. You can find the tracking ID in the URL of your website report -pages. The URL looks like this: - - \https://report.reinvigorate.net/accounts/XXXXX-XXXXXXXXXX/ - -Here, ``XXXXX-XXXXXXXXXX`` is the tracking ID. Set -:const:`REINVIGORATE_TRACKING_ID` in the project :file:`settings.py` -file:: - - REINVIGORATE_TRACKING_ID = 'XXXXX-XXXXXXXXXX' - -If you do not set a tracking ID, the tracking code will not be rendered. - - -.. _reinvigorate-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:`REINVIGORATE_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. - - -.. _reinvigorate-tags: - -Reinvigorate tags ------------------ - -As described in the Reinvigorate *NameTags* and *Snoop* pages, -the data that is tracked by Reinvigorate can be customized by adding -*tags* to the Javascript tracking code. (These should not be confused -with Django template tags.) Using template context variables, you can -let the :ttag:`reinvigorate` template tag pass reinvigorate tags to -automatically. You can set the context variables in your view when your -render a template containing the tracking code:: - - context = RequestContext({'reinvigorate_purchase': True, - 'reinvigorate_comment': 'Got discount'}) - return some_template.render(context) - -If you have tags that are generated on every page, you may want to set -them in a context processor that you add to the -:data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: - - def reinvigorate_tags(request): - try: - return {'name': 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. - -Here is a table with the most important tags. All tags listed on the -Reinvigorate pages can be set by replacing ``re_XXX_tag`` with -``reinvigorate_XXX``. - -========================= ============================================= -Context variable Description -========================= ============================================= -``reinvigorate_name`` The visitor name. -------------------------- --------------------------------------------- -``reinvigorate_context`` Some context information about the visitor, - e.g. an e-mail address. -------------------------- --------------------------------------------- -``reinvigorate_purchase`` A boolean indicating whether the visitor has - just made a purchase. Setting this variable - triggers an event in the Snoop notification - application. -------------------------- --------------------------------------------- -``reinvigorate_new_user`` A boolean indicating whether the visitor has - just registered as a new user. Setting this - variable triggers an event in the Snoop - notification application. -------------------------- --------------------------------------------- -``reinvigorate_comment`` A comment, which is included in a Snoop - event notification. -========================= ============================================= - - -.. _reinvigorate-identify-user: - -Identifying authenticated users -------------------------------- - -If you have not set the ``reinvigorate_name`` context variable -explicitly, the full name of an authenticated user is passed to -Reinvigorate automatically. Similarly, the e-mail address is passed -automatically in the ``context`` tag. See :ref:`identifying-visitors`. - - ----- - -Thanks go to Reinvigorate for their support with the development of this -application. diff --git a/docs/services/yandex_metrica.rst b/docs/services/yandex_metrica.rst new file mode 100644 index 0000000..d9861bb --- /dev/null +++ b/docs/services/yandex_metrica.rst @@ -0,0 +1,84 @@ +================================== +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. diff --git a/tox.ini b/tox.ini index 3f6996d..5f71e15 100644 --- a/tox.ini +++ b/tox.ini @@ -14,5 +14,6 @@ deps = django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 + virtualenv<14.0.0 passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH whitelist_externals = sh