From 6ea7e85b0242026e613f1772432f94794c5c204d Mon Sep 17 00:00:00 2001 From: Joost Cassee Date: Mon, 31 Jan 2011 08:57:26 +0100 Subject: [PATCH] Add Performable service. Update docs layout. Use included README.rst in documentation. Bump version to 0.3.0. --- CHANGELOG.rst | 4 +- MANIFEST.in | 2 +- README.rst | 15 +-- analytical/__init__.py | 2 +- analytical/templatetags/performable.py | 77 +++++++++++++ analytical/tests/__init__.py | 1 + analytical/tests/test_tag_performable.py | 69 ++++++++++++ docs/history.rst | 10 +- docs/index.rst | 24 ++-- docs/services/hubspot.rst | 14 +-- docs/services/optimizely.rst | 8 +- docs/services/performable.rst | 133 +++++++++++++++++++++++ docs/settings.rst | 18 +-- setup.py | 3 +- 14 files changed, 326 insertions(+), 54 deletions(-) create mode 100644 analytical/templatetags/performable.py create mode 100644 analytical/tests/test_tag_performable.py create mode 100644 docs/services/performable.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fda6a69..71de069 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -Development ------------ +Version 0.3.0 +------------- * Added support for the Performable service. Version 0.2.0 diff --git a/MANIFEST.in b/MANIFEST.in index dffb870..8bded2d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include LICENSE.txt +include LICENSE.txt *.rst recursive-include docs *.rst *.py diff --git a/README.rst b/README.rst index 1e01807..30a66c6 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ django-analytical ------------------ +================= The django-analytical application integrates analytics services into a Django_ project. @@ -8,6 +8,7 @@ Using an analytics service with a Django project means adding Javascript tracking code to the project templates. Of course, every service has its own specific installation instructions. Furthermore, you need to include your unique identifiers, which then end up in the templates. + This application hides the details of the different analytics services behind a generic interface, and keeps personal information and configuration out of the templates. Its goal is to make the basic @@ -30,12 +31,12 @@ Currently supported services: The documentation can be found in the ``docs`` directory or `read online`_. The project source is `hosted by GitHub`_. -If you want to help out with development of django-analytical, by -posting detailed bug reports, suggesting new features or other analytics -services to support, or doing some development work yourself, please use -the `issue tracker`_. If you want to get your hands dirty yourself, -great! Clone the repository, make changes and send a pull request. -Please do create an issue to discuss your plans. +If you want to help out with the development of django-analytical, by +posting detailed bug reports, proposing new features or other analytics +services to support, or suggesting documentation improvements, use the +`issue tracker`_. If you want to get your hands dirty, great! Clone +the repository, make changes and send a pull request. Please do create +an issue to discuss your plans. .. _Django: http://www.djangoproject.com/ .. _Chartbeat: http://www.chartbeat.com/ diff --git a/analytical/__init__.py b/analytical/__init__.py index 4737e69..4fc23c6 100644 --- a/analytical/__init__.py +++ b/analytical/__init__.py @@ -10,6 +10,6 @@ Django_ project. See the ``docs`` directory for more information. __author__ = "Joost Cassee" __email__ = "joost@cassee.net" -__version__ = "0.2.0" +__version__ = "0.3.0" __copyright__ = "Copyright (C) 2011 Joost Cassee" __license__ = "MIT License" diff --git a/analytical/templatetags/performable.py b/analytical/templatetags/performable.py new file mode 100644 index 0000000..26fc275 --- /dev/null +++ b/analytical/templatetags/performable.py @@ -0,0 +1,77 @@ +""" +Performable template tags and filters. +""" + +from __future__ import absolute_import + +import re + +from django.template import Library, Node, TemplateSyntaxError + +from analytical.utils import is_internal_ip, disable_html, get_identity, \ + get_required_setting + + +API_KEY_RE = re.compile(r'^\w{6}$') +SETUP_CODE = """""" +IDENTIFY_CODE = """ + +""" +EMBED_CODE = """ + + +""" + +register = Library() + + +@register.tag +def performable(parser, token): + """ + Performable template tag. + + Renders Javascript code to set-up Performable tracking. You must + supply your Performable API key in the ``PERFORMABLE_API_KEY`` + setting. + """ + bits = token.split_contents() + if len(bits) > 1: + raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) + return PerformableNode() + +class PerformableNode(Node): + def __init__(self): + self.api_key = get_required_setting( + 'PERFORMABLE_API_KEY', API_KEY_RE, + "must be a string containing five alphanumerical characters") + + def render(self, context): + html = SETUP_CODE % {'api_key': self.api_key} + identity = get_identity(context, 'performable') + if identity is not None: + html = "%s%s" % (IDENTIFY_CODE % identity, html) + if is_internal_ip(context, 'PERFORMABLE'): + html = disable_html(html, 'Performable') + return html + + +@register.simple_tag +def performable_embed(hostname, page_id): + """ + Include a Performable landing page. + """ + return EMBED_CODE % locals() + + +def contribute_to_analytical(add_node): + PerformableNode() # ensure properly configured + add_node('body_bottom', PerformableNode) diff --git a/analytical/tests/__init__.py b/analytical/tests/__init__.py index dd969f2..4ac739e 100644 --- a/analytical/tests/__init__.py +++ b/analytical/tests/__init__.py @@ -12,4 +12,5 @@ from analytical.tests.test_tag_kiss_insights import * from analytical.tests.test_tag_kiss_metrics import * from analytical.tests.test_tag_mixpanel import * from analytical.tests.test_tag_optimizely import * +from analytical.tests.test_tag_performable import * from analytical.tests.test_utils import * diff --git a/analytical/tests/test_tag_performable.py b/analytical/tests/test_tag_performable.py new file mode 100644 index 0000000..2935cc8 --- /dev/null +++ b/analytical/tests/test_tag_performable.py @@ -0,0 +1,69 @@ +""" +Tests for the Performable template tags and filters. +""" + +from django.http import HttpRequest +from django.template import Context + +from analytical.templatetags.performable import PerformableNode +from analytical.tests.utils import TagTestCase +from analytical.utils import AnalyticalException +from django.contrib.auth.models import User + + +class PerformableTagTestCase(TagTestCase): + """ + Tests for the ``performable`` template tag. + """ + + def setUp(self): + super(PerformableTagTestCase, self).setUp() + self.settings_manager.set(PERFORMABLE_API_KEY='123ABC') + + def test_tag(self): + r = self.render_tag('performable', 'performable') + self.assertTrue('/performable/pax/123ABC.js' in r, r) + + def test_node(self): + r = PerformableNode().render(Context()) + self.assertTrue('/performable/pax/123ABC.js' in r, r) + + def test_no_api_key(self): + self.settings_manager.delete('PERFORMABLE_API_KEY') + self.assertRaises(AnalyticalException, PerformableNode) + + def test_wrong_account_number(self): + self.settings_manager.set(PERFORMABLE_API_KEY='123AB') + self.assertRaises(AnalyticalException, PerformableNode) + self.settings_manager.set(PERFORMABLE_API_KEY='123ABCD') + self.assertRaises(AnalyticalException, PerformableNode) + + def test_render_internal_ip(self): + self.settings_manager.set(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) + req = HttpRequest() + req.META['REMOTE_ADDR'] = '1.1.1.1' + context = Context({'request': req}) + r = PerformableNode().render(context) + self.assertTrue(r.startswith( + ''), r) + + def test_identify(self): + self.settings_manager.set(ANALYTICAL_AUTO_IDENTIFY=True) + r = PerformableNode().render(Context({'user': User(username='test')})) + self.assertTrue('_paq.push(["identify", {identity: "test"}]);' in r, r) + + +class PerformableEmbedTagTestCase(TagTestCase): + """ + Tests for the ``performable_embed`` template tag. + """ + + def test_tag(self): + d = 'example.com' + p = 'test' + r = self.render_tag('performable', 'performable_embed "%s" "%s"' + % (d, p)) + self.assertTrue( + "$f.initialize({'host': 'example.com', 'page': 'test'});" in r, + r) diff --git a/docs/history.rst b/docs/history.rst index 950f2c7..6d5770f 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -26,11 +26,5 @@ Credits Helping out =========== -If you want to help out with development of django-analytical, by -posting detailed bug reports, suggesting new features or other analytics -services to support, or doing some development work yourself, please use -the `issue tracker`_. If you want to get your hands dirty yourself, -great! Clone the repository, make changes and send a pull request. -Please do create an issue to discuss your plans. - -.. _`issue tracker`: http://github.com/jcassee/django-analytical/issues +.. include:: ../README.rst + :start-line: 32 diff --git a/docs/index.rst b/docs/index.rst index 0f3a311..8fbae5f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,9 @@ -======================================== -Analytics service integration for Django -======================================== +================= +django-analytical +================= -The django-analytical application integrates various analytics services -into a Django_ project. +The django-analytical application integrates analytics services into a +Django_ project. .. _Django: http://www.djangoproject.com/ @@ -14,17 +14,9 @@ into a Django_ project. Overview ======== -If your want to integrating an analytics service into a Django project, -you need to add Javascript tracking code to the project templates. -Of course, every service has its own specific installation instructions. -Furthermore, you need to include your unique identifiers, which then end -up in the templates. This application hides the details of the -different analytics services behind a generic interface, and keeps -personal information and configuration out of the templates. Its goal -is to make basic usage set-up very simple, while allowing advanced users -to customize tracking. Each service is set up as recommended by the -services themselves, using an asynchronous version of the Javascript -code if possible. +.. include:: ../README.rst + :start-line: 6 + :end-line: 17 To get a feel of how django-analytics works, check out the :doc:`tutorial`. diff --git a/docs/services/hubspot.rst b/docs/services/hubspot.rst index 59245ae..bc7014e 100644 --- a/docs/services/hubspot.rst +++ b/docs/services/hubspot.rst @@ -51,13 +51,13 @@ portal ID and domain. Setting the portal ID and domain -------------------------------- -Your HubSpot account has its own portal ID and primary websit, and the -:ttag:`hubspot` tag will include them in the rendered Javascript code. -You can find the portal ID and domain by going to the *Domains* tab in -your HubSpot account. The domain you need to use is listed as *Primary -Domain* on that page, and the portal ID can be found in the footer. Set -:const:`HUBSPOT_PORTAL_ID` and :const:`HUBSPOT_DOMAIN` in the -project :file:`settings.py` file:: +Your HubSpot account has its own portal ID and primary domain name, and +the :ttag:`hubspot` tag will include them in the rendered Javascript +code. You can find the portal ID and domain by going to the *Domains* +tab in your HubSpot account. The domain you need to use is listed as +*Primary Domain* on that page, and the portal ID can be found in the +footer. Set :const:`HUBSPOT_PORTAL_ID` and :const:`HUBSPOT_DOMAIN` in +the project :file:`settings.py` file:: HUBSPOT_PORTAL_ID = 'XXXX' HUBSPOT_DOMAIN = 'XXXXXXXX.web101.hubspot.com' diff --git a/docs/services/optimizely.rst b/docs/services/optimizely.rst index 16137c4..39aa890 100644 --- a/docs/services/optimizely.rst +++ b/docs/services/optimizely.rst @@ -26,9 +26,9 @@ This step is only needed if you are not using the generic :ref:`optimizely-configuration`. The Optimizely Javascript code is inserted into templates using a -template tag. Load the :mod:`mixpanel` template tag library and insert -the :ttag:`optimizely` tag. Because every page that you want to track -must have the tag, it is useful to add it to your base template. +template tag. Load the :mod:`optimizely` template tag library and +insert the :ttag:`optimizely` tag. Because every page that you want to +track must have the tag, it is useful to add it to your base template. Insert the tag at the top of the HTML head:: {% load optimizely %} @@ -54,7 +54,7 @@ Setting the account number Optimizely gives you a unique account number, and the :ttag:`optimizely` tag will include it in the rendered Javascript code. You can find your -account number by clicking the `Implementation` link in the top +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:: diff --git a/docs/services/performable.rst b/docs/services/performable.rst new file mode 100644 index 0000000..c13dc2d --- /dev/null +++ b/docs/services/performable.rst @@ -0,0 +1,133 @@ +============================================== +Performable -- web analytics and landing pages +============================================== + +Performable_ provides a platform for inbound marketing, landing pages +and web analytics. Its analytics module tracks individual customer +interaction, funnel and e-commerce analysis. Landing pages can be +created and designed on-line, and integrated with you existing website. + +.. _Performable: http://www.performable.com/ + + +.. performable-installation: + +Installation +============ + +To start using the Performable integration, you must have installed the +django-analytical package and have added the ``analytical`` application +to :const:`INSTALLED_APPS` in your project :file:`settings.py` file. +See :doc:`../install` for details. + +Next you need to add the Performable template tags to your templates. +This step is only needed if you are not using the generic +:ttag:`analytical.*` tags. If you are, skip to +:ref:`performable-configuration`. + +The Performable Javascript code is inserted into templates using a +template tag. Load the :mod:`performable` template tag library and +insert the :ttag:`performable` tag. Because every page that you want to +track must have the tag, it is useful to add it to your base template. +Insert the tag at the bottom of the HTML body:: + + {% load performable %} + ... + {% performable %} + + + + +.. _performable-configuration: + +Configuration +============= + +Before you can use the Performable integration, you must first set your +API key. + + +.. _performable-account-code: + +Setting the API key +------------------- + +You Performable account has its own API key, which :ttag:`performable` +tag will include it in the rendered Javascript code. You can find your +API key on the *Account Settings* page (click 'Account Settings' in the +top right-hand corner of your Performable dashboard. Set +:const:`PERFORMABLE_API_KEY` in the project :file:`settings.py` file:: + + PERFORMABLE_API_KEY = 'XXXXXX' + +If you do not set an API key, the Javascript code will not be rendered. + + +.. _performable-identity-user: + +Identifying authenticated users +------------------------------- + +If your websites identifies visitors, you can pass this information on +to Performable so that you can tie survey submissions to customers. +By default, the username of an authenticated user is passed to +Performable automatically. See :ref:`identifying-visitors`. + +You can also send the visitor identity yourself by adding either the +``performable_identity`` or the ``analytical_identity`` variable to +the template context. If both variables are set, the former takes +precedence. For example:: + + context = RequestContext({'performable_identity': identity}) + return some_template.render(context) + +If you can derive the identity from the HTTP request, you can also use +a context processor that you add to the +:data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`:: + + def identify(request): + try: + return {'performable_identity': request.user.email} + except AttributeError: + return {} + +Just remember that if you set the same context variable in the +:class:`~django.template.context.RequestContext` constructor and in a +context processor, the latter clobbers the former. + + +.. _performable-internal-ips: + +Internal IP addresses +--------------------- + +Usually you do not want to track clicks from your development or +internal IP addresses. By default, if the tags detect that the client +comes from any address in the :const:`PERFORMABLE_INTERNAL_IPS` setting, +the tracking code is commented out. It takes the value of +:const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is +:const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for +important information about detecting the visitor IP address. + + +.. _performable-embed-page: + +Embedding a landing page +======================== + +You can embed a Performable landing page in your Django website. The +:ttag:`performable_embed` template tag adds the Javascript code to embed +the page. It takes two arguments: the hostname and the page ID:: + + {% performable_embed HOSTNAME PAGE_ID %} + +To find the hostname and page ID, select :menuselection:`Manage --> +Manage Landing Pages` on your Performable dashboard. Select the landing +page you want to embed. Look at the URL in your browser address bar; it +will look like this:: + + http://my.performable.com/s/HOSTNAME/page/PAGE_ID/ + +(If you are placing the hostname and page id values in the template, do +not forget to enclose them in quotes or they will be considered context +variable names.) diff --git a/docs/settings.rst b/docs/settings.rst index 71fe235..46b203a 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -9,16 +9,20 @@ their default values. .. data:: ANALYTICAL_AUTO_IDENTIFY - Default: ``True`` + Default: ``True`` - Automatically identify logged in users by their username. See - :ref:`identifying-visitors`. + Automatically identify logged in users by their username. See + :ref:`identifying-visitors`. .. data:: ANALYTICAL_INTERNAL_IPS - Default: :data:`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. See - :ref:`internal-ips`. + A list or tuple of internal IP addresses. Tracking code will be + commented out for visitors from any of these addresses. You can + configure this setting for each service individually by substituting + ``ANALYTICAL`` for the upper-case service name. For example, set + ``GOOGLE_ANALYTICS_INTERNAL_IPS`` to configure for Google Analytics. + + See :ref:`internal-ips`. diff --git a/setup.py b/setup.py index dcf0dfb..4eb8a59 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ setup( name = 'django-analytical', version = analytical.__version__, license = analytical.__license__, - description = 'Analytics services for Django projects', + description = 'Analytics service integration for Django projects', long_description = read('README.rst'), author = analytical.__author__, author_email = analytical.__email__, @@ -51,6 +51,7 @@ setup( 'analytical', 'analytical.templatetags', 'analytical.tests', + 'analytical.tests.templatetags', ], keywords = ['django', 'analytics'], classifiers = [