mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Add support for the Woopra service
Also bump the version to 0.7.0.
This commit is contained in:
parent
e4a53def30
commit
babfad16ac
10 changed files with 388 additions and 8 deletions
|
|
@ -1,9 +1,10 @@
|
|||
Version 0.7.0
|
||||
-------------
|
||||
* Fixed Crazy Egg template ``<script>`` tag.
|
||||
* 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 ``<script>`` tag for Crazy Egg.
|
||||
|
||||
.. _mixpanel-celery: https://github.com/winhamwr/mixpanel-celery
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ Currently supported services:
|
|||
* `Optimizely`_ A/B testing
|
||||
* `Performable`_ web analytics and landing pages
|
||||
* `Reinvigorate`_ visitor tracking
|
||||
* `Woopra`_ web analytics
|
||||
|
||||
The documentation can be found in the ``docs`` directory or `read
|
||||
online`_. The source code and issue tracker are generously `hosted by
|
||||
|
|
@ -56,6 +57,7 @@ an issue to discuss your plans.
|
|||
.. _Optimizely: http://www.optimizely.com/
|
||||
.. _Performable: http://www.performable.com/
|
||||
.. _Reinvigorate: http://www.reinvigorate.com/
|
||||
.. _Woopra: http://www.woopra.com/
|
||||
.. _`read online`: http://packages.python.org/django-analytical/
|
||||
.. _`hosted by GitHub`: http://github.com/jcassee/django-analytical
|
||||
.. _`issue tracker`: http://github.com/jcassee/django-analytical/issues
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ Django_ project. See the ``docs`` directory for more information.
|
|||
|
||||
__author__ = "Joost Cassee"
|
||||
__email__ = "joost@cassee.net"
|
||||
__version__ = "0.6.0"
|
||||
__version__ = "0.7.0"
|
||||
__copyright__ = "Copyright (C) 2011 Joost Cassee"
|
||||
__license__ = "MIT License"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ TAG_MODULES = [
|
|||
'analytical.optimizely',
|
||||
'analytical.performable',
|
||||
'analytical.reinvigorate',
|
||||
'analytical.woopra',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
99
analytical/templatetags/woopra.py
Normal file
99
analytical/templatetags/woopra.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"""
|
||||
Clicky template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
from django.utils import simplejson
|
||||
|
||||
from analytical.utils import get_identity, get_user_from_context, \
|
||||
is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
|
||||
DOMAIN_RE = re.compile(r'^\S+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
var woo_settings = %(settings)s;
|
||||
var woo_visitor = %(visitor)s;
|
||||
(function(){
|
||||
var wsc=document.createElement('script');
|
||||
wsc.type='text/javascript';
|
||||
wsc.src=document.location.protocol+'//static.woopra.com/js/woopra.js';
|
||||
wsc.async=true;
|
||||
var ssc = document.getElementsByTagName('script')[0];
|
||||
ssc.parentNode.insertBefore(wsc, ssc);
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
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': simplejson.dumps(settings),
|
||||
'visitor': simplejson.dumps(visitor),
|
||||
}
|
||||
if is_internal_ip(context, 'WOOPRA'):
|
||||
html = disable_html(html, 'Woopra')
|
||||
return html
|
||||
|
||||
def _get_settings(self, context):
|
||||
vars = {'domain': self.domain}
|
||||
try:
|
||||
vars['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT)
|
||||
except AttributeError:
|
||||
pass
|
||||
return vars
|
||||
|
||||
def _get_visitor(self, context):
|
||||
vars = {}
|
||||
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:
|
||||
user = get_user_from_context(context)
|
||||
if user is not None:
|
||||
vars['name'] = get_identity(context, 'woopra',
|
||||
self._identify, user)
|
||||
if user.email:
|
||||
vars['email'] = user.email
|
||||
return vars
|
||||
|
||||
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)
|
||||
|
|
@ -15,4 +15,5 @@ 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_reinvigorate import *
|
||||
from analytical.tests.test_tag_woopra import *
|
||||
from analytical.tests.test_utils import *
|
||||
|
|
|
|||
84
analytical/tests/test_tag_woopra.py
Normal file
84
analytical/tests/test_tag_woopra.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
"""
|
||||
Tests for the Woopra template tags and filters.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
|
||||
from analytical.templatetags.woopra import WoopraNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
class WoopraTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``woopra`` template tag.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(WoopraTagTestCase, self).setUp()
|
||||
self.settings_manager.set(WOOPRA_DOMAIN='example.com')
|
||||
|
||||
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)
|
||||
|
||||
def test_no_domain(self):
|
||||
self.settings_manager.set(WOOPRA_DOMAIN='this is not a domain')
|
||||
self.assertRaises(AnalyticalException, WoopraNode)
|
||||
|
||||
def test_wrong_domain(self):
|
||||
self.settings_manager.delete('WOOPRA_DOMAIN')
|
||||
self.assertRaises(AnalyticalException, WoopraNode)
|
||||
|
||||
def test_idle_timeout(self):
|
||||
self.settings_manager.set(WOOPRA_IDLE_TIMEOUT=1234)
|
||||
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)
|
||||
|
||||
def test_identify_name_and_email(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = WoopraNode().render(Context({'user': User(username='test',
|
||||
first_name='Firstname', last_name='Lastname',
|
||||
email="test@example.com")}))
|
||||
self.assertTrue('var woo_visitor = {"name": "Firstname Lastname", '
|
||||
'"email": "test@example.com"};' in r, r)
|
||||
|
||||
def test_identify_username_no_email(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = WoopraNode().render(Context({'user': User(username='test')}))
|
||||
self.assertTrue('var woo_visitor = {"name": "test"};' in r, r)
|
||||
|
||||
def test_no_identify_when_explicit_name(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = WoopraNode().render(Context({'woopra_name': 'explicit',
|
||||
'user': User(username='implicit')}))
|
||||
self.assertTrue('var woo_visitor = {"name": "explicit"};' in r, r)
|
||||
|
||||
def test_no_identify_when_explicit_email(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = WoopraNode().render(Context({'woopra_email': 'explicit',
|
||||
'user': User(username='implicit')}))
|
||||
self.assertTrue('var woo_visitor = {"email": "explicit"};' in r, r)
|
||||
|
||||
def test_render_internal_ip(self):
|
||||
self.settings_manager.set(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = WoopraNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Woopra disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
|
@ -27,7 +27,26 @@ def get_required_setting(setting, value_re, invalid_msg):
|
|||
return value
|
||||
|
||||
|
||||
def get_identity(context, prefix=None, identity_func=None):
|
||||
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_identity(context, prefix=None, identity_func=None, user=None):
|
||||
"""
|
||||
Get the identity of a logged in user from a template context.
|
||||
|
||||
|
|
@ -47,11 +66,8 @@ def get_identity(context, prefix=None, identity_func=None):
|
|||
pass
|
||||
if getattr(settings, 'ANALYTICAL_AUTO_IDENTIFY', True):
|
||||
try:
|
||||
try:
|
||||
user = context['user']
|
||||
except KeyError:
|
||||
request = context['request']
|
||||
user = request.user
|
||||
if user is None:
|
||||
user = get_user_from_context(context)
|
||||
if user.is_authenticated():
|
||||
if identity_func is not None:
|
||||
return identity_func(user)
|
||||
|
|
|
|||
|
|
@ -149,6 +149,10 @@ settings required to enable each service are listed here:
|
|||
|
||||
REINVIGORATE_TRACKING_ID = '12345-abcdefghij'
|
||||
|
||||
* :doc:`Woopra <services/woopra>`::
|
||||
|
||||
WOOPRA_DOMAIN = 'abcde.com'
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
|
|
|||
172
docs/services/woopra.rst
Normal file
172
docs/services/woopra.rst
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
===========================
|
||||
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 %}
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
{% woopra %}
|
||||
</head>
|
||||
...
|
||||
|
||||
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.
|
||||
Loading…
Reference in a new issue