diff --git a/AUTHORS.rst b/AUTHORS.rst index d33524f..46ced5b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -22,3 +22,4 @@ The work on Crazy Egg was made possible by `Bateau Knowledge`_. .. _`Tinnet Coronam`: https://github.com/tinnet .. _Analytical: https://github.com/jkrall/analytical .. _`Bateau Knowledge`: http://www.bateauknowledge.nl/ +.. _`arteria GmbH`: mailto:admin@arteria.ch diff --git a/README.rst b/README.rst index 1733dde..db5253b 100644 --- a/README.rst +++ b/README.rst @@ -20,6 +20,7 @@ an asynchronous version of the Javascript code if possible. Currently supported services: * `Chartbeat`_ traffic analysis +* `Clickmap`_ visual click tracking * `Clicky`_ traffic analysis * `Crazy Egg`_ visual click tracking * `Gaug.es`_ realtime traffic tracking @@ -51,6 +52,7 @@ an issue to discuss your plans. .. _`Django`: http://www.djangoproject.com/ .. _`Chartbeat`: http://www.chartbeat.com/ +.. _`Clickmap`: http://getclickmap.com/ .. _`Clicky`: http://getclicky.com/ .. _`Crazy Egg`: http://www.crazyegg.com/ .. _`Gaug.es`: http://gaug.es/ diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index 37bc6ae..51ee382 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -16,6 +16,7 @@ TAG_LOCATIONS = ['head_top', 'head_bottom', 'body_top', 'body_bottom'] TAG_POSITIONS = ['first', None, 'last'] TAG_MODULES = [ 'analytical.chartbeat', + 'analytical.clickmap', 'analytical.clicky', 'analytical.crazy_egg', 'analytical.gauges', diff --git a/analytical/templatetags/clickmap.py b/analytical/templatetags/clickmap.py new file mode 100644 index 0000000..fafe8d7 --- /dev/null +++ b/analytical/templatetags/clickmap.py @@ -0,0 +1,79 @@ +""" +Clickmap template tags and filters. +""" + +from __future__ import absolute_import + +import re + +from django.template import Library, Node, TemplateSyntaxError +from django.utils import simplejson + +from analytical.utils import get_identity, is_internal_ip, disable_html, get_required_setting + + +CLICKMAP_TRACKER_ID_RE = re.compile(r'^\d+$') +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 a (string containing) a number") + + def render(self, context): + """custom = {} + for dict_ in context: + for var, val in dict_.items(): + if var.startswith('clickmap_'): + custom[var[7:]] = val + if 'username' not in custom.get('session', {}): + identity = get_identity(context, 'clickmap') + if identity is not None: + custom.setdefault('session', {})['username'] = identity + + html = TRACKING_CODE % {'site_id': self.site_id, + 'custom': simplejson.dumps(custom)} + if is_internal_ip(context, 'CLICKMAP'): + html = disable_html(html, 'clickmap') + return html + """ + html = TRACKING_CODE % {'portal_id': self.portal_id, + 'domain': self.domain} + 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) diff --git a/analytical/tests/__init__.py b/analytical/tests/__init__.py index 1b1d4a7..634d29d 100644 --- a/analytical/tests/__init__.py +++ b/analytical/tests/__init__.py @@ -4,6 +4,7 @@ Tests for django-analytical. from analytical.tests.test_tag_analytical import * from analytical.tests.test_tag_chartbeat import * +from analytical.tests.test_tag_clickmap import * from analytical.tests.test_tag_clicky import * from analytical.tests.test_tag_crazy_egg import * from analytical.tests.test_tag_gauges import * diff --git a/analytical/tests/test_tag_clickmap.py b/analytical/tests/test_tag_clickmap.py new file mode 100644 index 0000000..bdfc566 --- /dev/null +++ b/analytical/tests/test_tag_clickmap.py @@ -0,0 +1,46 @@ +""" +Tests for the Clickmap 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 analytical.templatetags.clicky import ClickmapNode +from analytical.tests.utils import TagTestCase, override_settings, SETTING_DELETED +from analytical.utils import AnalyticalException + + +@override_settings(CLICKMAP_TRACKER_ID='12345') +class ClickyTagTestCase(TagTestCase): + """ + Tests for the ``clickmap`` template tag. + """ + + def test_tag(self): + r = self.render_tag('clicjmap', 'clickmap') + self.assertTrue("tracker: '12345', version:'2'};" in r, r) + + def test_node(self): + r = ClickmapNode().render(Context({})) + self.assertTrue("tracker: '12345', version:'2'};" in r, r) + + @override_settings(CLICKMAP_TRACKER_ID=SETTING_DELETED) + def test_no_site_id(self): + self.assertRaises(AnalyticalException, ClickmapNode) + + @override_settings(CLICKMAP_TRACKER_ID='abc') + def test_wrong_site_id(self): + self.assertRaises(AnalyticalException, ClickyNode) + + @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) diff --git a/docs/install.rst b/docs/install.rst index 8be6bee..4490d43 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -103,6 +103,10 @@ settings required to enable each service are listed here: CHARTBEAT_USER_ID = '12345' +* :doc:`Clickmap `:: + + CLICKMAP_TRACKER_CODE = '12345678....912' + * :doc:`Clicky `:: CLICKY_SITE_ID = '12345678' diff --git a/docs/services/clickmap.rst b/docs/services/clickmap.rst new file mode 100644 index 0000000..5ee0192 --- /dev/null +++ b/docs/services/clickmap.rst @@ -0,0 +1,77 @@ +================================== +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.