mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Updated analytical
* Added Chartbeat. * Added settings context processor. * Added identification. * Prepared events. * Updated tests.
This commit is contained in:
parent
005cb2da40
commit
39c205f546
37 changed files with 910 additions and 235 deletions
|
|
@ -4,6 +4,7 @@ django-analytical
|
|||
The django-analytical application integrates various analytics services
|
||||
into a Django_ project. Currently supported services:
|
||||
|
||||
* `Chartbeat`_ -- traffic analysis
|
||||
* `Clicky`_ -- traffic analysis
|
||||
* `Crazy Egg`_ -- visual click tracking
|
||||
* `Google Analytics`_ traffic analysis
|
||||
|
|
@ -23,6 +24,7 @@ Joshua Krall's all-purpose analytics front-end for Rails. The work on
|
|||
Crazy Egg was made possible by `Bateau Knowledge`_.
|
||||
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
.. _Chartbeat: http://www.chartbeat.com/
|
||||
.. _Clicky: http://getclicky.com/
|
||||
.. _`Crazy Egg`: http://www.crazyegg.com/
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
"""
|
||||
========================================
|
||||
Analytics service integration for Django
|
||||
========================================
|
||||
|
||||
The django-clicky application integrates Clicky_ analytics into a
|
||||
Django_ project.
|
||||
The django-analytical application integrates analytics services into a
|
||||
Django_ project. See the ``docs`` directory for more information.
|
||||
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
"""
|
||||
|
||||
__author__ = "Joost Cassee"
|
||||
|
|
@ -12,3 +13,10 @@ __email__ = "joost@cassee.net"
|
|||
__version__ = "0.1.0alpha"
|
||||
__copyright__ = "Copyright (C) 2011 Joost Cassee"
|
||||
__license__ = "MIT License"
|
||||
|
||||
try:
|
||||
from collections import namedtuple
|
||||
except ImportError:
|
||||
namedtuple = lambda name, fields: lambda *values: values
|
||||
|
||||
Property = namedtuple('Property', ['num', 'name', 'value'])
|
||||
|
|
|
|||
33
analytical/context_processors.py
Normal file
33
analytical/context_processors.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
Context processors for django-analytical.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
IMPORT_SETTINGS = [
|
||||
'ANALYTICAL_INTERNAL_IPS',
|
||||
'ANALYTICAL_SERVICES',
|
||||
'CHARTBEAT_USER_ID',
|
||||
'CLICKY_SITE_ID',
|
||||
'CRAZY_EGG_ACCOUNT_NUMBER',
|
||||
'GOOGLE_ANALYTICS_PROPERTY_ID',
|
||||
'KISS_INSIGHTS_ACCOUNT_NUMBER',
|
||||
'KISS_INSIGHTS_SITE_CODE',
|
||||
'KISS_METRICS_API_KEY',
|
||||
'MIXPANEL_TOKEN',
|
||||
'OPTIMIZELY_ACCOUNT_NUMBER',
|
||||
]
|
||||
|
||||
|
||||
def settings(request):
|
||||
"""
|
||||
Import all django-analytical settings into the template context.
|
||||
"""
|
||||
vars = {}
|
||||
for setting in IMPORT_SETTINGS:
|
||||
try:
|
||||
vars[setting] = getattr(settings, setting)
|
||||
except AttributeError:
|
||||
pass
|
||||
return vars
|
||||
|
|
@ -12,20 +12,21 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
_log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SERVICES = [
|
||||
'analytical.services.chartbeat.ChartbeatService',
|
||||
'analytical.services.clicky.ClickyService',
|
||||
'analytical.services.crazyegg.CrazyEggService',
|
||||
'analytical.services.crazy_egg.CrazyEggService',
|
||||
'analytical.services.google_analytics.GoogleAnalyticsService',
|
||||
'analytical.services.kissinsights.KissInsightsService',
|
||||
'analytical.services.kissmetrics.KissMetricsService',
|
||||
'analytical.services.kiss_insights.KissInsightsService',
|
||||
'analytical.services.kiss_metrics.KissMetricsService',
|
||||
'analytical.services.mixpanel.MixpanelService',
|
||||
'analytical.services.optimizely.OptimizelyService',
|
||||
]
|
||||
|
||||
|
||||
enabled_services = None
|
||||
def get_enabled_services(reload=False):
|
||||
def get_enabled_services():
|
||||
global enabled_services
|
||||
if enabled_services is None or reload:
|
||||
if enabled_services is None:
|
||||
enabled_services = load_services()
|
||||
return enabled_services
|
||||
|
||||
|
|
@ -39,19 +40,7 @@ def load_services():
|
|||
autoload = True
|
||||
for path in service_paths:
|
||||
try:
|
||||
module, attr = path.rsplit('.', 1)
|
||||
try:
|
||||
mod = import_module(module)
|
||||
except ImportError, e:
|
||||
raise ImproperlyConfigured(
|
||||
'error importing analytical service %s: "%s"'
|
||||
% (module, e))
|
||||
try:
|
||||
service = getattr(mod, attr)()
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
'module "%s" does not define service "%s"'
|
||||
% (module, attr))
|
||||
service = _load_service(path)
|
||||
enabled_services.append(service)
|
||||
except ImproperlyConfigured, e:
|
||||
if autoload:
|
||||
|
|
@ -60,3 +49,18 @@ def load_services():
|
|||
else:
|
||||
raise
|
||||
return enabled_services
|
||||
|
||||
def _load_service(path):
|
||||
module, attr = path.rsplit('.', 1)
|
||||
try:
|
||||
mod = import_module(module)
|
||||
except ImportError, e:
|
||||
raise ImproperlyConfigured(
|
||||
'error importing analytical service %s: "%s"' % (module, e))
|
||||
try:
|
||||
service = getattr(mod, attr)()
|
||||
except (AttributeError, TypeError):
|
||||
raise ImproperlyConfigured(
|
||||
'module "%s" does not define callable service "%s"'
|
||||
% (module, attr))
|
||||
return service
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
|
||||
HTML_COMMENT = "<!-- %(message):%(sep)s%(html)s%(sep)s-->"
|
||||
JS_COMMENT = "*/ %(message):%(sep)s%(html)s%(sep)s*/"
|
||||
IDENTITY_CONTEXT_KEY = 'analytical_identity'
|
||||
|
||||
|
||||
class AnalyticalService(object):
|
||||
"""
|
||||
Analytics service.
|
||||
|
|
@ -14,8 +20,6 @@ class AnalyticalService(object):
|
|||
func_name = "render_%s" % location
|
||||
func = getattr(self, func_name)
|
||||
html = func(context)
|
||||
if self.is_initialized(context):
|
||||
pass
|
||||
return html
|
||||
|
||||
def render_head_top(self, context):
|
||||
|
|
@ -30,6 +34,9 @@ class AnalyticalService(object):
|
|||
def render_body_bottom(self, context):
|
||||
return ""
|
||||
|
||||
def render_event(self, name, properties):
|
||||
return ""
|
||||
|
||||
def get_required_setting(self, setting, value_re, invalid_msg):
|
||||
try:
|
||||
value = getattr(settings, setting)
|
||||
|
|
@ -37,5 +44,45 @@ class AnalyticalService(object):
|
|||
raise ImproperlyConfigured("%s setting: not found" % setting)
|
||||
value = str(value)
|
||||
if not value_re.search(value):
|
||||
raise ImproperlyConfigured("%s setting: %s" % (value, invalid_msg))
|
||||
raise ImproperlyConfigured("%s setting: %s: '%s'"
|
||||
% (setting, invalid_msg, value))
|
||||
return value
|
||||
|
||||
def get_identity(self, context):
|
||||
try:
|
||||
return context[IDENTITY_CONTEXT_KEY]
|
||||
except KeyError:
|
||||
pass
|
||||
if getattr(settings, 'ANALYTICAL_AUTO_IDENTIFY', True):
|
||||
try:
|
||||
try:
|
||||
user = context['user']
|
||||
except KeyError:
|
||||
request = context['request']
|
||||
user = request.user
|
||||
if user.is_authenticated():
|
||||
return user.username
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_events(self, context):
|
||||
return context.get('analytical_events', {})
|
||||
|
||||
def get_properties(self, context):
|
||||
return context.get('analytical_properties', {})
|
||||
|
||||
def _html_comment(self, html, message=""):
|
||||
return self._comment(HTML_COMMENT, html, message)
|
||||
|
||||
def _js_comment(self, html, message=""):
|
||||
return self._comment(JS_COMMENT, html, message)
|
||||
|
||||
def _comment(self, format, html, message):
|
||||
if not message:
|
||||
message = "Disabled"
|
||||
if message.find('\n') > -1:
|
||||
sep = '\n'
|
||||
else:
|
||||
sep = ' '
|
||||
return format % {'message': message, 'html': html, 'sep': sep}
|
||||
|
|
|
|||
66
analytical/services/chartbeat.py
Normal file
66
analytical/services/chartbeat.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
Chartbeat service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.sites.models import Site, RequestSite
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
USER_ID_RE = re.compile(r'^\d{5}$')
|
||||
INIT_CODE = """<script type="text/javascript">var _sf_startpt=(new Date()).getTime()</script>"""
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
var _sf_async_config={uid:%(user_id)s,domain:"%(domain)s"};
|
||||
(function(){
|
||||
function loadChartbeat() {
|
||||
window._sf_endpt=(new Date()).getTime();
|
||||
var e = document.createElement('script');
|
||||
e.setAttribute('language', 'javascript');
|
||||
e.setAttribute('type', 'text/javascript');
|
||||
e.setAttribute('src',
|
||||
(("https:" == document.location.protocol) ? "https://a248.e.akamai.net/chartbeat.download.akamai.com/102508/" : "http://static.chartbeat.com/") +
|
||||
"js/chartbeat.js");
|
||||
document.body.appendChild(e);
|
||||
}
|
||||
var oldonload = window.onload;
|
||||
window.onload = (typeof window.onload != 'function') ?
|
||||
loadChartbeat : function() { oldonload(); loadChartbeat(); };
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
DOMAIN_CONTEXT_KEY = 'chartbeat_domain'
|
||||
|
||||
|
||||
class ChartbeatService(AnalyticalService):
|
||||
def __init__(self):
|
||||
self.user_id = self.get_required_setting(
|
||||
'CHARTBEAT_USER_ID', USER_ID_RE,
|
||||
"must be a string containing an five-digit number")
|
||||
|
||||
def render_head_top(self, context):
|
||||
return INIT_CODE
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return SETUP_CODE % {'user_id': self.user_id,
|
||||
'domain': self._get_domain(context)}
|
||||
|
||||
def _get_domain(self, context):
|
||||
try:
|
||||
return context[DOMAIN_CONTEXT_KEY]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
return Site.objects.get_current().domain
|
||||
except ImproperlyConfigured:
|
||||
pass
|
||||
try:
|
||||
request = context['request']
|
||||
return RequestSite(request).domain
|
||||
except (KeyError, AttributeError):
|
||||
raise KeyError("could not find access either '%s' or 'request' "
|
||||
"in the template context and 'django.contrib.sites' is "
|
||||
"not in INSTALLED_APPS" % DOMAIN_CONTEXT_KEY)
|
||||
|
|
@ -4,14 +4,17 @@ Clicky service.
|
|||
|
||||
import re
|
||||
|
||||
from django.utils import simplejson
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
SITE_ID_RE = re.compile(r'^\d{8}$')
|
||||
TRACKING_CODE = """
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
var clicky = { log: function(){ return; }, goal: function(){ return; }};
|
||||
var clicky_site_id = %(site_id)s;
|
||||
var clicky_custom = %(custom)s;
|
||||
(function() {
|
||||
var s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
|
|
@ -22,14 +25,23 @@ TRACKING_CODE = """
|
|||
</script>
|
||||
<noscript><p><img alt="Clicky" width="1" height="1" src="http://in.getclicky.com/%(site_id)sns.gif" /></p></noscript>
|
||||
"""
|
||||
CUSTOM_CONTEXT_KEY = 'clicky_custom'
|
||||
|
||||
|
||||
class ClickyService(AnalyticalService):
|
||||
KEY = 'clicky'
|
||||
|
||||
def __init__(self):
|
||||
self.site_id = self.get_required_setting('CLICKY_SITE_ID', SITE_ID_RE,
|
||||
"must be a string containing an eight-digit number")
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return TRACKING_CODE % {'site_id': self.site_id}
|
||||
custom = {
|
||||
'session': {
|
||||
'username': self.get_identity(context),
|
||||
}
|
||||
}
|
||||
custom.update(context.get(CUSTOM_CONTEXT_KEY, {}))
|
||||
return SETUP_CODE % {'site_id': self.site_id,
|
||||
'custom': simplejson.dumps(custom)}
|
||||
|
||||
def _convert_properties(self):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -8,23 +8,35 @@ from analytical.services.base import AnalyticalService
|
|||
DEBUG_CODE = """
|
||||
<script type="text/javascript">
|
||||
if(typeof(console) !== 'undefined' && console != null) {
|
||||
console.log('Analytical: rendering analytical_%(location)s tag');
|
||||
%s
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
LOG_CODE_ANONYMOUS = """
|
||||
console.log('Analytical: rendering analytical_%(location)s tag');
|
||||
"""
|
||||
LOG_CODE_IDENTIFIED = """
|
||||
console.log('Analytical: rendering analytical_%(location)s tag for user %(identity)s');
|
||||
"""
|
||||
|
||||
|
||||
class ConsoleService(AnalyticalService):
|
||||
KEY = 'console'
|
||||
|
||||
def render_head_top(self, context):
|
||||
return DEBUG_CODE % {'location': 'head_top'}
|
||||
return self._render_code('head_top', context)
|
||||
|
||||
def render_head_bottom(self, context):
|
||||
return DEBUG_CODE % {'location': 'head_bottom'}
|
||||
return self._render_code('head_bottom', context)
|
||||
|
||||
def render_body_top(self, context):
|
||||
return DEBUG_CODE % {'location': 'body_top'}
|
||||
return self._render_code('body_top', context)
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return DEBUG_CODE % {'location': 'body_bottom'}
|
||||
return self._render_code('body_bottom', context)
|
||||
|
||||
def _render_code(self, location, context):
|
||||
vars = {'location': location, 'identity': self.get_identity(context)}
|
||||
if vars['identity'] is None:
|
||||
debug_code = DEBUG_CODE % LOG_CODE_ANONYMOUS
|
||||
else:
|
||||
debug_code = DEBUG_CODE % LOG_CODE_IDENTIFIED
|
||||
return debug_code % vars
|
||||
|
|
|
|||
|
|
@ -8,17 +8,24 @@ from analytical.services.base import AnalyticalService
|
|||
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d{8}$')
|
||||
TRACK_CODE = """<script type="text/javascript" src="//dnn506yrbagrg.cloudfront.net/pages/scripts/%(account_nr_1)s/%(account_nr_2)s.js"</script>"""
|
||||
SETUP_CODE = """<script type="text/javascript" src="//dnn506yrbagrg.cloudfront.net/pages/scripts/%(account_nr_1)s/%(account_nr_2)s.js"</script>"""
|
||||
USERVAR_CODE = "CE2.set(%(varnr)d, '%(value)s');"
|
||||
USERVAR_CONTEXT_VAR = 'crazy_egg_uservars'
|
||||
|
||||
|
||||
class CrazyEggService(AnalyticalService):
|
||||
KEY = 'crazy_egg'
|
||||
|
||||
def __init__(self):
|
||||
self.account_nr = self.get_required_setting('CRAZY_EGG_ACCOUNT_NUMBER',
|
||||
ACCOUNT_NUMBER_RE,
|
||||
"must be a string containing an eight-digit number")
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return TRACK_CODE % {'account_nr_1': self.account_nr[:4],
|
||||
html = SETUP_CODE % {'account_nr_1': self.account_nr[:4],
|
||||
'account_nr_2': self.account_nr[4:]}
|
||||
uservars = context.get(USERVAR_CONTEXT_VAR, {})
|
||||
if uservars:
|
||||
js = "".join(USERVAR_CODE % {'varnr': varnr, 'value': value}
|
||||
for (varnr, value) in uservars.items())
|
||||
html = '%s\n<script type="text/javascript">%s</script>' \
|
||||
% (html, js)
|
||||
return html
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ from analytical.services.base import AnalyticalService
|
|||
|
||||
|
||||
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
||||
TRACKING_CODE = """
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '%(property_id)s']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
%(commands)s
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
|
|
@ -23,15 +23,36 @@ TRACKING_CODE = """
|
|||
|
||||
</script>
|
||||
"""
|
||||
|
||||
TRACK_CODE = "_gaq.push(['_trackPageview']);"
|
||||
CUSTOM_VARS_CONTEXT_KEY = "google_analytics_custom_vars"
|
||||
CUSTOM_VAR_CODE = "_gaq.push(['_setCustomVar', %(index)d, '%(name)s', " \
|
||||
"'%(value)s', %(scope)d]);"
|
||||
|
||||
class GoogleAnalyticsService(AnalyticalService):
|
||||
KEY = 'google_analytics'
|
||||
|
||||
def __init__(self):
|
||||
self.property_id = self.get_required_setting(
|
||||
'GOOGLE_ANALYTICS_PROPERTY_ID', PROPERTY_ID_RE,
|
||||
"must be a string looking like 'UA-XXXXXX-Y'")
|
||||
|
||||
def render_head_bottom(self, context):
|
||||
return TRACKING_CODE % {'property_id': self.property_id}
|
||||
commands = self._get_custom_var_commands(context)
|
||||
commands.append(TRACK_CODE)
|
||||
return SETUP_CODE % {'property_id': self.property_id,
|
||||
'commands': " ".join(commands)}
|
||||
|
||||
def _get_custom_var_commands(self, context):
|
||||
commands = []
|
||||
vardefs = context.get(CUSTOM_VARS_CONTEXT_KEY, [])
|
||||
for vardef in vardefs:
|
||||
index = vardef[0]
|
||||
if not 1 <= index <= 5:
|
||||
raise ValueError("Google Analytics custom variable index must "
|
||||
"be between 1 and 5: %s" % index)
|
||||
name = vardef[1]
|
||||
value = vardef[2]
|
||||
if len(vardef) >= 4:
|
||||
scope = vardef[3]
|
||||
else:
|
||||
scope = 2
|
||||
commands.append(CUSTOM_VAR_CODE % locals())
|
||||
return commands
|
||||
|
|
|
|||
39
analytical/services/kiss_insights.py
Normal file
39
analytical/services/kiss_insights.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
KISSinsights service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d{5}$')
|
||||
SITE_CODE_RE = re.compile(r'^[\d\w]{3}$')
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">var _kiq = _kiq || []; %(commands)s</script>
|
||||
<script type="text/javascript" src="//s3.amazonaws.com/ki.js/%(account_number)s/%(site_code)s.js" async="true"></script>
|
||||
"""
|
||||
IDENTIFY_CODE = "_kiq.push(['identify', '%s']);"
|
||||
SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);"
|
||||
SHOW_SURVEY_CONTEXT_KEY = 'kiss_insights_show_survey'
|
||||
|
||||
class KissInsightsService(AnalyticalService):
|
||||
def __init__(self):
|
||||
self.account_number = self.get_required_setting(
|
||||
'KISS_INSIGHTS_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
||||
"must be a string containing an five-digit number")
|
||||
self.site_code = self.get_required_setting('KISS_INSIGHTS_SITE_CODE',
|
||||
SITE_CODE_RE, "must be a string containing three characters")
|
||||
|
||||
def render_body_top(self, context):
|
||||
commands = []
|
||||
identity = self.get_identity(context)
|
||||
if identity is not None:
|
||||
commands.append(IDENTIFY_CODE % identity)
|
||||
try:
|
||||
commands.append(SHOW_SURVEY_CODE
|
||||
% context[SHOW_SURVEY_CONTEXT_KEY])
|
||||
except KeyError:
|
||||
pass
|
||||
return SETUP_CODE % {'account_number': self.account_number,
|
||||
'site_code': self.site_code, 'commands': " ".join(commands)}
|
||||
|
|
@ -4,13 +4,16 @@ KISSmetrics service.
|
|||
|
||||
import re
|
||||
|
||||
from django.utils import simplejson
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
|
||||
TRACKING_CODE = """
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
var _kmq = _kmq || [];
|
||||
%(commands)s
|
||||
function _kms(u){
|
||||
setTimeout(function(){
|
||||
var s = document.createElement('script');
|
||||
|
|
@ -25,15 +28,24 @@ TRACKING_CODE = """
|
|||
_kms('//doug1izaerwt3.cloudfront.net/%(api_key)s.1.js');
|
||||
</script>
|
||||
"""
|
||||
IDENTIFY_CODE = "_kmq.push(['identify', '%s']);"
|
||||
JS_EVENT_CODE = "_kmq.push(['record', '%(name)s', %(properties)s]);"
|
||||
|
||||
|
||||
class KissMetricsService(AnalyticalService):
|
||||
KEY = 'kissmetrics'
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = self.get_required_setting('KISSMETRICS_API_KEY',
|
||||
self.api_key = self.get_required_setting('KISS_METRICS_API_KEY',
|
||||
API_KEY_RE,
|
||||
"must be a string containing a 40-digit hexadecimal number")
|
||||
|
||||
def render_head_top(self, context):
|
||||
return TRACKING_CODE % {'api_key': self.api_key}
|
||||
commands = []
|
||||
identity = self.get_identity(context)
|
||||
if identity is not None:
|
||||
commands.append(IDENTIFY_CODE % identity)
|
||||
return SETUP_CODE % {'api_key': self.api_key,
|
||||
'commands': commands}
|
||||
|
||||
def render_event(self, name, properties):
|
||||
return JS_EVENT_CODE % {'name': name,
|
||||
'properties': simplejson.dumps(properties)}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
"""
|
||||
KISSinsights service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d{5}$')
|
||||
SITE_CODE_RE = re.compile(r'^[\d\w]{3}$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">var _kiq = _kiq || [];</script>
|
||||
<script type="text/javascript" src="//s3.amazonaws.com/ki.js/%(account_number)s/%(site_code)s.js" async="true"></script>
|
||||
"""
|
||||
|
||||
|
||||
class KissInsightsService(AnalyticalService):
|
||||
KEY = 'kissinsights'
|
||||
|
||||
def __init__(self):
|
||||
self.account_number = self.get_required_setting(
|
||||
'KISSINSIGHTS_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
||||
"must be a string containing an five-digit number")
|
||||
self.site_code = self.get_required_setting('KISSINSIGHTS_SITE_CODE',
|
||||
SITE_CODE_RE, "must be a string containing three characters")
|
||||
|
||||
def render_body_top(self, context):
|
||||
return TRACKING_CODE % {'account_number': self.account_number,
|
||||
'site_code': self.site_code}
|
||||
|
|
@ -4,35 +4,42 @@ Mixpanel service.
|
|||
|
||||
import re
|
||||
|
||||
from django.utils import simplejson
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
MIXPANEL_TOKEN_RE = re.compile(r'^[0-9a-f]{32}$')
|
||||
TRACKING_CODE = """
|
||||
<script type='text/javascript'>
|
||||
var mp_protocol = (('https:' == document.location.protocol) ? 'https://' : 'http://');
|
||||
document.write(unescape('%%3Cscript src="' + mp_protocol + 'api.mixpanel.com/site_media/js/api/mixpanel.js" type="text/javascript"%%3E%%3C/script%%3E'));
|
||||
</script>
|
||||
<script type='text/javascript'>
|
||||
try {
|
||||
var mpmetrics = new MixpanelLib('%(token)s');
|
||||
} catch(err) {
|
||||
null_fn = function () {};
|
||||
var mpmetrics = {track: null_fn, track_funnel: null_fn,
|
||||
register: null_fn, register_once: null_fn,
|
||||
register_funnel: null_fn};
|
||||
}
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
var mpq = [];
|
||||
mpq.push(['init', '%(token)s']);
|
||||
%(commands)s
|
||||
(function() {
|
||||
var mp = document.createElement("script"); mp.type = "text/javascript"; mp.async = true;
|
||||
mp.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + "//api.mixpanel.com/site_media/js/api/mixpanel.js";
|
||||
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mp, s);
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
IDENTIFY_CODE = "mpq.push(['identify', '%s']);"
|
||||
EVENT_CODE = "mpq.push(['track', '%(name)s', %(properties)s]);"
|
||||
|
||||
|
||||
class MixpanelService(AnalyticalService):
|
||||
KEY = 'mixpanel'
|
||||
|
||||
def __init__(self):
|
||||
self.token = self.get_required_setting('MIXPANEL_TOKEN',
|
||||
MIXPANEL_TOKEN_RE,
|
||||
"must be a string containing a 32-digit hexadecimal number")
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return TRACKING_CODE % {'token': self.token}
|
||||
def render_head_bottom(self, context):
|
||||
commands = []
|
||||
identity = self.get_identity(context)
|
||||
if identity is not None:
|
||||
commands.append(IDENTIFY_CODE % identity)
|
||||
return SETUP_CODE % {'token': self.token,
|
||||
'commands': " ".join(commands)}
|
||||
|
||||
def render_event(self, name, properties):
|
||||
return EVENT_CODE % {'name': name,
|
||||
'properties': simplejson.dumps(properties)}
|
||||
|
|
|
|||
|
|
@ -8,16 +8,14 @@ from analytical.services.base import AnalyticalService
|
|||
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d{7}$')
|
||||
TRACKING_CODE = """<script src="//cdn.optimizely.com/js/%(account_number)s.js"></script>"""
|
||||
SETUP_CODE = """<script src="//cdn.optimizely.com/js/%(account_number)s.js"></script>"""
|
||||
|
||||
|
||||
class OptimizelyService(AnalyticalService):
|
||||
KEY = 'optimizely'
|
||||
|
||||
def __init__(self):
|
||||
self.account_number = self.get_required_setting(
|
||||
'OPTIMIZELY_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
||||
"must be a string containing an seven-digit number")
|
||||
|
||||
def render_head_top(self, context):
|
||||
return TRACKING_CODE % {'account_number': self.account_number}
|
||||
return SETUP_CODE % {'account_number': self.account_number}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ from __future__ import absolute_import
|
|||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.template import Node, TemplateSyntaxError
|
||||
from django.template import Node, TemplateSyntaxError, Variable
|
||||
|
||||
from analytical.services import get_enabled_services
|
||||
|
||||
|
||||
DISABLE_CODE = "<!-- Analytical disabled on internal IP address\n%s\n-->"
|
||||
HTML_COMMENT_CODE = "<!-- Analytical disabled on internal IP address\n%s\n-->"
|
||||
JS_COMMENT_CODE = "/* %s */"
|
||||
SCRIPT_CODE = """<script type="text/javascript">%s</script>"""
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
|
@ -25,7 +27,7 @@ def _location_tag(location):
|
|||
return tag
|
||||
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
register.tag('analytical_%s' % l, _location_tag(l))
|
||||
register.tag('analytical_setup_%s' % l, _location_tag(l))
|
||||
|
||||
|
||||
class AnalyticalNode(Node):
|
||||
|
|
@ -36,13 +38,13 @@ class AnalyticalNode(Node):
|
|||
getattr(settings, 'INTERNAL_IPS', ()))
|
||||
|
||||
def render(self, context):
|
||||
html = "".join([self._render_service(service, context)
|
||||
result = "".join([self._render_service(service, context)
|
||||
for service in get_enabled_services()])
|
||||
if not html:
|
||||
if not result:
|
||||
return ""
|
||||
# if self._is_internal_ip(context):
|
||||
# return DISABLE_CODE % html
|
||||
return html
|
||||
if self._is_internal_ip(context):
|
||||
return HTML_COMMENT_CODE % result
|
||||
return result
|
||||
|
||||
def _render_service(self, service, context):
|
||||
func = getattr(service, self.render_func_name)
|
||||
|
|
@ -56,3 +58,42 @@ class AnalyticalNode(Node):
|
|||
return remote_ip in self.internal_ips
|
||||
except KeyError, AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
def event(parser, token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) < 2:
|
||||
raise TemplateSyntaxError("'%s' tag takes at least one argument"
|
||||
% bits[0])
|
||||
properties = _parse_properties(bits[0], bits[2:])
|
||||
return EventNode(bits[1], properties)
|
||||
|
||||
register.tag('event', event)
|
||||
|
||||
|
||||
class EventNode(Node):
|
||||
def __init__(self, name, properties):
|
||||
self.name = name
|
||||
self.properties = properties
|
||||
|
||||
def render(self, context):
|
||||
props = dict((var, Variable(val).resolve(context))
|
||||
for var, val in self.properties)
|
||||
result = "".join([service.render_js_event(props)
|
||||
for service in get_enabled_services()])
|
||||
if not result:
|
||||
return ""
|
||||
if self._is_internal_ip(context):
|
||||
return JS_COMMENT_CODE % result
|
||||
return result
|
||||
|
||||
|
||||
def _parse_properties(tag_name, bits):
|
||||
properties = []
|
||||
for bit in bits:
|
||||
try:
|
||||
properties.append(bit.split('=', 1))
|
||||
except IndexError:
|
||||
raise TemplateSyntaxError("'%s' tag argument must be of the form "
|
||||
" property=value: '%s'" % (tag_name, bit))
|
||||
return properties
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
Tests for the Analytical analytics services.
|
||||
"""
|
||||
|
||||
from analytical.tests.services.test_base import *
|
||||
from analytical.tests.services.test_chartbeat import *
|
||||
from analytical.tests.services.test_clicky import *
|
||||
from analytical.tests.services.test_console import *
|
||||
from analytical.tests.services.test_crazy_egg import *
|
||||
from analytical.tests.services.test_google_analytics import *
|
||||
from analytical.tests.services.test_kissinsights import *
|
||||
from analytical.tests.services.test_kissmetrics import *
|
||||
from analytical.tests.services.test_kiss_insights import *
|
||||
from analytical.tests.services.test_kiss_metrics import *
|
||||
from analytical.tests.services.test_mixpanel import *
|
||||
from analytical.tests.services.test_optimizely import *
|
||||
|
|
|
|||
74
analytical/tests/services/test_base.py
Normal file
74
analytical/tests/services/test_base.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
"""
|
||||
Tests for the base service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class DummyService(AnalyticalService):
|
||||
def render_test(self, context):
|
||||
return context
|
||||
|
||||
|
||||
class BaseServiceTestCase(TestCase):
|
||||
"""
|
||||
Tests for the base service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.service = DummyService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
def test_render(self):
|
||||
r = self.service.render('test', 'foo')
|
||||
self.assertEqual('foo', r)
|
||||
|
||||
def test_get_required_setting(self):
|
||||
self.settings_manager.set(TEST='test')
|
||||
r = self.service.get_required_setting('TEST', re.compile('es'), 'fail')
|
||||
self.assertEqual('test', r)
|
||||
|
||||
def test_get_required_setting_missing(self):
|
||||
self.settings_manager.delete('TEST')
|
||||
self.assertRaises(ImproperlyConfigured,
|
||||
self.service.get_required_setting, 'TEST', re.compile('es'),
|
||||
'fail')
|
||||
|
||||
def test_get_required_setting_wrong(self):
|
||||
self.settings_manager.set(TEST='test')
|
||||
self.assertRaises(ImproperlyConfigured,
|
||||
self.service.get_required_setting, 'TEST', re.compile('foo'),
|
||||
'fail')
|
||||
|
||||
def test_get_identity_none(self):
|
||||
context = {}
|
||||
self.assertEqual(None, self.service.get_identity(context))
|
||||
|
||||
def test_get_identity_authenticated(self):
|
||||
context = {'user': User(username='test')}
|
||||
self.assertEqual('test', self.service.get_identity(context))
|
||||
|
||||
def test_get_identity_authenticated_request(self):
|
||||
req = HttpRequest()
|
||||
req.user = User(username='test')
|
||||
context = {'request': req}
|
||||
self.assertEqual('test', self.service.get_identity(context))
|
||||
|
||||
def test_get_identity_anonymous(self):
|
||||
context = {'user': AnonymousUser()}
|
||||
self.assertEqual(None, self.service.get_identity(context))
|
||||
|
||||
def test_get_identity_non_user(self):
|
||||
context = {'user': object()}
|
||||
self.assertEqual(None, self.service.get_identity(context))
|
||||
66
analytical/tests/services/test_chartbeat.py
Normal file
66
analytical/tests/services/test_chartbeat.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
Tests for the Chartbeat service.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.chartbeat import ChartbeatService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class ChartbeatTestCase(TestCase):
|
||||
"""
|
||||
Tests for the Chartbeat service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(CHARTBEAT_USER_ID='12345')
|
||||
self.service = ChartbeatService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
def test_empty_locations(self):
|
||||
self.assertEqual(self.service.render_head_bottom({}), "")
|
||||
self.assertEqual(self.service.render_body_top({}), "")
|
||||
|
||||
def test_no_user_id(self):
|
||||
self.settings_manager.delete('CHARTBEAT_USER_ID')
|
||||
self.assertRaises(ImproperlyConfigured, ChartbeatService)
|
||||
|
||||
def test_wrong_user_id(self):
|
||||
self.settings_manager.set(CHARTBEAT_USER_ID='1234')
|
||||
self.assertRaises(ImproperlyConfigured, ChartbeatService)
|
||||
self.settings_manager.set(CHARTBEAT_USER_ID='123456')
|
||||
self.assertRaises(ImproperlyConfigured, ChartbeatService)
|
||||
|
||||
def test_rendering_init(self):
|
||||
r = self.service.render_head_top({})
|
||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
||||
|
||||
def test_rendering_setup(self):
|
||||
r = self.service.render_body_bottom({'chartbeat_domain': "test.com"})
|
||||
self.assertTrue('var _sf_async_config={uid:12345,domain:"test.com"};'
|
||||
in r, r)
|
||||
|
||||
def test_rendering_setup_request_domain(self):
|
||||
req = HttpRequest()
|
||||
req.META['HTTP_HOST'] = 'test.com'
|
||||
r = self.service.render_body_bottom({'request': req})
|
||||
self.assertTrue('var _sf_async_config={uid:12345,domain:"test.com"};'
|
||||
in r, r)
|
||||
|
||||
def test_rendering_setup_site(self):
|
||||
installed_apps = list(settings.INSTALLED_APPS)
|
||||
installed_apps.append('django.contrib.sites')
|
||||
self.settings_manager.set(INSTALLED_APPS=installed_apps)
|
||||
site = Site.objects.create(domain="test.com", name="test")
|
||||
self.settings_manager.set(SITE_ID=site.id)
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue('var _sf_async_config={uid:12345,domain:"test.com"};'
|
||||
in r, r)
|
||||
|
|
@ -2,6 +2,9 @@
|
|||
Tests for the Clicky service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
|
|
@ -31,7 +34,7 @@ class ClickyTestCase(TestCase):
|
|||
self.settings_manager.delete('CLICKY_SITE_ID')
|
||||
self.assertRaises(ImproperlyConfigured, ClickyService)
|
||||
|
||||
def test_wrong_id(self):
|
||||
def test_wrong_site_id(self):
|
||||
self.settings_manager.set(CLICKY_SITE_ID='1234567')
|
||||
self.assertRaises(ImproperlyConfigured, ClickyService)
|
||||
self.settings_manager.set(CLICKY_SITE_ID='123456789')
|
||||
|
|
@ -42,3 +45,16 @@ class ClickyTestCase(TestCase):
|
|||
self.assertTrue('var clicky_site_id = 12345678;' in r, r)
|
||||
self.assertTrue('src="http://in.getclicky.com/12345678ns.gif"' in r,
|
||||
r)
|
||||
|
||||
def test_identify(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = self.service.render_body_bottom({'user': User(username='test')})
|
||||
self.assertTrue(
|
||||
'var clicky_custom = {"session": {"username": "test"}};' in r,
|
||||
r)
|
||||
|
||||
def test_custom(self):
|
||||
custom = {'var1': 'val1', 'var2': 'val2'}
|
||||
r = self.service.render_body_bottom({'clicky_custom': custom})
|
||||
self.assertTrue(re.search('var clicky_custom = {.*'
|
||||
'"var1": "val1", "var2": "val2".*};', r), r)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Tests for the console debugging service.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.console import ConsoleService
|
||||
|
|
@ -18,15 +19,27 @@ class ConsoleTestCase(TestCase):
|
|||
def test_render_head_top(self):
|
||||
r = self.service.render_head_top({})
|
||||
self.assertTrue('rendering analytical_head_top tag' in r, r)
|
||||
r = self.service.render_head_top({'user': User(username='test')})
|
||||
self.assertTrue('rendering analytical_head_top tag for user test'
|
||||
in r, r)
|
||||
|
||||
def test_render_head_bottom(self):
|
||||
r = self.service.render_head_bottom({})
|
||||
self.assertTrue('rendering analytical_head_bottom tag' in r, r)
|
||||
r = self.service.render_head_bottom({'user': User(username='test')})
|
||||
self.assertTrue('rendering analytical_head_bottom tag for user test'
|
||||
in r, r)
|
||||
|
||||
def test_render_body_top(self):
|
||||
r = self.service.render_body_top({})
|
||||
self.assertTrue('rendering analytical_body_top tag' in r, r)
|
||||
r = self.service.render_body_top({'user': User(username='test')})
|
||||
self.assertTrue('rendering analytical_body_top tag for user test'
|
||||
in r, r)
|
||||
|
||||
def test_render_body_bottom(self):
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue('rendering analytical_body_bottom tag' in r, r)
|
||||
r = self.service.render_body_bottom({'user': User(username='test')})
|
||||
self.assertTrue('rendering analytical_body_bottom tag for user test'
|
||||
in r, r)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class CrazyEggTestCase(TestCase):
|
|||
self.settings_manager.delete('CRAZY_EGG_ACCOUNT_NUMBER')
|
||||
self.assertRaises(ImproperlyConfigured, CrazyEggService)
|
||||
|
||||
def test_wrong_id(self):
|
||||
def test_wrong_account_number(self):
|
||||
self.settings_manager.set(CRAZY_EGG_ACCOUNT_NUMBER='1234567')
|
||||
self.assertRaises(ImproperlyConfigured, CrazyEggService)
|
||||
self.settings_manager.set(CRAZY_EGG_ACCOUNT_NUMBER='123456789')
|
||||
|
|
@ -40,3 +40,9 @@ class CrazyEggTestCase(TestCase):
|
|||
def test_rendering(self):
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue('/1234/5678.js' in r, r)
|
||||
|
||||
def test_uservars(self):
|
||||
context = {'crazy_egg_uservars': {1: 'foo', 2: 'bar'}}
|
||||
r = self.service.render_body_bottom(context)
|
||||
self.assertTrue("CE2.set(1, 'foo');" in r, r)
|
||||
self.assertTrue("CE2.set(2, 'bar');" in r, r)
|
||||
|
|
|
|||
|
|
@ -31,10 +31,26 @@ class GoogleAnalyticsTestCase(TestCase):
|
|||
self.settings_manager.delete('GOOGLE_ANALYTICS_PROPERTY_ID')
|
||||
self.assertRaises(ImproperlyConfigured, GoogleAnalyticsService)
|
||||
|
||||
def test_wrong_id(self):
|
||||
def test_wrong_property_id(self):
|
||||
self.settings_manager.set(GOOGLE_ANALYTICS_PROPERTY_ID='wrong')
|
||||
self.assertRaises(ImproperlyConfigured, GoogleAnalyticsService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_head_bottom({})
|
||||
self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_trackPageview']);" in r, r)
|
||||
|
||||
def test_custom_vars(self):
|
||||
context = {'google_analytics_custom_vars': [
|
||||
(1, 'test1', 'foo'),
|
||||
(5, 'test2', 'bar', 1),
|
||||
]}
|
||||
r = self.service.render_head_bottom(context)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 2]);"
|
||||
in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 5, 'test2', 'bar', 1]);"
|
||||
in r, r)
|
||||
self.assertRaises(ValueError, self.service.render_head_bottom,
|
||||
{'google_analytics_custom_vars': [(0, 'test', 'test')]})
|
||||
self.assertRaises(ValueError, self.service.render_head_bottom,
|
||||
{'google_analytics_custom_vars': [(6, 'test', 'test')]})
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
Tests for the KISSinsights service.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.kissinsights import KissInsightsService
|
||||
from analytical.services.kiss_insights import KissInsightsService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
|
|
@ -16,8 +17,8 @@ class KissInsightsTestCase(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(KISSINSIGHTS_ACCOUNT_NUMBER='12345')
|
||||
self.settings_manager.set(KISSINSIGHTS_SITE_CODE='abc')
|
||||
self.settings_manager.set(KISS_INSIGHTS_ACCOUNT_NUMBER='12345')
|
||||
self.settings_manager.set(KISS_INSIGHTS_SITE_CODE='abc')
|
||||
self.service = KissInsightsService()
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -29,25 +30,34 @@ class KissInsightsTestCase(TestCase):
|
|||
self.assertEqual(self.service.render_body_bottom({}), "")
|
||||
|
||||
def test_no_account_number(self):
|
||||
self.settings_manager.delete('KISSINSIGHTS_ACCOUNT_NUMBER')
|
||||
self.settings_manager.delete('KISS_INSIGHTS_ACCOUNT_NUMBER')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_no_site_code(self):
|
||||
self.settings_manager.delete('KISSINSIGHTS_SITE_CODE')
|
||||
self.settings_manager.delete('KISS_INSIGHTS_SITE_CODE')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_wrong_account_number(self):
|
||||
self.settings_manager.set(KISSINSIGHTS_ACCOUNT_NUMBER='1234')
|
||||
self.settings_manager.set(KISS_INSIGHTS_ACCOUNT_NUMBER='1234')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
self.settings_manager.set(KISSINSIGHTS_ACCOUNT_NUMBER='123456')
|
||||
self.settings_manager.set(KISS_INSIGHTS_ACCOUNT_NUMBER='123456')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_wrong_site_id(self):
|
||||
self.settings_manager.set(KISSINSIGHTS_SITE_CODE='ab')
|
||||
self.settings_manager.set(KISS_INSIGHTS_SITE_CODE='ab')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
self.settings_manager.set(KISSINSIGHTS_SITE_CODE='abcd')
|
||||
self.settings_manager.set(KISS_INSIGHTS_SITE_CODE='abcd')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_body_top({})
|
||||
self.assertTrue("//s3.amazonaws.com/ki.js/12345/abc.js" in r, r)
|
||||
|
||||
def test_identify(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = self.service.render_body_top({'user': User(username='test')})
|
||||
self.assertTrue("_kiq.push(['identify', 'test']);" in r, r)
|
||||
|
||||
def test_show_survey(self):
|
||||
r = self.service.render_body_top({'kiss_insights_show_survey': 1234})
|
||||
self.assertTrue("_kiq.push(['showSurvey', 1234]);" in r, r)
|
||||
|
|
@ -2,10 +2,11 @@
|
|||
Tests for the KISSmetrics service.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.kissmetrics import KissMetricsService
|
||||
from analytical.services.kiss_metrics import KissMetricsService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
|
|
@ -16,8 +17,8 @@ class KissMetricsTestCase(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(KISSMETRICS_API_KEY='0123456789abcdef0123456'
|
||||
'789abcdef01234567')
|
||||
self.settings_manager.set(KISS_METRICS_API_KEY='0123456789abcdef012345'
|
||||
'6789abcdef01234567')
|
||||
self.service = KissMetricsService()
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -29,18 +30,29 @@ class KissMetricsTestCase(TestCase):
|
|||
self.assertEqual(self.service.render_body_bottom({}), "")
|
||||
|
||||
def test_no_api_key(self):
|
||||
self.settings_manager.delete('KISSMETRICS_API_KEY')
|
||||
self.settings_manager.delete('KISS_METRICS_API_KEY')
|
||||
self.assertRaises(ImproperlyConfigured, KissMetricsService)
|
||||
|
||||
def test_wrong_api_key(self):
|
||||
self.settings_manager.set(KISSMETRICS_API_KEY='0123456789abcdef0123456'
|
||||
'789abcdef0123456')
|
||||
self.settings_manager.set(KISS_METRICS_API_KEY='0123456789abcdef012345'
|
||||
'6789abcdef0123456')
|
||||
self.assertRaises(ImproperlyConfigured, KissMetricsService)
|
||||
self.settings_manager.set(KISSMETRICS_API_KEY='0123456789abcdef0123456'
|
||||
'789abcdef012345678')
|
||||
self.settings_manager.set(KISS_METRICS_API_KEY='0123456789abcdef012345'
|
||||
'6789abcdef012345678')
|
||||
self.assertRaises(ImproperlyConfigured, KissMetricsService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_head_top({})
|
||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/0123456789abcdef012345"
|
||||
"6789abcdef01234567.1.js" in r, r)
|
||||
|
||||
def test_identify(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = self.service.render_head_top({'user': User(username='test')})
|
||||
self.assertTrue("_kmq.push(['identify', 'test']);" in r, r)
|
||||
|
||||
def test_event(self):
|
||||
r = self.service.render_event('test_event', {'prop1': 'val1',
|
||||
'prop2': 'val2'})
|
||||
self.assertEqual(r, "_kmq.push(['record', 'test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);')
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
Tests for the Mixpanel service.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
|
|
@ -25,8 +26,8 @@ class MixpanelTestCase(TestCase):
|
|||
|
||||
def test_empty_locations(self):
|
||||
self.assertEqual(self.service.render_head_top({}), "")
|
||||
self.assertEqual(self.service.render_head_bottom({}), "")
|
||||
self.assertEqual(self.service.render_body_top({}), "")
|
||||
self.assertEqual(self.service.render_body_bottom({}), "")
|
||||
|
||||
def test_no_token(self):
|
||||
self.settings_manager.delete('MIXPANEL_TOKEN')
|
||||
|
|
@ -41,6 +42,18 @@ class MixpanelTestCase(TestCase):
|
|||
self.assertRaises(ImproperlyConfigured, MixpanelService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue("MixpanelLib('0123456789abcdef0123456789abcdef')" in r,
|
||||
r = self.service.render_head_bottom({})
|
||||
self.assertTrue(
|
||||
"mpq.push(['init', '0123456789abcdef0123456789abcdef']);" in r,
|
||||
r)
|
||||
|
||||
def test_identify(self):
|
||||
self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
r = self.service.render_head_bottom({'user': User(username='test')})
|
||||
self.assertTrue("mpq.push(['identify', 'test']);" in r, r)
|
||||
|
||||
def test_event(self):
|
||||
r = self.service.render_event('test_event', {'prop1': 'val1',
|
||||
'prop2': 'val2'})
|
||||
self.assertEqual(r, "mpq.push(['track', 'test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);')
|
||||
|
|
|
|||
|
|
@ -18,30 +18,18 @@ class GetEnabledServicesTestCase(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
services.enabled_services = None
|
||||
services.load_services = self._dummy_load_services_1
|
||||
services.load_services = lambda: 'test'
|
||||
|
||||
def tearDown(self):
|
||||
services.enabled_services = None
|
||||
services.load_services = load_services
|
||||
|
||||
def test_no_reload(self):
|
||||
def test_get_enabled_services(self):
|
||||
result = services.get_enabled_services()
|
||||
self.assertEqual(result, 'test1')
|
||||
services.load_services = self._dummy_load_services_2
|
||||
self.assertEqual(result, 'test')
|
||||
services.load_services = lambda: 'test2'
|
||||
result = services.get_enabled_services()
|
||||
self.assertEqual(result, 'test1')
|
||||
|
||||
def test_reload(self):
|
||||
result = services.get_enabled_services()
|
||||
self.assertEqual(result, 'test1')
|
||||
services.load_services = self._dummy_load_services_2
|
||||
result = services.get_enabled_services(reload=True)
|
||||
self.assertEqual(result, 'test2')
|
||||
|
||||
def _dummy_load_services_1(self):
|
||||
return 'test1'
|
||||
|
||||
def _dummy_load_services_2(self):
|
||||
return 'test2'
|
||||
self.assertEqual(result, 'test')
|
||||
|
||||
|
||||
class LoadServicesTestCase(TestCase):
|
||||
|
|
@ -53,6 +41,7 @@ class LoadServicesTestCase(TestCase):
|
|||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.delete('ANALYTICAL_SERVICES')
|
||||
self.settings_manager.delete('CLICKY_SITE_ID')
|
||||
self.settings_manager.delete('CHARTBEAT_USER_ID')
|
||||
self.settings_manager.delete('CRAZY_EGG_ACCOUNT_NUMBER')
|
||||
self.settings_manager.delete('GOOGLE_ANALYTICS_PROPERTY_ID')
|
||||
self.settings_manager.delete('KISSINSIGHTS_ACCOUNT_NUMBER')
|
||||
|
|
@ -60,9 +49,11 @@ class LoadServicesTestCase(TestCase):
|
|||
self.settings_manager.delete('KISSMETRICS_API_KEY')
|
||||
self.settings_manager.delete('MIXPANEL_TOKEN')
|
||||
self.settings_manager.delete('OPTIMIZELY_ACCOUNT_NUMBER')
|
||||
services.enabled_services = None
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
services.enabled_services = None
|
||||
|
||||
def test_no_services(self):
|
||||
self.assertEqual(load_services(), [])
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
Tests for the template tags.
|
||||
"""
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django import template
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical import services
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
|
|
@ -14,6 +17,74 @@ class TemplateTagsTestCase(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(ANALYTICAL_SERVICES=[
|
||||
'analytical.services.console.ConsoleService'])
|
||||
services.enabled_services = None
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
services.enabled_services = None
|
||||
|
||||
def render_location_tag(self, location, context=None):
|
||||
if context is None: context = {}
|
||||
t = template.Template(
|
||||
"{%% load analytical %%}{%% analytical_setup_%s %%}"
|
||||
% location)
|
||||
return t.render(template.Context(context))
|
||||
|
||||
def test_location_tags(self):
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(l)
|
||||
self.assertTrue('rendering analytical_%s tag' % l 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'
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(l, {'request': req})
|
||||
self.assertTrue('<!-- Analytical disabled on internal IP address'
|
||||
in r, r)
|
||||
|
||||
def test_render_internal_ip_fallback(self):
|
||||
self.settings_manager.set(INTERNAL_IPS=['1.1.1.1'])
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(l, {'request': req})
|
||||
self.assertTrue('<!-- Analytical disabled on internal IP address'
|
||||
in r, r)
|
||||
|
||||
def test_render_internal_ip_forwarded_for(self):
|
||||
self.settings_manager.set(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
req = HttpRequest()
|
||||
req.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1'
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(l, {'request': req})
|
||||
self.assertTrue('<!-- Analytical disabled on internal IP address'
|
||||
in r, r)
|
||||
|
||||
def test_render_different_internal_ip(self):
|
||||
self.settings_manager.set(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '2.2.2.2'
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(l, {'request': req})
|
||||
self.assertFalse('<!-- Analytical disabled on internal IP address'
|
||||
in r, r)
|
||||
|
||||
def test_render_internal_ip_empty(self):
|
||||
self.settings_manager.set(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
self.settings_manager.delete('ANALYTICAL_SERVICES')
|
||||
self.settings_manager.delete('CLICKY_SITE_ID')
|
||||
self.settings_manager.delete('CRAZY_EGG_ACCOUNT_NUMBER')
|
||||
self.settings_manager.delete('GOOGLE_ANALYTICS_PROPERTY_ID')
|
||||
self.settings_manager.delete('KISSINSIGHTS_ACCOUNT_NUMBER')
|
||||
self.settings_manager.delete('KISSINSIGHTS_SITE_CODE')
|
||||
self.settings_manager.delete('KISSMETRICS_API_KEY')
|
||||
self.settings_manager.delete('MIXPANEL_TOKEN')
|
||||
self.settings_manager.delete('OPTIMIZELY_ACCOUNT_NUMBER')
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
self.assertEqual(self.render_location_tag(l, {'request': req}), "")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
# directory.
|
||||
|
||||
import sys, os
|
||||
sys.path.append(os.path.join(os.path.abspath('.'), '.ext'))
|
||||
sys.path.append(os.path.dirname(os.path.abspath('.')))
|
||||
|
||||
import analytical
|
||||
|
|
@ -26,7 +27,10 @@ master_doc = 'index'
|
|||
add_function_parentheses = True
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
intersphinx_mapping = {
|
||||
'http://docs.python.org/2.6': None,
|
||||
'http://docs.djangoproject.com/en/1.2': 'http://docs.djangoproject.com/en/1.2/_objects/',
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ into a Django_ project.
|
|||
:Download: http://pypi.python.org/pypi/django-analytical/
|
||||
:Source: http://github.com/jcassee/django-analytical
|
||||
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
|
|
@ -22,14 +23,13 @@ the different analytics services behind a generic interface. It is
|
|||
designed to make the common case easy while allowing advanced users to
|
||||
customize tracking.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Easy installation. See the :doc:`quick`.
|
||||
* Supports many services. See :doc:`services/index`.
|
||||
* Automatically identifies logged-in users (not implemented yet)
|
||||
* Disables tracking on internal IP addresses (not implemented yet)
|
||||
The application provides four generic template tags that are added to
|
||||
the top and bottom of the head and body section of the base template.
|
||||
Configured services will be enabled automatically by adding Javascript
|
||||
code at these locations. The installation will follow the
|
||||
recommendations from the analytics services, using an asynchronous
|
||||
version of the code if possible. See :doc:`services/index` for detailed
|
||||
information about each individual analytics service.
|
||||
|
||||
|
||||
Contents
|
||||
|
|
@ -38,7 +38,7 @@ Contents
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
quick
|
||||
install
|
||||
services/index
|
||||
settings
|
||||
history
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
Installation and global configuration
|
||||
=====================================
|
||||
==============================
|
||||
Installation and configuration
|
||||
==============================
|
||||
|
||||
Integration of your analytics service is very simple. There are four
|
||||
steps: installing the package, adding it to the list of installed Django
|
||||
|
|
@ -9,11 +10,11 @@ the identifiers for the services you use to the project settings.
|
|||
#. `Installing the Python package`_
|
||||
#. `Installing the Django application`_
|
||||
#. `Adding the template tags to the base template`_
|
||||
#. `Configuring global settings`_
|
||||
#. `Configuring the application`_
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -21,7 +22,7 @@ the Python path. You can install it directly from PyPI using
|
|||
|
||||
$ easy_install django-analytical
|
||||
|
||||
You can also install directly from source. Download either the latest
|
||||
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::
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ Then install by running the setup script::
|
|||
|
||||
|
||||
Installing the Django application
|
||||
---------------------------------
|
||||
=================================
|
||||
|
||||
After you install django-analytical, add the ``analytical`` Django
|
||||
application to the list of installed applications in the ``settings.py``
|
||||
|
|
@ -51,7 +52,7 @@ file of your project::
|
|||
|
||||
|
||||
Adding the template tags to the base template
|
||||
---------------------------------------------
|
||||
=============================================
|
||||
|
||||
Because every analytics service has uses own specific Javascript code
|
||||
that should be added to the top or bottom of either the head or body
|
||||
|
|
@ -63,23 +64,77 @@ Your base template should look like this::
|
|||
<!DOCTYPE ... >
|
||||
<html>
|
||||
<head>
|
||||
{% analytical_head_top %}
|
||||
{% analytical_setup_head_top %}
|
||||
|
||||
...
|
||||
|
||||
{% analytical_head_bottom %}
|
||||
{% analytical_setup_head_bottom %}
|
||||
</head>
|
||||
<body>
|
||||
{% analytical_body_top %}
|
||||
{% analytical_setup_body_top %}
|
||||
|
||||
...
|
||||
|
||||
{% analytical_body_bottom %}
|
||||
{% analytical_setup_body_bottom %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Configuring global settings
|
||||
---------------------------
|
||||
Configuring the application
|
||||
===========================
|
||||
|
||||
The next step is to :doc:`configure the services <services/index>`.
|
||||
Without configuration, the template tags all render the empty string.
|
||||
You must enable at least one service, and optionally configure other
|
||||
django-analytical features.
|
||||
|
||||
|
||||
Enabling services
|
||||
-----------------
|
||||
|
||||
By default, only configured analytics services are installed by the
|
||||
template tags. You can also use the :data:`ANALYTICAL_SERVICES` setting
|
||||
to specify the used services explicitly. Services are configured in the
|
||||
project ``settings.py`` file. The settings required to enable each
|
||||
service are listed here. See the service documentation for details.
|
||||
|
||||
* :doc:`Clicky <services/clicky>`::
|
||||
|
||||
CLICKY_SITE_ID = '12345678'
|
||||
|
||||
* :doc:`Crazy Egg <services/crazy_egg>`::
|
||||
|
||||
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
|
||||
|
||||
* :doc:`Google Analytics <services/google_analytics>`::
|
||||
|
||||
GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8'
|
||||
|
||||
* :doc:`KISSinsights <services/kiss_insights>`::
|
||||
|
||||
KISS_INSIGHTS_ACCOUNT_NUMBER = '12345'
|
||||
KISS_INSIGHTS_SITE_CODE = 'abc'
|
||||
|
||||
* :doc:`KISSmetrics <services/kiss_metrics>`::
|
||||
|
||||
KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567'
|
||||
|
||||
* :doc:`Mixpanel <services/mixpanel>`::
|
||||
|
||||
MIXPANEL_TOKEN = '0123456789abcdef0123456789abcdef'
|
||||
|
||||
* :doc:`Optimizely <services/optimizely>`::
|
||||
|
||||
OPTIMIZELY_ACCOUNT_NUMBER = '1234567'
|
||||
|
||||
|
||||
Configuring behavior
|
||||
--------------------
|
||||
|
||||
By default, django-analytical will comment out the service
|
||||
initialization code if the client IP address is detected as one from the
|
||||
:data:`ANALYTICAL_INTERNAL_IPS` setting, which is set to
|
||||
:data:`INTERNAL_IPS` by default.
|
||||
|
||||
Also, if the visitor is a logged in user and the user is accessible in
|
||||
the template context, the username is passed to the analytics services
|
||||
that support identifying users. See :data:`ANALYTICAL_AUTO_IDENTIFY`.
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
Quick Start Guide
|
||||
=================
|
||||
|
||||
If you do not need any advanced analytics tracking, installing
|
||||
django-analytical is very simple. 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 django-analytical
|
||||
|
||||
After you install django-analytical, add the ``analytical`` Django
|
||||
application to the list of installed applications in the ``settings.py``
|
||||
file of your project::
|
||||
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
'analytical',
|
||||
...
|
||||
]
|
||||
|
||||
Now add the django-analytical template tags to your base template::
|
||||
|
||||
{% load analytical %}
|
||||
<!DOCTYPE ... >
|
||||
<html>
|
||||
<head>
|
||||
{% analytical_head_top %}
|
||||
|
||||
...
|
||||
|
||||
{% analytical_head_bottom %}
|
||||
</head>
|
||||
<body>
|
||||
{% analytical_body_top %}
|
||||
|
||||
...
|
||||
|
||||
{% analytical_body_bottom %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Finally, configure the analytics services you use in the project
|
||||
``settings.py`` file. This is a list of the settings required for the
|
||||
different services::
|
||||
|
||||
CLICKY_SITE_ID = 'xxxxxxxx'
|
||||
CRAZY_EGG_ACCOUNT_NUMBER = 'xxxxxxxx'
|
||||
GOOGLE_ANALYTICS_ACCOUNT_NUMBER = 'UA-xxxxxx-x'
|
||||
KISSINSIGHTS_ACCOUNT_NUMBER = 'xxxxx'
|
||||
KISSINSIGHTS_ACCOUNT_NUMBER = 'xxx'
|
||||
KISSINSIGHTS_ACCOUNT_NUMBER = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
MIXPANEL_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
OPTIMIZELY_ACCOUNT_NUMBER = 'xxxxxx'
|
||||
|
||||
Your analytics services are now installed. Take a look at the rest of
|
||||
the documentation for more information about further configuration and
|
||||
customization of the various services.
|
||||
31
docs/services/chartbeat.rst
Normal file
31
docs/services/chartbeat.rst
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
Chartbeat -- traffic analysis
|
||||
=============================
|
||||
|
||||
Chartbeat_ provides real-time analytics to websites and blogs. It shows
|
||||
visitors, load times, and referring sites on a minute-by-minute basis.
|
||||
The service also provides alerts the second your website crashes or
|
||||
slows to a crawl.
|
||||
|
||||
.. _Chartbeat: http://www.chartbeat.com/
|
||||
|
||||
The Chartbeat service adds code both to the top of the head section and
|
||||
the bottom of the body section.
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: CHARTBEAT_USER_ID
|
||||
|
||||
The User ID::
|
||||
|
||||
CHARTBEAT_USER_ID = '12345'
|
||||
|
||||
You can find the User ID by visiting the Chartbeat `Add New Site`_
|
||||
page. The second code snippet contains a line that looks like this::
|
||||
|
||||
var _sf_async_config={uid:XXXXX,domain:"YYYYYYYYYY"};
|
||||
|
||||
Here, ``XXXXX`` is your User ID.
|
||||
|
||||
.. _`Add New Site`: http://chartbeat.com/code/
|
||||
69
docs/settings.rst
Normal file
69
docs/settings.rst
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
========
|
||||
Settings
|
||||
========
|
||||
|
||||
Here's a full list of all available settings, in alphabetical order, and
|
||||
their default values.
|
||||
|
||||
|
||||
.. data:: ANALYTICAL_AUTO_IDENTIFY
|
||||
|
||||
Default: ``True``
|
||||
|
||||
Automatically identify logged in users by their username.
|
||||
|
||||
.. note::
|
||||
|
||||
The template tags can only access the visitor username if the
|
||||
Django user is present in the template context either as the
|
||||
``user`` variable, or as an attribute on the HTTP request in the
|
||||
``request`` variable. Use a
|
||||
:class:`~django.template.RequestContext` to render your
|
||||
templates and add
|
||||
``'django.contrib.auth.context_processors.auth'`` or
|
||||
``'django.core.context_processors.request'`` to the list of
|
||||
context processors in the :data:`TEMPLATE_CONTEXT_PROCESSORS`
|
||||
setting. (The first of these is added by default.)
|
||||
Alternatively, add one of the variables to the context yourself
|
||||
when you render the template.
|
||||
|
||||
|
||||
.. data:: ANALYTICAL_INTERNAL_IPS
|
||||
|
||||
Default: :data:`INTERNAL_IPS`
|
||||
|
||||
A list or tuple of internal IP addresses. Tracking code will be
|
||||
commented out for visitors from any of these addresses.
|
||||
|
||||
Example::
|
||||
|
||||
ANALYTICAL_INTERNAL_IPS = ['192.168.1.45', '192.168.1.57']
|
||||
|
||||
.. note::
|
||||
|
||||
The template tags can only access the visitor IP address if the
|
||||
HTTP request is present in the template context as the
|
||||
``request`` variable. For this reason, the
|
||||
:data:`ANALYTICAL_INTERNAL_IPS` settings only works if you add
|
||||
this variable to the context yourself when you render the
|
||||
template, or you use the ``RequestContext`` and add
|
||||
``'django.core.context_processors.request'`` to the list of
|
||||
context processors in the ``TEMPLATE_CONTEXT_PROCESSORS``
|
||||
setting.
|
||||
|
||||
|
||||
.. data:: ANALYTICAL_SERVICES
|
||||
|
||||
Default: all included services that have been configured correctly
|
||||
|
||||
A list or tuple of analytics services to use. If this setting is
|
||||
used and one of the services is not configured correctly, an
|
||||
:exc:`ImproperlyConfigured` exception is raised when the services
|
||||
are first loaded.
|
||||
|
||||
Example::
|
||||
|
||||
ANALYTICAL_SERVICES = [
|
||||
'analytical.services.crazy_egg.CrazyEggService',
|
||||
'analytical.services.google_analytics.GoogleAnalyticsService',
|
||||
]
|
||||
6
setup.py
6
setup.py
|
|
@ -1,4 +1,5 @@
|
|||
from distutils.core import setup, Command
|
||||
import os
|
||||
|
||||
cmdclass = {}
|
||||
|
||||
|
|
@ -33,6 +34,9 @@ class TestCommand(Command):
|
|||
cmdclass['test'] = TestCommand
|
||||
|
||||
|
||||
def read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
import analytical
|
||||
|
||||
setup(
|
||||
|
|
@ -40,7 +44,7 @@ setup(
|
|||
version = analytical.__version__,
|
||||
license = analytical.__license__,
|
||||
description = 'Analytics services for Django projects',
|
||||
long_description = analytical.__doc__,
|
||||
long_description = read('README.rst'),
|
||||
author = analytical.__author__,
|
||||
author_email = analytical.__email__,
|
||||
packages = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue