mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Create project
This commit is contained in:
commit
1fa0d7760d
47 changed files with 1545 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/.*
|
||||
!/.gitignore
|
||||
|
||||
/build
|
||||
/dist
|
||||
/MANIFEST
|
||||
|
||||
*.pyc
|
||||
*.pyo
|
||||
19
LICENSE.txt
Normal file
19
LICENSE.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2011 Joost Cassee
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
|
|
@ -0,0 +1 @@
|
|||
include LICENSE.txt
|
||||
36
README.rst
Normal file
36
README.rst
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
django-analytical
|
||||
-----------------
|
||||
|
||||
The django-analytical application integrates various analytics services
|
||||
into a Django_ project. Currently supported services:
|
||||
|
||||
* `Clicky`_ -- traffic analysis
|
||||
* `Crazy Egg`_ -- visual click tracking
|
||||
* `Google Analytics`_ traffic analysis
|
||||
* `KISSinsights`_ -- feedback surveys
|
||||
* `KISSmetrics`_ -- funnel analysis
|
||||
* `Mixpanel`_ -- event tracking
|
||||
* `Optimizely`_ -- A/B testing
|
||||
|
||||
The documentation can be found in the ``docs`` directory or `read
|
||||
online`_. The source code and issue tracker are `hosted on GitHub`_.
|
||||
|
||||
Copyright (C) 2011 Joost Cassee <joost@cassee.net>. This software is
|
||||
licensed under the MIT License (see LICENSE.txt).
|
||||
|
||||
This application was inspired by and uses ideas from Analytical_,
|
||||
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/
|
||||
.. _Clicky: http://getclicky.com/
|
||||
.. _`Crazy Egg`: http://www.crazyegg.com/
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
.. _KISSinsights: http://www.kissinsights.com/
|
||||
.. _KISSmetrics: http://www.kissmetrics.com/
|
||||
.. _Mixpanel: http://www.mixpanel.com/
|
||||
.. _Optimizely: http://www.optimizely.com/
|
||||
.. _`read online`: http://packages.python.org/django-analytical/
|
||||
.. _`hosted on GitHub`: http://www.github.com/jcassee/django-analytical
|
||||
.. _Analytical: https://github.com/jkrall/analytical
|
||||
.. _`Bateau Knowledge`: http://www.bateauknowledge.nl/
|
||||
14
analytical/__init__.py
Normal file
14
analytical/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
========================================
|
||||
Analytics service integration for Django
|
||||
========================================
|
||||
|
||||
The django-clicky application integrates Clicky_ analytics into a
|
||||
Django_ project.
|
||||
"""
|
||||
|
||||
__author__ = "Joost Cassee"
|
||||
__email__ = "joost@cassee.net"
|
||||
__version__ = "0.1.0alpha"
|
||||
__copyright__ = "Copyright (C) 2011 Joost Cassee"
|
||||
__license__ = "MIT License"
|
||||
0
analytical/models.py
Normal file
0
analytical/models.py
Normal file
62
analytical/services/__init__.py
Normal file
62
analytical/services/__init__.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"""
|
||||
Analytics services.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.importlib import import_module
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SERVICES = [
|
||||
'analytical.services.clicky.ClickyService',
|
||||
'analytical.services.crazyegg.CrazyEggService',
|
||||
'analytical.services.google_analytics.GoogleAnalyticsService',
|
||||
'analytical.services.kissinsights.KissInsightsService',
|
||||
'analytical.services.kissmetrics.KissMetricsService',
|
||||
'analytical.services.mixpanel.MixpanelService',
|
||||
'analytical.services.optimizely.OptimizelyService',
|
||||
]
|
||||
|
||||
|
||||
enabled_services = None
|
||||
def get_enabled_services(reload=False):
|
||||
global enabled_services
|
||||
if enabled_services is None or reload:
|
||||
enabled_services = load_services()
|
||||
return enabled_services
|
||||
|
||||
def load_services():
|
||||
enabled_services = []
|
||||
try:
|
||||
service_paths = settings.ANALYTICAL_SERVICES
|
||||
autoload = False
|
||||
except AttributeError:
|
||||
service_paths = DEFAULT_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))
|
||||
enabled_services.append(service)
|
||||
except ImproperlyConfigured, e:
|
||||
if autoload:
|
||||
_log.debug('not loading analytical service "%s": %s',
|
||||
path, e)
|
||||
else:
|
||||
raise
|
||||
return enabled_services
|
||||
41
analytical/services/base.py
Normal file
41
analytical/services/base.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
Base analytical service.
|
||||
"""
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class AnalyticalService(object):
|
||||
"""
|
||||
Analytics service.
|
||||
"""
|
||||
|
||||
def render(self, location, context):
|
||||
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):
|
||||
return ""
|
||||
|
||||
def render_head_bottom(self, context):
|
||||
return ""
|
||||
|
||||
def render_body_top(self, context):
|
||||
return ""
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return ""
|
||||
|
||||
def get_required_setting(self, setting, value_re, invalid_msg):
|
||||
try:
|
||||
value = getattr(settings, setting)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured("%s setting: not found" % setting)
|
||||
value = str(value)
|
||||
if not value_re.search(value):
|
||||
raise ImproperlyConfigured("%s setting: %s" % (value, invalid_msg))
|
||||
return value
|
||||
35
analytical/services/clicky.py
Normal file
35
analytical/services/clicky.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Clicky service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
SITE_ID_RE = re.compile(r'^\d{8}$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
var clicky = { log: function(){ return; }, goal: function(){ return; }};
|
||||
var clicky_site_id = %(site_id)s;
|
||||
(function() {
|
||||
var s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.async = true;
|
||||
s.src = ( document.location.protocol == 'https:' ? 'https://static.getclicky.com/js' : 'http://static.getclicky.com/js' );
|
||||
( document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] ).appendChild( s );
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img alt="Clicky" width="1" height="1" src="http://in.getclicky.com/%(site_id)sns.gif" /></p></noscript>
|
||||
"""
|
||||
|
||||
|
||||
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}
|
||||
30
analytical/services/console.py
Normal file
30
analytical/services/console.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
Console debugging service.
|
||||
"""
|
||||
|
||||
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');
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
class ConsoleService(AnalyticalService):
|
||||
KEY = 'console'
|
||||
|
||||
def render_head_top(self, context):
|
||||
return DEBUG_CODE % {'location': 'head_top'}
|
||||
|
||||
def render_head_bottom(self, context):
|
||||
return DEBUG_CODE % {'location': 'head_bottom'}
|
||||
|
||||
def render_body_top(self, context):
|
||||
return DEBUG_CODE % {'location': 'body_top'}
|
||||
|
||||
def render_body_bottom(self, context):
|
||||
return DEBUG_CODE % {'location': 'body_bottom'}
|
||||
24
analytical/services/crazy_egg.py
Normal file
24
analytical/services/crazy_egg.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
Crazy Egg service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
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>"""
|
||||
|
||||
|
||||
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],
|
||||
'account_nr_2': self.account_nr[4:]}
|
||||
37
analytical/services/google_analytics.py
Normal file
37
analytical/services/google_analytics.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"""
|
||||
Google Analytics service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '%(property_id)s']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
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}
|
||||
30
analytical/services/kissinsights.py
Normal file
30
analytical/services/kissinsights.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
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}
|
||||
39
analytical/services/kissmetrics.py
Normal file
39
analytical/services/kissmetrics.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
KISSmetrics service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from analytical.services.base import AnalyticalService
|
||||
|
||||
|
||||
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
var _kmq = _kmq || [];
|
||||
function _kms(u){
|
||||
setTimeout(function(){
|
||||
var s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.async = true;
|
||||
s.src = u;
|
||||
var f = document.getElementsByTagName('script')[0];
|
||||
f.parentNode.insertBefore(s, f);
|
||||
}, 1);
|
||||
}
|
||||
_kms('//i.kissmetrics.com/i.js');
|
||||
_kms('//doug1izaerwt3.cloudfront.net/%(api_key)s.1.js');
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
class KissMetricsService(AnalyticalService):
|
||||
KEY = 'kissmetrics'
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = self.get_required_setting('KISSMETRICS_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}
|
||||
38
analytical/services/mixpanel.py
Normal file
38
analytical/services/mixpanel.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""
|
||||
Mixpanel service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
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};
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
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}
|
||||
23
analytical/services/optimizely.py
Normal file
23
analytical/services/optimizely.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
Optimizely service.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
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>"""
|
||||
|
||||
|
||||
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}
|
||||
0
analytical/templatetags/__init__.py
Normal file
0
analytical/templatetags/__init__.py
Normal file
58
analytical/templatetags/analytical.py
Normal file
58
analytical/templatetags/analytical.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
Analytical template tags.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.template import Node, TemplateSyntaxError
|
||||
|
||||
from analytical.services import get_enabled_services
|
||||
|
||||
|
||||
DISABLE_CODE = "<!-- Analytical disabled on internal IP address\n%s\n-->"
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
def _location_tag(location):
|
||||
def tag(parser, token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0])
|
||||
return AnalyticalNode(location)
|
||||
return tag
|
||||
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
register.tag('analytical_%s' % l, _location_tag(l))
|
||||
|
||||
|
||||
class AnalyticalNode(Node):
|
||||
def __init__(self, location):
|
||||
self.location = location
|
||||
self.render_func_name = "render_%s" % self.location
|
||||
self.internal_ips = getattr(settings, 'ANALYTICAL_INTERNAL_IPS',
|
||||
getattr(settings, 'INTERNAL_IPS', ()))
|
||||
|
||||
def render(self, context):
|
||||
html = "".join([self._render_service(service, context)
|
||||
for service in get_enabled_services()])
|
||||
if not html:
|
||||
return ""
|
||||
# if self._is_internal_ip(context):
|
||||
# return DISABLE_CODE % html
|
||||
return html
|
||||
|
||||
def _render_service(self, service, context):
|
||||
func = getattr(service, self.render_func_name)
|
||||
return func(context)
|
||||
|
||||
def _is_internal_ip(self, context):
|
||||
try:
|
||||
request = context['request']
|
||||
remote_ip = request.META.get('HTTP_X_FORWARDED_FOR',
|
||||
request.META.get('REMOTE_ADDR', ''))
|
||||
return remote_ip in self.internal_ips
|
||||
except KeyError, AttributeError:
|
||||
return False
|
||||
8
analytical/tests/__init__.py
Normal file
8
analytical/tests/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
Tests for django-analytical.
|
||||
"""
|
||||
|
||||
from analytical.tests.test_services import *
|
||||
from analytical.tests.test_template_tags import *
|
||||
|
||||
from analytical.tests.services import *
|
||||
12
analytical/tests/services/__init__.py
Normal file
12
analytical/tests/services/__init__.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
Tests for the Analytical analytics services.
|
||||
"""
|
||||
|
||||
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_mixpanel import *
|
||||
from analytical.tests.services.test_optimizely import *
|
||||
44
analytical/tests/services/test_clicky.py
Normal file
44
analytical/tests/services/test_clicky.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
Tests for the Clicky service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.clicky import ClickyService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class ClickyTestCase(TestCase):
|
||||
"""
|
||||
Tests for the Clicky service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(CLICKY_SITE_ID='12345678')
|
||||
self.service = ClickyService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
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({}), "")
|
||||
|
||||
def test_no_site_id(self):
|
||||
self.settings_manager.delete('CLICKY_SITE_ID')
|
||||
self.assertRaises(ImproperlyConfigured, ClickyService)
|
||||
|
||||
def test_wrong_id(self):
|
||||
self.settings_manager.set(CLICKY_SITE_ID='1234567')
|
||||
self.assertRaises(ImproperlyConfigured, ClickyService)
|
||||
self.settings_manager.set(CLICKY_SITE_ID='123456789')
|
||||
self.assertRaises(ImproperlyConfigured, ClickyService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue('var clicky_site_id = 12345678;' in r, r)
|
||||
self.assertTrue('src="http://in.getclicky.com/12345678ns.gif"' in r,
|
||||
r)
|
||||
32
analytical/tests/services/test_console.py
Normal file
32
analytical/tests/services/test_console.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"""
|
||||
Tests for the console debugging service.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.console import ConsoleService
|
||||
|
||||
|
||||
class ConsoleTestCase(TestCase):
|
||||
"""
|
||||
Tests for the console debugging service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.service = ConsoleService()
|
||||
|
||||
def test_render_head_top(self):
|
||||
r = self.service.render_head_top({})
|
||||
self.assertTrue('rendering analytical_head_top tag' 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)
|
||||
|
||||
def test_render_body_top(self):
|
||||
r = self.service.render_body_top({})
|
||||
self.assertTrue('rendering analytical_body_top tag' 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)
|
||||
42
analytical/tests/services/test_crazy_egg.py
Normal file
42
analytical/tests/services/test_crazy_egg.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
Tests for the Crazy Egg service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.crazy_egg import CrazyEggService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class CrazyEggTestCase(TestCase):
|
||||
"""
|
||||
Tests for the Crazy Egg service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(CRAZY_EGG_ACCOUNT_NUMBER='12345678')
|
||||
self.service = CrazyEggService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
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({}), "")
|
||||
|
||||
def test_no_account_number(self):
|
||||
self.settings_manager.delete('CRAZY_EGG_ACCOUNT_NUMBER')
|
||||
self.assertRaises(ImproperlyConfigured, CrazyEggService)
|
||||
|
||||
def test_wrong_id(self):
|
||||
self.settings_manager.set(CRAZY_EGG_ACCOUNT_NUMBER='1234567')
|
||||
self.assertRaises(ImproperlyConfigured, CrazyEggService)
|
||||
self.settings_manager.set(CRAZY_EGG_ACCOUNT_NUMBER='123456789')
|
||||
self.assertRaises(ImproperlyConfigured, CrazyEggService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue('/1234/5678.js' in r, r)
|
||||
40
analytical/tests/services/test_google_analytics.py
Normal file
40
analytical/tests/services/test_google_analytics.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
Tests for the Google Analytics service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.google_analytics import GoogleAnalyticsService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class GoogleAnalyticsTestCase(TestCase):
|
||||
"""
|
||||
Tests for the Google Analytics service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7')
|
||||
self.service = GoogleAnalyticsService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
def test_empty_locations(self):
|
||||
self.assertEqual(self.service.render_head_top({}), "")
|
||||
self.assertEqual(self.service.render_body_top({}), "")
|
||||
self.assertEqual(self.service.render_body_bottom({}), "")
|
||||
|
||||
def test_no_property_id(self):
|
||||
self.settings_manager.delete('GOOGLE_ANALYTICS_PROPERTY_ID')
|
||||
self.assertRaises(ImproperlyConfigured, GoogleAnalyticsService)
|
||||
|
||||
def test_wrong_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)
|
||||
53
analytical/tests/services/test_kissinsights.py
Normal file
53
analytical/tests/services/test_kissinsights.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"""
|
||||
Tests for the KISSinsights service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.kissinsights import KissInsightsService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class KissInsightsTestCase(TestCase):
|
||||
"""
|
||||
Tests for the KISSinsights service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(KISSINSIGHTS_ACCOUNT_NUMBER='12345')
|
||||
self.settings_manager.set(KISSINSIGHTS_SITE_CODE='abc')
|
||||
self.service = KissInsightsService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
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_bottom({}), "")
|
||||
|
||||
def test_no_account_number(self):
|
||||
self.settings_manager.delete('KISSINSIGHTS_ACCOUNT_NUMBER')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_no_site_code(self):
|
||||
self.settings_manager.delete('KISSINSIGHTS_SITE_CODE')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_wrong_account_number(self):
|
||||
self.settings_manager.set(KISSINSIGHTS_ACCOUNT_NUMBER='1234')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
self.settings_manager.set(KISSINSIGHTS_ACCOUNT_NUMBER='123456')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
|
||||
def test_wrong_site_id(self):
|
||||
self.settings_manager.set(KISSINSIGHTS_SITE_CODE='ab')
|
||||
self.assertRaises(ImproperlyConfigured, KissInsightsService)
|
||||
self.settings_manager.set(KISSINSIGHTS_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)
|
||||
46
analytical/tests/services/test_kissmetrics.py
Normal file
46
analytical/tests/services/test_kissmetrics.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
Tests for the KISSmetrics service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.kissmetrics import KissMetricsService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class KissMetricsTestCase(TestCase):
|
||||
"""
|
||||
Tests for the KISSmetrics service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(KISSMETRICS_API_KEY='0123456789abcdef0123456'
|
||||
'789abcdef01234567')
|
||||
self.service = KissMetricsService()
|
||||
|
||||
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({}), "")
|
||||
self.assertEqual(self.service.render_body_bottom({}), "")
|
||||
|
||||
def test_no_api_key(self):
|
||||
self.settings_manager.delete('KISSMETRICS_API_KEY')
|
||||
self.assertRaises(ImproperlyConfigured, KissMetricsService)
|
||||
|
||||
def test_wrong_api_key(self):
|
||||
self.settings_manager.set(KISSMETRICS_API_KEY='0123456789abcdef0123456'
|
||||
'789abcdef0123456')
|
||||
self.assertRaises(ImproperlyConfigured, KissMetricsService)
|
||||
self.settings_manager.set(KISSMETRICS_API_KEY='0123456789abcdef0123456'
|
||||
'789abcdef012345678')
|
||||
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)
|
||||
46
analytical/tests/services/test_mixpanel.py
Normal file
46
analytical/tests/services/test_mixpanel.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
Tests for the Mixpanel service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.mixpanel import MixpanelService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class MixpanelTestCase(TestCase):
|
||||
"""
|
||||
Tests for the Mixpanel service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(
|
||||
MIXPANEL_TOKEN='0123456789abcdef0123456789abcdef')
|
||||
self.service = MixpanelService()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
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({}), "")
|
||||
|
||||
def test_no_token(self):
|
||||
self.settings_manager.delete('MIXPANEL_TOKEN')
|
||||
self.assertRaises(ImproperlyConfigured, MixpanelService)
|
||||
|
||||
def test_wrong_token(self):
|
||||
self.settings_manager.set(
|
||||
MIXPANEL_TOKEN='0123456789abcdef0123456789abcde')
|
||||
self.assertRaises(ImproperlyConfigured, MixpanelService)
|
||||
self.settings_manager.set(
|
||||
MIXPANEL_TOKEN='0123456789abcdef0123456789abcdef0')
|
||||
self.assertRaises(ImproperlyConfigured, MixpanelService)
|
||||
|
||||
def test_rendering(self):
|
||||
r = self.service.render_body_bottom({})
|
||||
self.assertTrue("MixpanelLib('0123456789abcdef0123456789abcdef')" in r,
|
||||
r)
|
||||
42
analytical/tests/services/test_optimizely.py
Normal file
42
analytical/tests/services/test_optimizely.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
Tests for the Optimizely service.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.services.optimizely import OptimizelyService
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class OptimizelyTestCase(TestCase):
|
||||
"""
|
||||
Tests for the Optimizely service.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
self.settings_manager.set(OPTIMIZELY_ACCOUNT_NUMBER='1234567')
|
||||
self.service = OptimizelyService()
|
||||
|
||||
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({}), "")
|
||||
self.assertEqual(self.service.render_body_bottom({}), "")
|
||||
|
||||
def test_no_account_number(self):
|
||||
self.settings_manager.delete('OPTIMIZELY_ACCOUNT_NUMBER')
|
||||
self.assertRaises(ImproperlyConfigured, OptimizelyService)
|
||||
|
||||
def test_wrong_account_number(self):
|
||||
self.settings_manager.set(OPTIMIZELY_ACCOUNT_NUMBER='123456')
|
||||
self.assertRaises(ImproperlyConfigured, OptimizelyService)
|
||||
self.settings_manager.set(OPTIMIZELY_ACCOUNT_NUMBER='12345678')
|
||||
self.assertRaises(ImproperlyConfigured, OptimizelyService)
|
||||
|
||||
def test_rendering(self):
|
||||
self.assertEqual(self.service.render_head_top({}),
|
||||
'<script src="//cdn.optimizely.com/js/1234567.js"></script>')
|
||||
14
analytical/tests/settings.py
Normal file
14
analytical/tests/settings.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
django-analytical testing settings.
|
||||
"""
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:',
|
||||
}
|
||||
}
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'analytical',
|
||||
]
|
||||
89
analytical/tests/test_services.py
Normal file
89
analytical/tests/test_services.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
Tests for the services package.
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical import services
|
||||
from analytical.services import load_services
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
from analytical.services.google_analytics import GoogleAnalyticsService
|
||||
|
||||
|
||||
class GetEnabledServicesTestCase(TestCase):
|
||||
"""
|
||||
Tests for get_enabled_services.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
services.enabled_services = None
|
||||
services.load_services = self._dummy_load_services_1
|
||||
|
||||
def tearDown(self):
|
||||
services.load_services = load_services
|
||||
|
||||
def test_no_reload(self):
|
||||
result = services.get_enabled_services()
|
||||
self.assertEqual(result, 'test1')
|
||||
services.load_services = self._dummy_load_services_2
|
||||
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'
|
||||
|
||||
|
||||
class LoadServicesTestCase(TestCase):
|
||||
"""
|
||||
Tests for load_services.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
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')
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
|
||||
def test_no_services(self):
|
||||
self.assertEqual(load_services(), [])
|
||||
|
||||
def test_enabled_service(self):
|
||||
self.settings_manager.set(GOOGLE_ANALYTICS_PROPERTY_ID='UA-1234567-8')
|
||||
results = load_services()
|
||||
self.assertEqual(len(results), 1, results)
|
||||
self.assertTrue(isinstance(results[0], GoogleAnalyticsService),
|
||||
results)
|
||||
|
||||
def test_explicit_service(self):
|
||||
self.settings_manager.set(ANALYTICAL_SERVICES=[
|
||||
'analytical.services.google_analytics.GoogleAnalyticsService'])
|
||||
self.settings_manager.set(GOOGLE_ANALYTICS_PROPERTY_ID='UA-1234567-8')
|
||||
results = load_services()
|
||||
self.assertEqual(len(results), 1, results)
|
||||
self.assertTrue(isinstance(results[0], GoogleAnalyticsService),
|
||||
results)
|
||||
|
||||
def test_explicit_service_misconfigured(self):
|
||||
self.settings_manager.set(ANALYTICAL_SERVICES=[
|
||||
'analytical.services.google_analytics.GoogleAnalyticsService'])
|
||||
self.assertRaises(ImproperlyConfigured, load_services)
|
||||
19
analytical/tests/test_template_tags.py
Normal file
19
analytical/tests/test_template_tags.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""
|
||||
Tests for the template tags.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from analytical.tests.utils import TestSettingsManager
|
||||
|
||||
|
||||
class TemplateTagsTestCase(TestCase):
|
||||
"""
|
||||
Tests for the template tags.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.settings_manager = TestSettingsManager()
|
||||
|
||||
def tearDown(self):
|
||||
self.settings_manager.revert()
|
||||
64
analytical/tests/utils.py
Normal file
64
analytical/tests/utils.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
Testing utilities.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.db.models import loading
|
||||
from django.test.simple import run_tests as django_run_tests
|
||||
|
||||
|
||||
def run_tests():
|
||||
"""
|
||||
Use the Django test runner to run the tests.
|
||||
"""
|
||||
django_run_tests([], verbosity=1, interactive=True)
|
||||
|
||||
|
||||
class TestSettingsManager(object):
|
||||
"""
|
||||
From: http://www.djangosnippets.org/snippets/1011/
|
||||
|
||||
A class which can modify some Django settings temporarily for a
|
||||
test and then revert them to their original values later.
|
||||
|
||||
Automatically handles resyncing the DB if INSTALLED_APPS is
|
||||
modified.
|
||||
"""
|
||||
|
||||
NO_SETTING = ('!', None)
|
||||
|
||||
def __init__(self):
|
||||
self._original_settings = {}
|
||||
|
||||
def set(self, **kwargs):
|
||||
for k, v in kwargs.iteritems():
|
||||
self._original_settings.setdefault(k, getattr(settings, k,
|
||||
self.NO_SETTING))
|
||||
setattr(settings, k, v)
|
||||
if 'INSTALLED_APPS' in kwargs:
|
||||
self.syncdb()
|
||||
|
||||
def delete(self, *args):
|
||||
for k in args:
|
||||
try:
|
||||
self._original_settings.setdefault(k, getattr(settings, k,
|
||||
self.NO_SETTING))
|
||||
delattr(settings, k)
|
||||
except AttributeError:
|
||||
pass # setting did not exist
|
||||
|
||||
def syncdb(self):
|
||||
loading.cache.loaded = False
|
||||
call_command('syncdb', verbosity=0, interactive=False)
|
||||
|
||||
def revert(self):
|
||||
for k,v in self._original_settings.iteritems():
|
||||
if v == self.NO_SETTING:
|
||||
if hasattr(settings, k):
|
||||
delattr(settings, k)
|
||||
else:
|
||||
setattr(settings, k, v)
|
||||
if 'INSTALLED_APPS' in self._original_settings:
|
||||
self.syncdb()
|
||||
self._original_settings = {}
|
||||
44
docs/conf.py
Normal file
44
docs/conf.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing
|
||||
# directory.
|
||||
|
||||
import sys, os
|
||||
sys.path.append(os.path.dirname(os.path.abspath('.')))
|
||||
|
||||
import analytical
|
||||
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
project = u'django-analytical'
|
||||
copyright = u'2011, Joost Cassee <joost@cassee.net>'
|
||||
|
||||
release = analytical.__version__
|
||||
# The short X.Y version.
|
||||
version = release.rsplit('.', 1)[0]
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
|
||||
templates_path = ['.templates']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
add_function_parentheses = True
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
html_theme = 'default'
|
||||
html_static_path = ['.static']
|
||||
htmlhelp_basename = 'analyticaldoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_documents = [
|
||||
('index', 'django-analytical.tex', u'Documentation for django-analytical',
|
||||
u'Joost Cassee', 'manual'),
|
||||
]
|
||||
23
docs/history.rst
Normal file
23
docs/history.rst
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
History and credits
|
||||
===================
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
0.1.0
|
||||
First project release.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
django-analytical was written by `Joost Cassee`_. The project source
|
||||
code is hosted generously hosted by GitHub_.
|
||||
|
||||
This application was inspired by and uses ideas from Analytical_,
|
||||
Joshua Krall's all-purpose analytics front-end for Rails. The work on
|
||||
Crazy Egg was made possible by `Bateau Knowledge`_.
|
||||
|
||||
.. _`Joost Cassee`: mailto:joost@cassee.net
|
||||
.. _GitHub: http://github.com/
|
||||
.. _Analytical: https://github.com/jkrall/analytical
|
||||
.. _`Bateau Knowledge`: http://www.bateauknowledge.nl/
|
||||
44
docs/index.rst
Normal file
44
docs/index.rst
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
========================================
|
||||
Analytics service integration for Django
|
||||
========================================
|
||||
|
||||
The django-analytical application integrates various analytics services
|
||||
into a Django_ project.
|
||||
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
|
||||
:Download: http://pypi.python.org/pypi/django-analytical/
|
||||
:Source: http://github.com/jcassee/django-analytical
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
If your want to integrating an analytics service into a Django project,
|
||||
you need to add Javascript tracking code to the project templates.
|
||||
Unfortunately, every services has its own specific installation
|
||||
instructions. Furthermore, you need to specify your unique identifiers
|
||||
which would end up in templates. This application hides the details of
|
||||
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)
|
||||
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
quick
|
||||
install
|
||||
services/index
|
||||
history
|
||||
85
docs/install.rst
Normal file
85
docs/install.rst
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
Installation and global configuration
|
||||
=====================================
|
||||
|
||||
Integration of your analytics service is very simple. There are four
|
||||
steps: installing the package, adding it to the list of installed Django
|
||||
applications, adding the template tags to your base template, and adding
|
||||
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`_
|
||||
|
||||
|
||||
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
|
||||
``easy_install``::
|
||||
|
||||
$ easy_install django-analytical
|
||||
|
||||
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::
|
||||
|
||||
$ git clone https://github.com/jcassee/django-analytical.git
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/django-analytical/
|
||||
.. _GitHub: http://github.com/jcassee/django-analytical
|
||||
|
||||
Then install by running the setup script::
|
||||
|
||||
$ cd django-analytical
|
||||
$ python setup.py install
|
||||
|
||||
|
||||
Installing the Django application
|
||||
---------------------------------
|
||||
|
||||
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',
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
of every HTML page, the django-analytical provides four general-purpose
|
||||
tags that will render the code needed for the services you are using.
|
||||
Your base template should look like this::
|
||||
|
||||
{% load analytical %}
|
||||
<!DOCTYPE ... >
|
||||
<html>
|
||||
<head>
|
||||
{% analytical_head_top %}
|
||||
|
||||
...
|
||||
|
||||
{% analytical_head_bottom %}
|
||||
</head>
|
||||
<body>
|
||||
{% analytical_body_top %}
|
||||
|
||||
...
|
||||
|
||||
{% analytical_body_bottom %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Configuring global settings
|
||||
---------------------------
|
||||
|
||||
The next step is to :doc:`configure the services <services/index>`.
|
||||
57
docs/quick.rst
Normal file
57
docs/quick.rst
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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.
|
||||
22
docs/services/clicky.rst
Normal file
22
docs/services/clicky.rst
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Clicky -- traffic analysis
|
||||
==========================
|
||||
|
||||
Clicky_ is an online web analytics tool. It is similar to Google
|
||||
Analytics in that it provides statistics on who is visiting your website
|
||||
and what they are doing. Clicky provides its data in real time and is
|
||||
designed to be very easy to use.
|
||||
|
||||
.. _Clicky: http://getclicky.com/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: CLICKY_SITE_ID
|
||||
|
||||
The Clicky site identifier, or Site ID::
|
||||
|
||||
CLICKY_SITE_ID = '12345678'
|
||||
|
||||
You can find the Site ID in the Info tab of the website Preferences
|
||||
page on your Clicky account.
|
||||
21
docs/services/crazy_egg.rst
Normal file
21
docs/services/crazy_egg.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
Crazy Egg -- visual click tracking
|
||||
==================================
|
||||
|
||||
`Crazy Egg`_ is an easy to use hosted web application that visualizes
|
||||
website clicks using heatmaps. It allows you to discover the areas of
|
||||
web pages that are most important to your visitors.
|
||||
|
||||
.. _`Crazy Egg`: http://www.crazyegg.com/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: CRAZY_EGG_ACCOUNT_NUMBER
|
||||
|
||||
Your Crazy Egg account number::
|
||||
|
||||
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
|
||||
|
||||
You can find the account number by clicking the link named "What's my
|
||||
code?" in the dashboard of your Crazy Egg account.
|
||||
21
docs/services/google_analytics.rst
Normal file
21
docs/services/google_analytics.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
Google Analytics -- traffic analysis
|
||||
====================================
|
||||
|
||||
`Google Analytics`_ is the well-known web analytics service from
|
||||
Google. The product is aimed more at marketers than webmasters or
|
||||
technologists, supporting integration with AdWords and other e-commence
|
||||
features.
|
||||
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: GOOGLE_ANALYTICS_PROPERTY_ID
|
||||
|
||||
The Google Analytics web property ID::
|
||||
|
||||
GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-123456-1'
|
||||
|
||||
You can find the web property ID on the overview page of your account.
|
||||
10
docs/services/index.rst
Normal file
10
docs/services/index.rst
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Services
|
||||
========
|
||||
|
||||
A number of analytics services is supported.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
*
|
||||
30
docs/services/kissinsights.rst
Normal file
30
docs/services/kissinsights.rst
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
KISSinsights -- feedback surveys
|
||||
================================
|
||||
|
||||
KISSinsights_ provides unobtrusive surveys that pop up from the bottom
|
||||
right-hand corner of your website. Asking specific questions gets you
|
||||
the targeted, actionable feedback you need to make your site better.
|
||||
|
||||
.. _KISSinsights: http://www.kissinsights.com/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: KISSINSIGHTS_ACCOUNT_NUMBER
|
||||
|
||||
The KISSinsights account number::
|
||||
|
||||
KISSINSIGHTS_ACCOUNT_NUMBER = '12345'
|
||||
|
||||
.. data:: KISSINSIGHTS_SITE_CODE
|
||||
|
||||
The KISSinsights website code::
|
||||
|
||||
KISSINSIGHTS_SITE_CODE = 'abc'
|
||||
|
||||
You can find the account number and website code by visiting the code
|
||||
installation page of the website you want to place the surveys on. You
|
||||
will see some HTML code with a Javascript tag with a ``src`` attribute
|
||||
containing ``//s3.amazonaws.com/ki.js/XXXXX/YYY.js``. Here ``XXXXX`` is
|
||||
the account number and ``YYY`` the website code.
|
||||
22
docs/services/kissmetrics.rst
Normal file
22
docs/services/kissmetrics.rst
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
KISSmetrics -- funnel analysis
|
||||
==============================
|
||||
|
||||
KISSmetrics_ is an easy to implement analytics solution that provides a
|
||||
powerful visual representation of your customer lifecycle. Discover how
|
||||
many visitors go from your landing page to pricing to sign up, and how
|
||||
many drop out at each stage.
|
||||
|
||||
.. _KISSmetrics: http://www.kissmetrics.com/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: KISSMETRICS_API_KEY
|
||||
|
||||
The website API key::
|
||||
|
||||
KISSMETRICS_API_KEY = '1234567890abcdef1234567890abcdef12345678'
|
||||
|
||||
You can find the website API key by visiting the website `Product
|
||||
center` on your KISSmetrics dashboard.
|
||||
20
docs/services/mixpanel.rst
Normal file
20
docs/services/mixpanel.rst
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Mixpanel -- event tracking
|
||||
==========================
|
||||
|
||||
Mixpanel_ tracks events and actions to see what features users are using
|
||||
the most and how they are trending. You could use it for real-time
|
||||
analysis of visitor retention or funnels.
|
||||
|
||||
.. _Mixpanel: http://www.mixpanel.com/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: MIXPANEL_TOKEN
|
||||
|
||||
The website project token ::
|
||||
|
||||
MIXPANEL_TOKEN = '1234567890abcdef1234567890abcdef'
|
||||
|
||||
You can find the project token on the Mixpanel `projects` page.
|
||||
25
docs/services/optimizely.rst
Normal file
25
docs/services/optimizely.rst
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Optimizely -- A/B testing
|
||||
=========================
|
||||
|
||||
Optimizely_ is an easy way to implement A/B testing. Try different
|
||||
decisions, images, layouts, and copy without touching your website code
|
||||
and see exactly how your experiments are affecting pagevieuws,
|
||||
retention and sales.
|
||||
|
||||
.. _Optimizely: http://www.optimizely.com/
|
||||
|
||||
|
||||
Required settings
|
||||
-----------------
|
||||
|
||||
.. data:: OPTIMIZELY_ACCOUNT_NUMBER
|
||||
|
||||
The website project token ::
|
||||
|
||||
OPTIMIZELY_ACCOUNT_NUMBER = '1234567'
|
||||
|
||||
You can find your account number by clicking the `Implementation` link
|
||||
in the top right-hand corner of the Optimizely website. A pop-up
|
||||
window will appear containing HTML code looking like this:
|
||||
``<script src="//cdn.optimizely.com/js/XXXXXXX.js"></script>``.
|
||||
The number ``XXXXXXX`` is your account number.
|
||||
7
setup.cfg
Normal file
7
setup.cfg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[build_sphinx]
|
||||
source-dir = docs
|
||||
build-dir = build/docs
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = build/docs/html
|
||||
67
setup.py
Normal file
67
setup.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
from distutils.core import setup, Command
|
||||
|
||||
cmdclass = {}
|
||||
|
||||
try:
|
||||
from sphinx.setup_command import BuildDoc
|
||||
cmdclass['build_sphinx'] = BuildDoc
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from sphinx_pypi_upload import UploadDoc
|
||||
cmdclass['upload_sphinx'] = UploadDoc
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class TestCommand(Command):
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
import os
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'analytical.tests.settings'
|
||||
from analytical.tests.utils import run_tests
|
||||
run_tests()
|
||||
|
||||
cmdclass['test'] = TestCommand
|
||||
|
||||
|
||||
import analytical
|
||||
|
||||
setup(
|
||||
name = 'django-analytical',
|
||||
version = analytical.__version__,
|
||||
license = analytical.__license__,
|
||||
description = 'Analytics services for Django projects',
|
||||
long_description = analytical.__doc__,
|
||||
author = analytical.__author__,
|
||||
author_email = analytical.__email__,
|
||||
packages = [
|
||||
'analytical',
|
||||
'analytical.templatetags',
|
||||
'analytical.tests',
|
||||
],
|
||||
keywords = ['django', 'analytics]'],
|
||||
classifiers = [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
platforms = ['any'],
|
||||
url = 'http://github.com/jcassee/django-analytical',
|
||||
download_url = 'http://github.com/jcassee/django-analytical/archives/master',
|
||||
cmdclass = cmdclass,
|
||||
)
|
||||
Loading…
Reference in a new issue