Merge pull request #40 from bittner/master

Added Piwik open source web analytics
This commit is contained in:
Joost Cassee 2014-07-06 21:14:28 +02:00
commit bf0da79846
8 changed files with 266 additions and 3 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
/*.geany
/.idea
/.tox
/build

View file

@ -34,6 +34,7 @@ Currently supported services:
* `Olark`_ visitor chat
* `Optimizely`_ A/B testing
* `Performable`_ web analytics and landing pages
* `Piwik`_ open source web analytics
* `Reinvigorate`_ visitor tracking
* `SnapEngage`_ live chat
* `Spring Metrics`_ conversion tracking
@ -67,6 +68,7 @@ an issue to discuss your plans.
.. _`Olark`: http://www.olark.com/
.. _`Optimizely`: http://www.optimizely.com/
.. _`Performable`: http://www.performable.com/
.. _`Piwik`: http://www.piwik.org/
.. _`Reinvigorate`: http://www.reinvigorate.net/
.. _`SnapEngage`: http://www.snapengage.com/
.. _`Spring Metrics`: http://www.springmetrics.com/

View file

@ -30,6 +30,7 @@ TAG_MODULES = [
'analytical.olark',
'analytical.optimizely',
'analytical.performable',
'analytical.piwik',
'analytical.reinvigorate',
'analytical.snapengage',
'analytical.spring_metrics',
@ -37,7 +38,6 @@ TAG_MODULES = [
'analytical.woopra',
]
logger = logging.getLogger(__name__)
register = template.Library()
@ -48,8 +48,10 @@ def _location_tag(location):
if len(bits) > 1:
raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0])
return AnalyticalNode(location)
return analytical_tag
for loc in TAG_LOCATIONS:
register.tag('analytical_%s' % loc, _location_tag(loc))
@ -64,9 +66,11 @@ class AnalyticalNode(Node):
def _load_template_nodes():
template_nodes = dict((l, dict((p, []) for p in TAG_POSITIONS))
for l in TAG_LOCATIONS)
for l in TAG_LOCATIONS)
def add_node_cls(location, node, position=None):
template_nodes[location][position].append(node)
for path in TAG_MODULES:
module = _import_tag_module(path)
try:
@ -75,11 +79,13 @@ def _load_template_nodes():
logger.debug("not loading tags from '%s': %s", path, e)
for location in TAG_LOCATIONS:
template_nodes[location] = sum((template_nodes[location][p]
for p in TAG_POSITIONS), [])
for p in TAG_POSITIONS), [])
return template_nodes
def _import_tag_module(path):
app_name, lib_name = path.rsplit('.', 1)
return import_module("%s.templatetags.%s" % (app_name, lib_name))
template_nodes = _load_template_nodes()

View file

@ -0,0 +1,78 @@
"""
Piwik template tags and filters.
"""
from __future__ import absolute_import
import re
from django.template import Library, Node, TemplateSyntaxError
from analytical.utils import is_internal_ip, disable_html, get_required_setting
# domain name (characters separated by a dot), optional URI path, no slash
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)+[^./?#@:]+)+(/[^/?#@:]+)*$')
# numeric ID
SITEID_RE = re.compile(r'^\d+$')
TRACKING_CODE = """
<script type="text/javascript">
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u=(("https:" == document.location.protocol) ? "https" : "http") + "://%(url)s/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', %(siteid)s]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
g.defer=true; g.async=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="http://%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
""" # noqa
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.
"""
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):
html = TRACKING_CODE % {
'url': self.domain_path,
'siteid': self.site_id,
}
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)

View file

@ -18,6 +18,7 @@ from analytical.tests.test_tag_mixpanel import *
from analytical.tests.test_tag_olark import *
from analytical.tests.test_tag_optimizely import *
from analytical.tests.test_tag_performable import *
from analytical.tests.test_tag_piwik import *
from analytical.tests.test_tag_reinvigorate import *
from analytical.tests.test_tag_snapengage import *
from analytical.tests.test_tag_spring_metrics import *

View file

@ -0,0 +1,69 @@
"""
Tests for the Piwik template tags and filters.
"""
from django.http import HttpRequest
from django.template import Context
from analytical.templatetags.piwik import PiwikNode
from analytical.tests.utils import TagTestCase, override_settings, \
SETTING_DELETED
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(' ? "https" : "http") + "://example.com/";' in r, r)
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
self.assertTrue('img src="http://example.com/piwik.php?idsite=345"'
in r, r)
def test_node(self):
r = PiwikNode().render(Context({}))
self.assertTrue(' ? "https" : "http") + "://example.com/";' in r, r)
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
self.assertTrue('img src="http://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(' ? "https" : "http") + "://example.com/piwik/";' in r,
r)
@override_settings(PIWIK_DOMAIN_PATH=SETTING_DELETED)
def test_no_domain(self):
self.assertRaises(AnalyticalException, PiwikNode)
@override_settings(PIWIK_SITE_ID=SETTING_DELETED)
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(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(
'<!-- Piwik disabled on internal IP address'), r)
self.assertTrue(r.endswith('-->'), r)

View file

@ -157,6 +157,11 @@ settings required to enable each service are listed here:
PERFORMABLE_API_KEY = '123abc'
* :doc:`Piwik <services/piwik>`::
PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path'
PIWIK_SITE_ID = '123'
* :doc:`Reinvigorate <services/reinvigorate>`::
REINVIGORATE_TRACKING_ID = '12345-abcdefghij'

100
docs/services/piwik.rst Normal file
View file

@ -0,0 +1,100 @@
==================================
Piwik -- 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/
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 %}
</body>
</html>
.. _`Piwik best practice for integration`: 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 your tracking::
PIWIK_SITE_ID = '4'
If you do not set the site ID 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:`ANALYTICAL_INTERNAL_IPS` by
default (which takes the value of :const:`INTERNAL_IPS` by default).
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/