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