mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Merge branch 'master' into woopra-config-options
This commit is contained in:
commit
31ee7414f4
71 changed files with 1865 additions and 494 deletions
54
.travis.yml
54
.travis.yml
|
|
@ -1,23 +1,39 @@
|
|||
language: python
|
||||
python: "3.5"
|
||||
cache: pip
|
||||
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
env:
|
||||
- DJANGO=1.7
|
||||
- DJANGO=1.8
|
||||
- DJANGO=1.9
|
||||
- DJANGO=1.10
|
||||
- DJANGO=1.11
|
||||
- DJANGO=2.0
|
||||
- DJANGO=2.1
|
||||
matrix:
|
||||
exclude:
|
||||
# Python/Django combinations that aren't officially supported
|
||||
- { python: 3.5, env: DJANGO=1.7 }
|
||||
- { python: 3.6, env: DJANGO=1.7 }
|
||||
- { python: 3.6, env: DJANGO=1.8 }
|
||||
- { python: 3.6, env: DJANGO=1.9 }
|
||||
- { python: 3.6, env: DJANGO=1.10 }
|
||||
- { python: 2.7, env: DJANGO=2.0 }
|
||||
- { python: 2.7, env: DJANGO=2.1 }
|
||||
- { python: 3.4, env: DJANGO=2.1 }
|
||||
include:
|
||||
- { python: 3.6, env: TOXENV=flake8 }
|
||||
- { python: 3.6, env: TOXENV=readme }
|
||||
# Work around Travis Python 3.7 issue: https://github.com/travis-ci/travis-ci/issues/9815
|
||||
- { python: 3.7, env: DJANGO=1.11, dist: xenial, sudo: true }
|
||||
- { python: 3.7, env: DJANGO=2.0, dist: xenial, sudo: true }
|
||||
- { python: 3.7, env: DJANGO=2.1, dist: xenial, sudo: true }
|
||||
|
||||
install:
|
||||
# continue to support Python 3.2 (see issue #84)
|
||||
- pip install "virtualenv<14.0.0"
|
||||
- pip install coveralls tox
|
||||
- pip install tox-travis
|
||||
script:
|
||||
- tox
|
||||
env:
|
||||
# NOTE: To generate (update) the env list run
|
||||
# $ tox -l | sort | xargs -I ITEM echo " - TOXENV="ITEM
|
||||
- TOXENV=py27-django17
|
||||
- TOXENV=py27-django18
|
||||
- TOXENV=py27-django19
|
||||
- TOXENV=py32-django17
|
||||
- TOXENV=py32-django18
|
||||
- TOXENV=py33-django17
|
||||
- TOXENV=py33-django18
|
||||
- TOXENV=py34-django17
|
||||
- TOXENV=py34-django18
|
||||
- TOXENV=py34-django19
|
||||
- TOXENV=py35-django18
|
||||
- TOXENV=py35-django19
|
||||
|
|
|
|||
24
AUTHORS.rst
24
AUTHORS.rst
|
|
@ -1,10 +1,12 @@
|
|||
The django-analytical package was written by `Joost Cassee`_, with
|
||||
contributions from `Eric Davis`_, `Paul Oswald`_, `Uros Trebec`_,
|
||||
`Steven Skoczen`_, `Piet Delport`_, `Sandra Mau`_, `Simon Ye`_,
|
||||
`Tinnet Coronam`_, `Philippe O. Wagner`_, `Max Arnold`_ , `Martín
|
||||
Gaitán`_, `Craig Bruce`_, `Peter Bittner`_, `Scott Adams`_, `Eric Amador`_,
|
||||
`Alexandre Pocquet`_, `Brad Pitcher`_, `Hugo Osvaldo Barrera`_,
|
||||
`Nikolay Korotkiy`_, `Steve Schwarz`_, `Aleck Landgraf`_ and others.
|
||||
The django-analytical package was originally written by `Joost Cassee`_
|
||||
and is now maintained by the `Jazzband community`_, with contributions
|
||||
from `Eric Davis`_, `Paul Oswald`_, `Uros Trebec`_, `Steven Skoczen`_,
|
||||
`Pi Delport`_, `Sandra Mau`_, `Simon Ye`_, `Tinnet Coronam`_,
|
||||
`Philippe O. Wagner`_, `Max Arnold`_ , `Martín Gaitán`_, `Craig Bruce`_,
|
||||
`Peter Bittner`_, `Scott Adams`_, `Eric Amador`_, `Alexandre Pocquet`_,
|
||||
`Brad Pitcher`_, `Hugo Osvaldo Barrera`_, `Nikolay Korotkiy`_,
|
||||
`Steve Schwarz`_, `Aleck Landgraf`_, `Marc Bourqui`_,
|
||||
`Diederik van der Boor`_, `Matthäus G. Chajdas`_ and others.
|
||||
|
||||
Included Javascript code snippets for integration of the analytics
|
||||
services were written by the respective service providers.
|
||||
|
|
@ -15,12 +17,13 @@ Krall's all-purpose analytics front-end for Rails.
|
|||
The work on Crazy Egg was made possible by `Bateau Knowledge`_.
|
||||
The work on Intercom was made possible by `GreenKahuna`_.
|
||||
|
||||
.. _`Joost Cassee`: mailto:joost@cassee.net
|
||||
.. _`Joost Cassee`: https://github.com/jcassee
|
||||
.. _`Jazzband community`: https://jazzband.co/
|
||||
.. _`Eric Davis`: https://github.com/edavis
|
||||
.. _`Paul Oswald`: https://github.com/poswald
|
||||
.. _`Uros Trebec`: https://github.com/failedguidedog
|
||||
.. _`Steven Skoczen`: https://github.com/skoczen
|
||||
.. _`Piet Delport`: https://github.com/pjdelport
|
||||
.. _`Pi Delport`: https://github.com/pjdelport
|
||||
.. _`Sandra Mau`: https://github.com/xthepoet
|
||||
.. _`Simon Ye`: https://github.com/yesimon
|
||||
.. _`Tinnet Coronam`: https://github.com/tinnet
|
||||
|
|
@ -37,6 +40,9 @@ The work on Intercom was made possible by `GreenKahuna`_.
|
|||
.. _`Nikolay Korotkiy`: https://github.com/sikmir
|
||||
.. _`Steve Schwarz`: https://github.com/saschwarz
|
||||
.. _`Aleck Landgraf`: https://github.com/alecklandgraf
|
||||
.. _`Marc Bourqui`: https://github.com/mbourqui
|
||||
.. _`Diederik van der Boor`: https://github.com/vdboor
|
||||
.. _`Matthäus G. Chajdas`: https://github.com/Anteru
|
||||
.. _`Analytical`: https://github.com/jkrall/analytical
|
||||
.. _`Bateau Knowledge`: http://www.bateauknowledge.nl/
|
||||
.. _`GreenKahuna`: http://www.greenkahuna.com/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,23 @@
|
|||
Version 2.2.2
|
||||
Version 2.5.0
|
||||
-------------
|
||||
* Add support for Google analytics.js (Marc Bourqui)
|
||||
* Add support for Intercom HMAC identity verification (Pi Delport)
|
||||
* Add support for HotJar (Pi Delport)
|
||||
* Make sure _trackPageview happens before other settings in Google Analytics
|
||||
(Diederik van der Boor)
|
||||
|
||||
Version 2.4.0
|
||||
-------------
|
||||
* Support Django 2.0 (Matthäus G. Chajdas)
|
||||
|
||||
Version 2.3.0
|
||||
-------------
|
||||
* Add Facebook Pixel support (Pi Delport)
|
||||
* Add Python 3.6 and Django 1.10 & 1.11 tests (Pi Delport)
|
||||
* Drop Python 3.2 support
|
||||
|
||||
Version 2.2.2
|
||||
-------------
|
||||
* Allow port in Piwik domain path. (Alex Ramsay)
|
||||
|
||||
Version 2.2.1
|
||||
|
|
@ -78,7 +96,7 @@ Version 0.14.0
|
|||
Version 0.13.0
|
||||
--------------
|
||||
* Add support for the KISSmetrics alias feature (Sandra Mau)
|
||||
* Update testing code for Django 1.4 (Piet Delport)
|
||||
* Update testing code for Django 1.4 (Pi Delport)
|
||||
|
||||
Version 0.12.0
|
||||
--------------
|
||||
|
|
@ -101,7 +119,7 @@ Version 0.11.0
|
|||
--------------
|
||||
* Added support for the Spring Metrics service.
|
||||
* Allow sending events and properties to KISSmetrics (Paul Oswald).
|
||||
* Add support for the Site Speed report in Google Analytics (Uros
|
||||
* Add support for the Site Speed report in Google Analytics (Uros
|
||||
Trebec).
|
||||
|
||||
Version 0.10.0
|
||||
|
|
@ -121,7 +139,7 @@ Version 0.9.1
|
|||
Version 0.9.0
|
||||
-------------
|
||||
* Updated Clicky tracking code to support multiple site ids.
|
||||
* Fixed Chartbeat auto-domain bug when the Sites framework is not used
|
||||
* Fixed Chartbeat auto-domain bug when the Sites framework is not used
|
||||
(Eric Davis).
|
||||
* Improved testing code (Eric Davis).
|
||||
|
||||
|
|
|
|||
5
CONTRIBUTING.rst
Normal file
5
CONTRIBUTING.rst
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.. image:: https://jazzband.co/static/img/jazzband.svg
|
||||
:target: https://jazzband.co/
|
||||
:alt: Jazzband
|
||||
|
||||
This is a `Jazzband <https://jazzband.co>`_ project. By contributing you agree to abide by the `Contributor Code of Conduct <https://jazzband.co/about/conduct>`_ and follow the `guidelines <https://jazzband.co/about/guidelines>`_.
|
||||
48
README.rst
48
README.rst
|
|
@ -1,7 +1,7 @@
|
|||
django-analytical |latest-version|
|
||||
==================================
|
||||
|
||||
|travis-ci| |coveralls| |health| |python-support| |downloads| |license| |gitter|
|
||||
|travis-ci| |coveralls| |health| |python-support| |license| |gitter| |jazzband|
|
||||
|
||||
The django-analytical application integrates analytics services into a
|
||||
Django_ project.
|
||||
|
|
@ -26,27 +26,27 @@ an asynchronous version of the Javascript code if possible.
|
|||
.. |latest-version| image:: https://img.shields.io/pypi/v/django-analytical.svg
|
||||
:alt: Latest version on PyPI
|
||||
:target: https://pypi.python.org/pypi/django-analytical
|
||||
.. |travis-ci| image:: https://travis-ci.org/jcassee/django-analytical.svg
|
||||
.. |travis-ci| image:: https://img.shields.io/travis/jazzband/django-analytical/master.svg
|
||||
:alt: Build status
|
||||
:target: https://travis-ci.org/jcassee/django-analytical
|
||||
.. |coveralls| image:: https://coveralls.io/repos/jcassee/django-analytical/badge.svg
|
||||
:target: https://travis-ci.org/jazzband/django-analytical
|
||||
.. |coveralls| image:: https://coveralls.io/repos/jazzband/django-analytical/badge.svg
|
||||
:alt: Test coverage
|
||||
:target: https://coveralls.io/r/jcassee/django-analytical
|
||||
.. |health| image:: https://landscape.io/github/jcassee/django-analytical/master/landscape.svg?style=flat
|
||||
:target: https://landscape.io/github/jcassee/django-analytical/master
|
||||
:target: https://coveralls.io/r/jazzband/django-analytical
|
||||
.. |health| image:: https://landscape.io/github/jazzband/django-analytical/master/landscape.svg?style=flat
|
||||
:target: https://landscape.io/github/jazzband/django-analytical/master
|
||||
:alt: Code health
|
||||
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg
|
||||
:target: https://pypi.python.org/pypi/django-analytical
|
||||
:alt: Python versions
|
||||
.. |downloads| image:: https://img.shields.io/pypi/dm/django-analytical.svg
|
||||
:alt: Monthly downloads from PyPI
|
||||
:target: https://pypi.python.org/pypi/django-analytical
|
||||
.. |license| image:: https://img.shields.io/pypi/l/django-analytical.svg
|
||||
:alt: Software license
|
||||
:target: https://github.com/jcassee/django-analytical/blob/master/LICENSE.txt
|
||||
:target: https://github.com/jazzband/django-analytical/blob/master/LICENSE.txt
|
||||
.. |gitter| image:: https://badges.gitter.im/Join%20Chat.svg
|
||||
:alt: Gitter chat room
|
||||
:target: https://gitter.im/jcassee/django-analytical
|
||||
:target: https://gitter.im/jazzband/django-analytical
|
||||
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
|
||||
:alt: Jazzband
|
||||
:target: https://jazzband.co/
|
||||
.. _`Django`: http://www.djangoproject.com/
|
||||
|
||||
Currently Supported Services
|
||||
|
|
@ -56,9 +56,11 @@ Currently Supported Services
|
|||
* `Clickmap`_ visual click tracking
|
||||
* `Clicky`_ traffic analysis
|
||||
* `Crazy Egg`_ visual click tracking
|
||||
* `Facebook Pixel`_ advertising analytics
|
||||
* `Gaug.es`_ real time web analytics
|
||||
* `Google Analytics`_ traffic analysis
|
||||
* `GoSquared`_ traffic monitoring
|
||||
* `Hotjar`_ analytics and user feedback
|
||||
* `HubSpot`_ inbound marketing
|
||||
* `Intercom`_ live chat and support
|
||||
* `KISSinsights`_ feedback surveys
|
||||
|
|
@ -67,7 +69,7 @@ Currently Supported Services
|
|||
* `Olark`_ visitor chat
|
||||
* `Optimizely`_ A/B testing
|
||||
* `Performable`_ web analytics and landing pages
|
||||
* `Piwik`_ open source web analytics
|
||||
* `Matomo (formerly Piwik)`_ open source web analytics
|
||||
* `Rating\@Mail.ru`_ web analytics
|
||||
* `SnapEngage`_ live chat
|
||||
* `Spring Metrics`_ conversion tracking
|
||||
|
|
@ -79,9 +81,11 @@ Currently Supported Services
|
|||
.. _`Clickmap`: http://getclickmap.com/
|
||||
.. _`Clicky`: http://getclicky.com/
|
||||
.. _`Crazy Egg`: http://www.crazyegg.com/
|
||||
.. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/
|
||||
.. _`Gaug.es`: http://get.gaug.es/
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
.. _`GoSquared`: http://www.gosquared.com/
|
||||
.. _`Hotjar`: https://www.hotjar.com/
|
||||
.. _`HubSpot`: http://www.hubspot.com/
|
||||
.. _`Intercom`: http://www.intercom.io/
|
||||
.. _`KISSinsights`: http://www.kissinsights.com/
|
||||
|
|
@ -90,7 +94,7 @@ Currently Supported Services
|
|||
.. _`Olark`: http://www.olark.com/
|
||||
.. _`Optimizely`: http://www.optimizely.com/
|
||||
.. _`Performable`: http://www.performable.com/
|
||||
.. _`Piwik`: http://www.piwik.org/
|
||||
.. _`Matomo (formerly Piwik)`: https://matomo.org
|
||||
.. _`Rating\@Mail.ru`: http://top.mail.ru/
|
||||
.. _`SnapEngage`: http://www.snapengage.com/
|
||||
.. _`Spring Metrics`: http://www.springmetrics.com/
|
||||
|
|
@ -107,9 +111,9 @@ GitHub`_. Bugs should be reported there, whereas for lengthy chats
|
|||
and coding support when implementing new service integrations you're
|
||||
welcome to use our `Gitter chat room`_.
|
||||
|
||||
.. _`read online`: https://packages.python.org/django-analytical/
|
||||
.. _`hosted by GitHub`: https://github.com/jcassee/django-analytical
|
||||
.. _`Gitter chat room`: https://gitter.im/jcassee/django-analytical
|
||||
.. _`read online`: https://django-analytical.readthedocs.io/
|
||||
.. _`hosted by GitHub`: https://github.com/jazzband/django-analytical
|
||||
.. _`Gitter chat room`: https://gitter.im/jazzband/django-analytical
|
||||
|
||||
How To Contribute
|
||||
-----------------
|
||||
|
|
@ -123,7 +127,13 @@ services to support, or suggesting documentation improvements, use the
|
|||
the repository, make changes and place a `pull request`_. Creating an
|
||||
issue to discuss your plans is useful.
|
||||
|
||||
.. _`issue tracker`: https://github.com/jcassee/django-analytical/issues
|
||||
.. _`pull request`: https://github.com/jcassee/django-analytical/pulls
|
||||
This is a `Jazzband`_ project. By contributing you agree to abide by the
|
||||
`Contributor Code of Conduct`_ and follow the `guidelines`_.
|
||||
|
||||
.. _`issue tracker`: https://github.com/jazzband/django-analytical/issues
|
||||
.. _`pull request`: https://github.com/jazzband/django-analytical/pulls
|
||||
.. _`Jazzband`: https://jazzband.co
|
||||
.. _`Contributor Code of Conduct`: https://jazzband.co/about/conduct
|
||||
.. _`guidelines`: https://jazzband.co/about/guidelines
|
||||
|
||||
.. end contribute include
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
"""
|
||||
Analytics service integration for Django
|
||||
========================================
|
||||
|
||||
The django-analytical application integrates analytics services into a
|
||||
Django_ project. See the ``docs`` directory for more information.
|
||||
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
Analytics service integration for Django projects
|
||||
"""
|
||||
|
||||
__author__ = "Joost Cassee"
|
||||
__email__ = "joost@cassee.net"
|
||||
__version__ = "2.2.2"
|
||||
__copyright__ = "Copyright (C) 2011-2016 Joost Cassee and others"
|
||||
__version__ = "2.5.0"
|
||||
__copyright__ = "Copyright (C) 2011-2017 Joost Cassee and others"
|
||||
__license__ = "MIT License"
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ TAG_MODULES = [
|
|||
'analytical.clickmap',
|
||||
'analytical.clicky',
|
||||
'analytical.crazy_egg',
|
||||
'analytical.facebook_pixel',
|
||||
'analytical.gauges',
|
||||
'analytical.google_analytics',
|
||||
'analytical.google_analytics_js',
|
||||
'analytical.gosquared',
|
||||
'analytical.hotjar',
|
||||
'analytical.hubspot',
|
||||
'analytical.intercom',
|
||||
'analytical.kiss_insights',
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ SETUP_CODE = """
|
|||
loadChartbeat : function() { oldonload(); loadChartbeat(); };
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
DOMAIN_CONTEXT_KEY = 'chartbeat_domain'
|
||||
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ def chartbeat_bottom(parser, token):
|
|||
class ChartbeatBottomNode(Node):
|
||||
def __init__(self):
|
||||
self.user_id = get_required_setting('CHARTBEAT_USER_ID', USER_ID_RE,
|
||||
"must be (a string containing) a number")
|
||||
"must be (a string containing) a number")
|
||||
|
||||
def render(self, context):
|
||||
config = {'uid': self.user_id}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ TRACKING_CODE = """
|
|||
})();
|
||||
</script>
|
||||
<noscript><p><img alt="Clicky" width="1" height="1" src="//in.getclicky.com/%(site_id)sns.gif" /></p></noscript>
|
||||
"""
|
||||
|
||||
""" # noqa
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
@ -52,8 +51,9 @@ def clicky(parser, token):
|
|||
|
||||
class ClickyNode(Node):
|
||||
def __init__(self):
|
||||
self.site_id = get_required_setting('CLICKY_SITE_ID', SITE_ID_RE,
|
||||
"must be a (string containing) a number")
|
||||
self.site_id = get_required_setting(
|
||||
'CLICKY_SITE_ID', SITE_ID_RE,
|
||||
"must be a (string containing) a number")
|
||||
|
||||
def render(self, context):
|
||||
custom = {}
|
||||
|
|
@ -66,8 +66,10 @@ class ClickyNode(Node):
|
|||
if identity is not None:
|
||||
custom.setdefault('session', {})['username'] = identity
|
||||
|
||||
html = TRACKING_CODE % {'site_id': self.site_id,
|
||||
'custom': json.dumps(custom, sort_keys=True)}
|
||||
html = TRACKING_CODE % {
|
||||
'site_id': self.site_id,
|
||||
'custom': json.dumps(custom, sort_keys=True),
|
||||
}
|
||||
if is_internal_ip(context, 'CLICKY'):
|
||||
html = disable_html(html, 'Clicky')
|
||||
return html
|
||||
|
|
|
|||
97
analytical/templatetags/facebook_pixel.py
Normal file
97
analytical/templatetags/facebook_pixel.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
Facebook Pixel template tags and filters.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_required_setting, is_internal_ip, disable_html
|
||||
|
||||
|
||||
FACEBOOK_PIXEL_HEAD_CODE = """\
|
||||
<script>
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '%(FACEBOOK_PIXEL_ID)s');
|
||||
fbq('track', 'PageView');
|
||||
</script>
|
||||
"""
|
||||
|
||||
FACEBOOK_PIXEL_BODY_CODE = """\
|
||||
<noscript><img height="1" width="1" style="display:none"
|
||||
src="https://www.facebook.com/tr?id=%(FACEBOOK_PIXEL_ID)s&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
"""
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
def _validate_no_args(token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
|
||||
|
||||
@register.tag
|
||||
def facebook_pixel_head(parser, token):
|
||||
"""
|
||||
Facebook Pixel head template tag.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return FacebookPixelHeadNode()
|
||||
|
||||
|
||||
@register.tag
|
||||
def facebook_pixel_body(parser, token):
|
||||
"""
|
||||
Facebook Pixel body template tag.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return FacebookPixelBodyNode()
|
||||
|
||||
|
||||
class _FacebookPixelNode(Node):
|
||||
"""
|
||||
Base class: override and provide code_template.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.pixel_id = get_required_setting(
|
||||
'FACEBOOK_PIXEL_ID',
|
||||
re.compile(r'^\d+$'),
|
||||
"must be (a string containing) a number",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = self.code_template % {'FACEBOOK_PIXEL_ID': self.pixel_id}
|
||||
if is_internal_ip(context, 'FACEBOOK_PIXEL'):
|
||||
return disable_html(html, 'Facebook Pixel')
|
||||
else:
|
||||
return html
|
||||
|
||||
@property
|
||||
def code_template(self):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
|
||||
class FacebookPixelHeadNode(_FacebookPixelNode):
|
||||
code_template = FACEBOOK_PIXEL_HEAD_CODE
|
||||
|
||||
|
||||
class FacebookPixelBodyNode(_FacebookPixelNode):
|
||||
code_template = FACEBOOK_PIXEL_BODY_CODE
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
# ensure properly configured
|
||||
FacebookPixelHeadNode()
|
||||
FacebookPixelBodyNode()
|
||||
add_node('head_bottom', FacebookPixelHeadNode)
|
||||
add_node('body_bottom', FacebookPixelBodyNode)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
"""
|
||||
Google Analytics template tags and filters.
|
||||
|
||||
DEPRECATED
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
|
@ -32,7 +34,6 @@ SETUP_CODE = """
|
|||
|
||||
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;
|
||||
|
|
@ -44,15 +45,16 @@ SETUP_CODE = """
|
|||
"""
|
||||
DOMAIN_CODE = "_gaq.push(['_setDomainName', '%s']);"
|
||||
NO_ALLOW_HASH_CODE = "_gaq.push(['_setAllowHash', false]);"
|
||||
TRACK_PAGE_VIEW = "_gaq.push(['_trackPageview']);"
|
||||
ALLOW_LINKER_CODE = "_gaq.push(['_setAllowLinker', true]);"
|
||||
CUSTOM_VAR_CODE = "_gaq.push(['_setCustomVar', %(index)s, '%(name)s', " \
|
||||
"'%(value)s', %(scope)s]);"
|
||||
SITE_SPEED_CODE = "_gaq.push(['_trackPageLoadTime']);"
|
||||
ANONYMIZE_IP_CODE = "_gaq.push (['_gat._anonymizeIp']);"
|
||||
SAMPLE_RATE_CODE = "_gaq.push (['_setSampleRate', '%s']);"
|
||||
SITE_SPEED_SAMPLE_RATE_CODE = "_gaq.push (['_setSiteSpeedSampleRate', '%s']);"
|
||||
SESSION_COOKIE_TIMEOUT_CODE = "_gaq.push (['_setSessionCookieTimeout', '%s']);"
|
||||
VISITOR_COOKIE_TIMEOUT_CODE = "_gaq.push (['_setVisitorCookieTimeout', '%s']);"
|
||||
ANONYMIZE_IP_CODE = "_gaq.push(['_gat._anonymizeIp']);"
|
||||
SAMPLE_RATE_CODE = "_gaq.push(['_setSampleRate', '%s']);"
|
||||
SITE_SPEED_SAMPLE_RATE_CODE = "_gaq.push(['_setSiteSpeedSampleRate', '%s']);"
|
||||
SESSION_COOKIE_TIMEOUT_CODE = "_gaq.push(['_setSessionCookieTimeout', '%s']);"
|
||||
VISITOR_COOKIE_TIMEOUT_CODE = "_gaq.push(['_setVisitorCookieTimeout', '%s']);"
|
||||
DEFAULT_SOURCE = ("'https://ssl' : 'http://www'", "'.google-analytics.com/ga.js'")
|
||||
DISPLAY_ADVERTISING_SOURCE = ("'https://' : 'http://'", "'stats.g.doubleclick.net/dc.js'")
|
||||
|
||||
|
|
@ -87,14 +89,17 @@ class GoogleAnalyticsNode(Node):
|
|||
commands = self._get_domain_commands(context)
|
||||
commands.extend(self._get_custom_var_commands(context))
|
||||
commands.extend(self._get_other_commands(context))
|
||||
commands.append(TRACK_PAGE_VIEW)
|
||||
if getattr(settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False):
|
||||
source = DISPLAY_ADVERTISING_SOURCE
|
||||
else:
|
||||
source = DEFAULT_SOURCE
|
||||
html = SETUP_CODE % {'property_id': self.property_id,
|
||||
'commands': " ".join(commands),
|
||||
'source_scheme': source[0],
|
||||
'source_url': source[1]}
|
||||
html = SETUP_CODE % {
|
||||
'property_id': self.property_id,
|
||||
'commands': " ".join(commands),
|
||||
'source_scheme': source[0],
|
||||
'source_url': source[1],
|
||||
}
|
||||
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
||||
html = disable_html(html, 'Google Analytics')
|
||||
return html
|
||||
|
|
@ -109,8 +114,7 @@ class GoogleAnalyticsNode(Node):
|
|||
domain = get_domain(context, 'google_analytics')
|
||||
if domain is None:
|
||||
raise AnalyticalException(
|
||||
"tracking multiple domains with Google Analytics"
|
||||
" requires a domain name")
|
||||
"tracking multiple domains with Google Analytics requires a domain name")
|
||||
commands.append(DOMAIN_CODE % domain)
|
||||
commands.append(NO_ALLOW_HASH_CODE)
|
||||
if tracking_type == TRACK_MULTIPLE_DOMAINS:
|
||||
|
|
@ -157,7 +161,8 @@ class GoogleAnalyticsNode(Node):
|
|||
if siteSpeedSampleRate is not False:
|
||||
value = decimal.Decimal(siteSpeedSampleRate)
|
||||
if not 0 <= value <= 100:
|
||||
raise AnalyticalException("'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100")
|
||||
raise AnalyticalException(
|
||||
"'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100")
|
||||
commands.append(SITE_SPEED_SAMPLE_RATE_CODE % value.quantize(TWOPLACES))
|
||||
|
||||
sessionCookieTimeout = getattr(settings, 'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT', False)
|
||||
|
|
|
|||
155
analytical/templatetags/google_analytics_js.py
Normal file
155
analytical/templatetags/google_analytics_js.py
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
"""
|
||||
Google Analytics template tags and filters, using the new analytics.js library.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import decimal
|
||||
import re
|
||||
from django.conf import settings
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import (
|
||||
AnalyticalException,
|
||||
disable_html,
|
||||
get_domain,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
TRACK_SINGLE_DOMAIN = 1
|
||||
TRACK_MULTIPLE_SUBDOMAINS = 2
|
||||
TRACK_MULTIPLE_DOMAINS = 3
|
||||
|
||||
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
||||
SETUP_CODE = """
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){{i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){{
|
||||
(i[r].q=i[r].q||[]).push(arguments)}},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
}})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{property_id}', 'auto', {create_fields});
|
||||
{display_features}
|
||||
ga('send', 'pageview');
|
||||
{commands}
|
||||
</script>
|
||||
"""
|
||||
REQUIRE_DISPLAY_FEATURES = "ga('require', 'displayfeatures');"
|
||||
CUSTOM_VAR_CODE = "ga('set', '{name}', {value});"
|
||||
ANONYMIZE_IP_CODE = "ga('set', 'anonymizeIp', true);"
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.tag
|
||||
def google_analytics_js(parser, token):
|
||||
"""
|
||||
Google Analytics tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
your website property ID (as a string) in the
|
||||
``GOOGLE_ANALYTICS_JS_PROPERTY_ID`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
return GoogleAnalyticsJsNode()
|
||||
|
||||
|
||||
class GoogleAnalyticsJsNode(Node):
|
||||
def __init__(self):
|
||||
self.property_id = get_required_setting(
|
||||
'GOOGLE_ANALYTICS_JS_PROPERTY_ID', PROPERTY_ID_RE,
|
||||
"must be a string looking like 'UA-XXXXXX-Y'")
|
||||
|
||||
def render(self, context):
|
||||
import json
|
||||
create_fields = self._get_domain_fields(context)
|
||||
create_fields.update(self._get_other_create_fields(context))
|
||||
commands = self._get_custom_var_commands(context)
|
||||
commands.extend(self._get_other_commands(context))
|
||||
display_features = getattr(settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False)
|
||||
html = SETUP_CODE.format(
|
||||
property_id=self.property_id,
|
||||
create_fields=json.dumps(create_fields),
|
||||
display_features=REQUIRE_DISPLAY_FEATURES if display_features else '',
|
||||
commands=" ".join(commands),
|
||||
)
|
||||
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
||||
html = disable_html(html, 'Google Analytics')
|
||||
return html
|
||||
|
||||
def _get_domain_fields(self, context):
|
||||
domain_fields = {}
|
||||
tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', TRACK_SINGLE_DOMAIN)
|
||||
if tracking_type == TRACK_SINGLE_DOMAIN:
|
||||
pass
|
||||
else:
|
||||
domain = get_domain(context, 'google_analytics')
|
||||
if domain is None:
|
||||
raise AnalyticalException(
|
||||
"tracking multiple domains with Google Analytics requires a domain name")
|
||||
domain_fields['legacyCookieDomain'] = domain
|
||||
if tracking_type == TRACK_MULTIPLE_DOMAINS:
|
||||
domain_fields['allowLinker'] = True
|
||||
return domain_fields
|
||||
|
||||
def _get_other_create_fields(self, context):
|
||||
other_fields = {}
|
||||
|
||||
site_speed_sample_rate = getattr(settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False)
|
||||
if site_speed_sample_rate is not False:
|
||||
value = int(decimal.Decimal(site_speed_sample_rate))
|
||||
if not 0 <= value <= 100:
|
||||
raise AnalyticalException(
|
||||
"'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100")
|
||||
other_fields['siteSpeedSampleRate'] = value
|
||||
|
||||
sample_rate = getattr(settings, 'GOOGLE_ANALYTICS_SAMPLE_RATE', False)
|
||||
if sample_rate is not False:
|
||||
value = int(decimal.Decimal(sample_rate))
|
||||
if not 0 <= value <= 100:
|
||||
raise AnalyticalException("'GOOGLE_ANALYTICS_SAMPLE_RATE' must be >= 0 and <= 100")
|
||||
other_fields['sampleRate'] = value
|
||||
|
||||
cookie_expires = getattr(settings, 'GOOGLE_ANALYTICS_COOKIE_EXPIRATION', False)
|
||||
if cookie_expires is not False:
|
||||
value = int(decimal.Decimal(cookie_expires))
|
||||
if value < 0:
|
||||
raise AnalyticalException("'GOOGLE_ANALYTICS_COOKIE_EXPIRATION' must be >= 0")
|
||||
other_fields['cookieExpires'] = value
|
||||
|
||||
return other_fields
|
||||
|
||||
def _get_custom_var_commands(self, context):
|
||||
values = (
|
||||
context.get('google_analytics_var%s' % i) for i in range(1, 6)
|
||||
)
|
||||
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
|
||||
commands = []
|
||||
for _, var in params:
|
||||
name = var[0]
|
||||
value = var[1]
|
||||
try:
|
||||
float(value)
|
||||
except ValueError:
|
||||
value = "'{}'".format(value)
|
||||
commands.append(CUSTOM_VAR_CODE.format(
|
||||
name=name,
|
||||
value=value,
|
||||
))
|
||||
return commands
|
||||
|
||||
def _get_other_commands(self, context):
|
||||
commands = []
|
||||
|
||||
if getattr(settings, 'GOOGLE_ANALYTICS_ANONYMIZE_IP', False):
|
||||
commands.append(ANONYMIZE_IP_CODE)
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
GoogleAnalyticsJsNode() # ensure properly configured
|
||||
add_node('head_bottom', GoogleAnalyticsJsNode)
|
||||
|
|
@ -26,7 +26,7 @@ TRACKING_CODE = """
|
|||
w.addEventListener?w.addEventListener("load",gs,false):w.attachEvent("onload",gs);
|
||||
})(window);
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
TOKEN_CODE = 'GoSquared.acct = "%s";'
|
||||
IDENTIFY_CODE = 'GoSquared.UserName = "%s";'
|
||||
|
||||
|
|
|
|||
65
analytical/templatetags/hotjar.py
Normal file
65
analytical/templatetags/hotjar.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""
|
||||
Hotjar template tags and filters.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_required_setting, is_internal_ip, disable_html
|
||||
|
||||
|
||||
HOTJAR_TRACKING_CODE = """\
|
||||
<script>
|
||||
(function(h,o,t,j,a,r){
|
||||
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
||||
h._hjSettings={hjid:%(HOTJAR_SITE_ID)s,hjsv:6};
|
||||
a=o.getElementsByTagName('head')[0];
|
||||
r=o.createElement('script');r.async=1;
|
||||
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
def _validate_no_args(token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
|
||||
|
||||
@register.tag
|
||||
def hotjar(parser, token):
|
||||
"""
|
||||
Hotjar template tag.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return HotjarNode()
|
||||
|
||||
|
||||
class HotjarNode(Node):
|
||||
|
||||
def __init__(self):
|
||||
self.site_id = get_required_setting(
|
||||
'HOTJAR_SITE_ID',
|
||||
re.compile(r'^\d+$'),
|
||||
"must be (a string containing) a number",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = HOTJAR_TRACKING_CODE % {'HOTJAR_SITE_ID': self.site_id}
|
||||
if is_internal_ip(context, 'HOTJAR'):
|
||||
return disable_html(html, 'Hotjar')
|
||||
else:
|
||||
return html
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
# ensure properly configured
|
||||
HotjarNode()
|
||||
add_node('head_bottom', HotjarNode)
|
||||
|
|
@ -23,7 +23,7 @@ TRACKING_CODE = """
|
|||
})(document,"script","hs-analytics",300000);
|
||||
</script>
|
||||
<!-- End of Async HubSpot Analytics Code -->
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
@ -44,8 +44,8 @@ def hubspot(parser, token):
|
|||
|
||||
class HubSpotNode(Node):
|
||||
def __init__(self):
|
||||
self.portal_id = get_required_setting('HUBSPOT_PORTAL_ID',
|
||||
PORTAL_ID_RE, "must be a (string containing a) number")
|
||||
self.portal_id = get_required_setting('HUBSPOT_PORTAL_ID', PORTAL_ID_RE,
|
||||
"must be a (string containing a) number")
|
||||
|
||||
def render(self, context):
|
||||
html = TRACKING_CODE % {'portal_id': self.portal_id}
|
||||
|
|
|
|||
|
|
@ -3,26 +3,68 @@ intercom.io template tags and filters.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, \
|
||||
is_internal_ip, get_user_from_context, get_identity
|
||||
is_internal_ip, get_user_from_context, get_identity, \
|
||||
get_user_is_authenticated
|
||||
|
||||
APP_ID_RE = re.compile(r'[\da-f]+$')
|
||||
APP_ID_RE = re.compile(r'[\da-z]+$')
|
||||
TRACKING_CODE = """
|
||||
<script id="IntercomSettingsScriptTag">
|
||||
window.intercomSettings = %(settings_json)s;
|
||||
</script>
|
||||
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
def _timestamp(when): # type: (datetime) -> float
|
||||
"""
|
||||
Python 2 compatibility for `datetime.timestamp()`.
|
||||
"""
|
||||
return (time.mktime(when.timetuple()) if sys.version_info < (3,) else
|
||||
when.timestamp())
|
||||
|
||||
|
||||
def _hashable_bytes(data): # type: (AnyStr) -> bytes
|
||||
"""
|
||||
Coerce strings to hashable bytes.
|
||||
"""
|
||||
if isinstance(data, bytes):
|
||||
return data
|
||||
elif isinstance(data, str):
|
||||
return data.encode('ascii') # Fail on anything non-ASCII.
|
||||
else:
|
||||
raise TypeError(data)
|
||||
|
||||
|
||||
def intercom_user_hash(data): # type: (AnyStr) -> Optional[str]
|
||||
"""
|
||||
Return a SHA-256 HMAC `user_hash` as expected by Intercom, if configured.
|
||||
|
||||
Return None if the `INTERCOM_HMAC_SECRET_KEY` setting is not configured.
|
||||
"""
|
||||
if getattr(settings, 'INTERCOM_HMAC_SECRET_KEY', None):
|
||||
return hmac.new(
|
||||
key=_hashable_bytes(settings.INTERCOM_HMAC_SECRET_KEY),
|
||||
msg=_hashable_bytes(data),
|
||||
digestmod=hashlib.sha256,
|
||||
).hexdigest()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@register.tag
|
||||
def intercom(parser, token):
|
||||
"""
|
||||
|
|
@ -58,31 +100,39 @@ class IntercomNode(Node):
|
|||
params[var[9:]] = val
|
||||
|
||||
user = get_user_from_context(context)
|
||||
if user is not None and user.is_authenticated():
|
||||
if user is not None and get_user_is_authenticated(user):
|
||||
if 'name' not in params:
|
||||
params['name'] = get_identity(
|
||||
context, 'intercom', self._identify, user)
|
||||
if 'email' not in params and user.email:
|
||||
params['email'] = user.email
|
||||
|
||||
params['created_at'] = int(time.mktime(
|
||||
user.date_joined.timetuple()))
|
||||
params.setdefault('user_id', user.pk)
|
||||
|
||||
params['created_at'] = int(_timestamp(user.date_joined))
|
||||
else:
|
||||
params['created_at'] = None
|
||||
|
||||
# Generate a user_hash HMAC to verify the user's identity, if configured.
|
||||
# (If both user_id and email are present, the user_id field takes precedence.)
|
||||
# See:
|
||||
# https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
|
||||
user_hash_data = params.get('user_id', params.get('email')) # type: Optional[str]
|
||||
if user_hash_data:
|
||||
user_hash = intercom_user_hash(str(user_hash_data)) # type: Optional[str]
|
||||
if user_hash is not None:
|
||||
params.setdefault('user_hash', user_hash)
|
||||
|
||||
return params
|
||||
|
||||
def render(self, context):
|
||||
user = get_user_from_context(context)
|
||||
params = self._get_custom_attrs(context)
|
||||
params["app_id"] = self.app_id
|
||||
html = TRACKING_CODE % {
|
||||
"settings_json": json.dumps(params, sort_keys=True)
|
||||
}
|
||||
|
||||
if is_internal_ip(context, 'INTERCOM') \
|
||||
or not user or not user.is_authenticated():
|
||||
# Intercom is disabled for non-logged in users.
|
||||
if is_internal_ip(context, 'INTERCOM'):
|
||||
html = disable_html(html, 'Intercom')
|
||||
return html
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ SITE_CODE_RE = re.compile(r'^[\w]+$')
|
|||
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>
|
||||
"""
|
||||
""" # noqa
|
||||
IDENTIFY_CODE = "_kiq.push(['identify', '%s']);"
|
||||
SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);"
|
||||
SHOW_SURVEY_CONTEXT_KEY = 'kiss_insights_show_survey'
|
||||
|
|
@ -44,10 +44,11 @@ def kiss_insights(parser, token):
|
|||
class KissInsightsNode(Node):
|
||||
def __init__(self):
|
||||
self.account_number = get_required_setting(
|
||||
'KISS_INSIGHTS_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
||||
"must be (a string containing) a number")
|
||||
self.site_code = get_required_setting('KISS_INSIGHTS_SITE_CODE',
|
||||
SITE_CODE_RE, "must be a string containing three characters")
|
||||
'KISS_INSIGHTS_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
||||
"must be (a string containing) a number")
|
||||
self.site_code = get_required_setting(
|
||||
'KISS_INSIGHTS_SITE_CODE', SITE_CODE_RE,
|
||||
"must be a string containing three characters")
|
||||
|
||||
def render(self, context):
|
||||
commands = []
|
||||
|
|
@ -55,12 +56,14 @@ class KissInsightsNode(Node):
|
|||
if identity is not None:
|
||||
commands.append(IDENTIFY_CODE % identity)
|
||||
try:
|
||||
commands.append(SHOW_SURVEY_CODE
|
||||
% context[SHOW_SURVEY_CONTEXT_KEY])
|
||||
commands.append(SHOW_SURVEY_CODE % context[SHOW_SURVEY_CONTEXT_KEY])
|
||||
except KeyError:
|
||||
pass
|
||||
html = SETUP_CODE % {'account_number': self.account_number,
|
||||
'site_code': self.site_code, 'commands': " ".join(commands)}
|
||||
html = SETUP_CODE % {
|
||||
'account_number': self.account_number,
|
||||
'site_code': self.site_code,
|
||||
'commands': " ".join(commands),
|
||||
}
|
||||
return html
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ def kiss_metrics(parser, token):
|
|||
|
||||
class KissMetricsNode(Node):
|
||||
def __init__(self):
|
||||
self.api_key = get_required_setting('KISS_METRICS_API_KEY',
|
||||
API_KEY_RE,
|
||||
"must be a string containing a 40-digit hexadecimal number")
|
||||
self.api_key = get_required_setting(
|
||||
'KISS_METRICS_API_KEY', API_KEY_RE,
|
||||
"must be a string containing a 40-digit hexadecimal number")
|
||||
|
||||
def render(self, context):
|
||||
commands = []
|
||||
|
|
@ -78,18 +78,23 @@ class KissMetricsNode(Node):
|
|||
pass
|
||||
try:
|
||||
name, properties = context[EVENT_CONTEXT_KEY]
|
||||
commands.append(EVENT_CODE % {'name': name,
|
||||
'properties': json.dumps(properties, sort_keys=True)})
|
||||
commands.append(EVENT_CODE % {
|
||||
'name': name,
|
||||
'properties': json.dumps(properties, sort_keys=True),
|
||||
})
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
properties = context[PROPERTY_CONTEXT_KEY]
|
||||
commands.append(PROPERTY_CODE % {
|
||||
'properties': json.dumps(properties, sort_keys=True)})
|
||||
'properties': json.dumps(properties, sort_keys=True),
|
||||
})
|
||||
except KeyError:
|
||||
pass
|
||||
html = TRACKING_CODE % {'api_key': self.api_key,
|
||||
'commands': " ".join(commands)}
|
||||
html = TRACKING_CODE % {
|
||||
'api_key': self.api_key,
|
||||
'commands': " ".join(commands),
|
||||
}
|
||||
if is_internal_ip(context, 'KISS_METRICS'):
|
||||
html = disable_html(html, 'KISSmetrics')
|
||||
return html
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
|
|||
mixpanel.init('%(token)s');
|
||||
%(commands)s
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
IDENTIFY_CODE = "mixpanel.identify('%s');"
|
||||
IDENTIFY_PROPERTIES = "mixpanel.people.set(%s);"
|
||||
EVENT_CODE = "mixpanel.track('%(name)s', %(properties)s);"
|
||||
|
|
@ -62,12 +62,16 @@ class MixpanelNode(Node):
|
|||
commands.append(IDENTIFY_CODE % identity)
|
||||
try:
|
||||
name, properties = context[EVENT_CONTEXT_KEY]
|
||||
commands.append(EVENT_CODE % {'name': name,
|
||||
'properties': json.dumps(properties, sort_keys=True)})
|
||||
commands.append(EVENT_CODE % {
|
||||
'name': name,
|
||||
'properties': json.dumps(properties, sort_keys=True),
|
||||
})
|
||||
except KeyError:
|
||||
pass
|
||||
html = TRACKING_CODE % {'token': self._token,
|
||||
'commands': " ".join(commands)}
|
||||
html = TRACKING_CODE % {
|
||||
'token': self._token,
|
||||
'commands': " ".join(commands),
|
||||
}
|
||||
if is_internal_ip(context, 'MIXPANEL'):
|
||||
html = disable_html(html, 'Mixpanel')
|
||||
return mark_safe(html)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ SETUP_CODE = """
|
|||
/*{literal}<![CDATA[*/ window.olark||(function(k){var g=window,j=document,a=g.location.protocol=="https:"?"https:":"http:",i=k.name,b="load",h="addEventListener";(function(){g[i]=function(){(c.s=c.s||[]).push(arguments)};var c=g[i]._={},f=k.methods.length;while(f--){(function(l){g[i][l]=function(){g[i]("call",l,arguments)}})(k.methods[f])}c.l=k.loader;c.i=arguments.callee;c.p={0:+new Date};c.P=function(l){c.p[l]=new Date-c.p[0]};function e(){c.P(b);g[i](b)}g[h]?g[h](b,e,false):g.attachEvent("on"+b,e);c.P(1);var d=j.createElement("script"),m=document.getElementsByTagName("script")[0];d.type="text/javascript";d.async=true;d.src=a+"//"+c.l;m.parentNode.insertBefore(d,m);c.P(2)})()})({loader:(function(a){return "static.olark.com/jsclient/loader1.js?ts="+(a?a[1]:(+new Date))})(document.cookie.match(/olarkld=([0-9]+)/)),name:"olark",methods:["configure","extend","declare","identify"]}); olark.identify('%(site_id)s');/*]]>{/literal}*/
|
||||
%(extra_code)s
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
NICKNAME_CODE = "olark('api.chat.updateVisitorNickname', {snippet: '%s'});"
|
||||
NICKNAME_CONTEXT_KEY = 'olark_nickname'
|
||||
FULLNAME_CODE = "olark('api.visitor.updateFullName', {{fullName: '{0}'}});"
|
||||
|
|
@ -28,14 +28,16 @@ EMAIL_CONTEXT_KEY = 'olark_email'
|
|||
STATUS_CODE = "olark('api.chat.updateVisitorStatus', {snippet: %s});"
|
||||
STATUS_CONTEXT_KEY = 'olark_status'
|
||||
MESSAGE_CODE = "olark.configure('locale.%(key)s', \"%(msg)s\");"
|
||||
MESSAGE_KEYS = set(["welcome_title", "chatting_title", "unavailable_title",
|
||||
"busy_title", "away_message", "loading_title", "welcome_message",
|
||||
"busy_message", "chat_input_text", "name_input_text",
|
||||
"email_input_text", "offline_note_message", "send_button_text",
|
||||
"offline_note_thankyou_text", "offline_note_error_text",
|
||||
"offline_note_sending_text", "operator_is_typing_text",
|
||||
"operator_has_stopped_typing_text", "introduction_error_text",
|
||||
"introduction_messages", "introduction_submit_button_text"])
|
||||
MESSAGE_KEYS = {
|
||||
"welcome_title", "chatting_title", "unavailable_title",
|
||||
"busy_title", "away_message", "loading_title", "welcome_message",
|
||||
"busy_message", "chat_input_text", "name_input_text",
|
||||
"email_input_text", "offline_note_message", "send_button_text",
|
||||
"offline_note_thankyou_text", "offline_note_error_text",
|
||||
"offline_note_sending_text", "operator_is_typing_text",
|
||||
"operator_has_stopped_typing_text", "introduction_error_text",
|
||||
"introduction_messages", "introduction_submit_button_text",
|
||||
}
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
@ -56,8 +58,9 @@ def olark(parser, token):
|
|||
|
||||
class OlarkNode(Node):
|
||||
def __init__(self):
|
||||
self.site_id = get_required_setting('OLARK_SITE_ID', SITE_ID_RE,
|
||||
"must be a string looking like 'XXXX-XXX-XX-XXXX'")
|
||||
self.site_id = get_required_setting(
|
||||
'OLARK_SITE_ID', SITE_ID_RE,
|
||||
"must be a string looking like 'XXXX-XXX-XX-XXXX'")
|
||||
|
||||
def render(self, context):
|
||||
extra_code = []
|
||||
|
|
@ -76,13 +79,15 @@ class OlarkNode(Node):
|
|||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
extra_code.append(STATUS_CODE %
|
||||
json.dumps(context[STATUS_CONTEXT_KEY], sort_keys=True))
|
||||
extra_code.append(STATUS_CODE % json.dumps(context[STATUS_CONTEXT_KEY],
|
||||
sort_keys=True))
|
||||
except KeyError:
|
||||
pass
|
||||
extra_code.extend(self._get_configuration(context))
|
||||
html = SETUP_CODE % {'site_id': self.site_id,
|
||||
'extra_code': " ".join(extra_code)}
|
||||
html = SETUP_CODE % {
|
||||
'site_id': self.site_id,
|
||||
'extra_code': " ".join(extra_code),
|
||||
}
|
||||
return html
|
||||
|
||||
def _get_nickname(self, user):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
|||
API_KEY_RE = re.compile(r'^\w+$')
|
||||
SETUP_CODE = """
|
||||
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/pax/%(api_key)s.js" type="text/javascript"></script>
|
||||
"""
|
||||
""" # noqa
|
||||
IDENTIFY_CODE = """
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
|
|
@ -32,7 +32,7 @@ EMBED_CODE = """
|
|||
$f.write();
|
||||
})()
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
@ -54,8 +54,9 @@ def performable(parser, token):
|
|||
|
||||
class PerformableNode(Node):
|
||||
def __init__(self):
|
||||
self.api_key = get_required_setting('PERFORMABLE_API_KEY', API_KEY_RE,
|
||||
"must be a string looking like 'XXXXX'")
|
||||
self.api_key = get_required_setting(
|
||||
'PERFORMABLE_API_KEY', API_KEY_RE,
|
||||
"must be a string looking like 'XXXXX'")
|
||||
|
||||
def render(self, context):
|
||||
html = SETUP_CODE % {'api_key': self.api_key}
|
||||
|
|
@ -72,7 +73,10 @@ def performable_embed(hostname, page_id):
|
|||
"""
|
||||
Include a Performable landing page.
|
||||
"""
|
||||
return mark_safe(EMBED_CODE % {'hostname': hostname, 'page_id': page_id})
|
||||
return mark_safe(EMBED_CODE % {
|
||||
'hostname': hostname,
|
||||
'page_id': page_id,
|
||||
})
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from collections import namedtuple
|
|||
from itertools import chain
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import (is_internal_ip, disable_html,
|
||||
|
|
@ -24,21 +25,23 @@ TRACKING_CODE = """
|
|||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
%(variables)s
|
||||
%(commands)s
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u=(("https:" == document.location.protocol) ? "https" : "http") + "://%(url)s/";
|
||||
var u="//%(url)s/";
|
||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||
_paq.push(['setSiteId', %(siteid)s]);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
|
||||
g.defer=true; g.async=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img src="http://%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
|
||||
<noscript><p><img src="//%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
|
||||
""" # noqa
|
||||
|
||||
VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa
|
||||
IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);'
|
||||
DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);'
|
||||
|
||||
DEFAULT_SCOPE = 'page'
|
||||
|
||||
|
|
@ -89,6 +92,10 @@ class PiwikNode(Node):
|
|||
variables_code = (VARIABLE_CODE % PiwikVar(*var)._asdict()
|
||||
for var in complete_variables)
|
||||
|
||||
commands = []
|
||||
if getattr(settings, 'PIWIK_DISABLE_COOKIES', False):
|
||||
commands.append(DISABLE_COOKIES_CODE)
|
||||
|
||||
userid = get_identity(context, 'piwik')
|
||||
if userid is not None:
|
||||
variables_code = chain(variables_code, (
|
||||
|
|
@ -98,7 +105,8 @@ class PiwikNode(Node):
|
|||
html = TRACKING_CODE % {
|
||||
'url': self.domain_path,
|
||||
'siteid': self.site_id,
|
||||
'variables': '\n '.join(variables_code)
|
||||
'variables': '\n '.join(variables_code),
|
||||
'commands': '\n '.join(commands)
|
||||
}
|
||||
if is_internal_ip(context, 'PIWIK'):
|
||||
html = disable_html(html, 'Piwik')
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ Rating@Mail.ru template tags and filters.
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, \
|
||||
|
|
@ -30,7 +28,7 @@ COUNTER_CODE = """
|
|||
<noscript><div style="position:absolute;left:-10000px;">
|
||||
<img src="//top-fwz1.mail.ru/counter?id=%(counter_id)s;js=na" style="border:0;" height="1" width="1" alt="Rating@Mail.ru" />
|
||||
</div></noscript>
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
|
||||
register = Library()
|
||||
|
|
@ -54,8 +52,8 @@ def rating_mailru(parser, token):
|
|||
class RatingMailruNode(Node):
|
||||
def __init__(self):
|
||||
self.counter_id = get_required_setting(
|
||||
'RATING_MAILRU_COUNTER_ID', COUNTER_ID_RE,
|
||||
"must be (a string containing) a number'")
|
||||
'RATING_MAILRU_COUNTER_ID', COUNTER_ID_RE,
|
||||
"must be (a string containing) a number'")
|
||||
|
||||
def render(self, context):
|
||||
html = COUNTER_CODE % {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from django.utils import translation
|
|||
|
||||
from analytical.utils import get_identity, get_required_setting
|
||||
|
||||
|
||||
BUTTON_LOCATION_LEFT = 0
|
||||
BUTTON_LOCATION_RIGHT = 1
|
||||
BUTTON_LOCATION_TOP = 2
|
||||
|
|
@ -33,7 +32,7 @@ SETUP_CODE = """
|
|||
document.write(unescape("%%3Cscript src='" + ((document.location.protocol=="https:")?"https://snapabug.appspot.com":"http://www.snapengage.com") + "/snapabug.js' type='text/javascript'%%3E%%3C/script%%3E"));</script><script type="text/javascript">
|
||||
%(settings_code)s
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
DOMAIN_CODE = 'SnapABug.setDomain("%s");'
|
||||
SECURE_CONNECTION_CODE = 'SnapABug.setSecureConnexion();'
|
||||
INIT_CODE = 'SnapABug.init("%s");'
|
||||
|
|
@ -69,21 +68,22 @@ def snapengage(parser, token):
|
|||
|
||||
class SnapEngageNode(Node):
|
||||
def __init__(self):
|
||||
self.widget_id = get_required_setting('SNAPENGAGE_WIDGET_ID',
|
||||
WIDGET_ID_RE, "must be a string looking like this: "
|
||||
"'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'")
|
||||
self.widget_id = get_required_setting(
|
||||
'SNAPENGAGE_WIDGET_ID', WIDGET_ID_RE,
|
||||
"must be a string looking like this: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'")
|
||||
|
||||
def render(self, context):
|
||||
settings_code = []
|
||||
|
||||
domain = self._get_setting(context, 'snapengage_domain',
|
||||
'SNAPENGAGE_DOMAIN')
|
||||
'SNAPENGAGE_DOMAIN')
|
||||
if domain is not None:
|
||||
settings_code.append(DOMAIN_CODE % domain)
|
||||
|
||||
secure_connection = self._get_setting(context,
|
||||
'snapengage_secure_connection', 'SNAPENGAGE_SECURE_CONNECTION',
|
||||
False)
|
||||
'snapengage_secure_connection',
|
||||
'SNAPENGAGE_SECURE_CONNECTION',
|
||||
False)
|
||||
if secure_connection:
|
||||
settings_code.append(SECURE_CONNECTION_CODE)
|
||||
|
||||
|
|
@ -92,41 +92,42 @@ class SnapEngageNode(Node):
|
|||
email = get_identity(context, 'snapengage', lambda u: u.email)
|
||||
if email is not None:
|
||||
if self._get_setting(context, 'snapengage_readonly_email',
|
||||
'SNAPENGAGE_READONLY_EMAIL', False):
|
||||
'SNAPENGAGE_READONLY_EMAIL', False):
|
||||
readonly_tail = ',true'
|
||||
else:
|
||||
readonly_tail = ''
|
||||
settings_code.append(SETEMAIL_CODE % (email, readonly_tail))
|
||||
|
||||
locale = self._get_setting(context, 'snapengage_locale',
|
||||
'SNAPENGAGE_LOCALE')
|
||||
'SNAPENGAGE_LOCALE')
|
||||
if locale is None:
|
||||
locale = translation.to_locale(translation.get_language())
|
||||
settings_code.append(SETLOCALE_CODE % locale)
|
||||
|
||||
form_position = self._get_setting(context,
|
||||
'snapengage_form_position', 'SNAPENGAGE_FORM_POSITION')
|
||||
'snapengage_form_position', 'SNAPENGAGE_FORM_POSITION')
|
||||
if form_position is not None:
|
||||
settings_code.append(FORM_POSITION_CODE % form_position)
|
||||
|
||||
form_top_position = self._get_setting(context,
|
||||
'snapengage_form_top_position', 'SNAPENGAGE_FORM_TOP_POSITION')
|
||||
'snapengage_form_top_position',
|
||||
'SNAPENGAGE_FORM_TOP_POSITION')
|
||||
if form_top_position is not None:
|
||||
settings_code.append(FORM_TOP_POSITION_CODE % form_top_position)
|
||||
|
||||
show_offline = self._get_setting(context, 'snapengage_show_offline',
|
||||
'SNAPENGAGE_SHOW_OFFLINE', True)
|
||||
'SNAPENGAGE_SHOW_OFFLINE', True)
|
||||
if not show_offline:
|
||||
settings_code.append(DISABLE_OFFLINE_CODE)
|
||||
|
||||
screenshots = self._get_setting(context, 'snapengage_screenshots',
|
||||
'SNAPENGAGE_SCREENSHOTS', True)
|
||||
'SNAPENGAGE_SCREENSHOTS', True)
|
||||
if not screenshots:
|
||||
settings_code.append(DISABLE_SCREENSHOT_CODE)
|
||||
|
||||
offline_screenshots = self._get_setting(context,
|
||||
'snapengage_offline_screenshots',
|
||||
'SNAPENGAGE_OFFLINE_SCREENSHOTS', True)
|
||||
'snapengage_offline_screenshots',
|
||||
'SNAPENGAGE_OFFLINE_SCREENSHOTS', True)
|
||||
if not offline_screenshots:
|
||||
settings_code.append(DISABLE_OFFLINE_SCREENSHOT_CODE)
|
||||
|
||||
|
|
@ -134,37 +135,41 @@ class SnapEngageNode(Node):
|
|||
settings_code.append(DISABLE_PROACTIVE_CHAT_CODE)
|
||||
|
||||
sounds = self._get_setting(context, 'snapengage_sounds',
|
||||
'SNAPENGAGE_SOUNDS', True)
|
||||
'SNAPENGAGE_SOUNDS', True)
|
||||
if not sounds:
|
||||
settings_code.append(DISABLE_SOUNDS_CODE)
|
||||
|
||||
button_effect = self._get_setting(context, 'snapengage_button_effect',
|
||||
'SNAPENGAGE_BUTTON_EFFECT')
|
||||
'SNAPENGAGE_BUTTON_EFFECT')
|
||||
if button_effect is not None:
|
||||
settings_code.append(BUTTONEFFECT_CODE % button_effect)
|
||||
|
||||
button = self._get_setting(context, 'snapengage_button',
|
||||
'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT)
|
||||
'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT)
|
||||
if button == BUTTON_STYLE_NONE:
|
||||
settings_code.append(INIT_CODE % self.widget_id)
|
||||
else:
|
||||
if not isinstance(button, int):
|
||||
# Assume button as a URL to a custom image
|
||||
settings_code.append(SETBUTTON_CODE % button)
|
||||
button_location = self._get_setting(context,
|
||||
'snapengage_button_location', 'SNAPENGAGE_BUTTON_LOCATION',
|
||||
BUTTON_LOCATION_LEFT)
|
||||
button_offset = self._get_setting(context,
|
||||
'snapengage_button_location_offset',
|
||||
'SNAPENGAGE_BUTTON_LOCATION_OFFSET', '55%')
|
||||
button_location = self._get_setting(
|
||||
context,
|
||||
'snapengage_button_location', 'SNAPENGAGE_BUTTON_LOCATION',
|
||||
BUTTON_LOCATION_LEFT)
|
||||
button_offset = self._get_setting(
|
||||
context,
|
||||
'snapengage_button_location_offset',
|
||||
'SNAPENGAGE_BUTTON_LOCATION_OFFSET', '55%')
|
||||
settings_code.append(ADDBUTTON_CODE % {
|
||||
'id': self.widget_id,
|
||||
'location': button_location,
|
||||
'offset': button_offset,
|
||||
'dynamic_tail': ',true' if (button == BUTTON_STYLE_LIVE) else '',
|
||||
})
|
||||
html = SETUP_CODE % {'widget_id': self.widget_id,
|
||||
'settings_code': " ".join(settings_code)}
|
||||
})
|
||||
html = SETUP_CODE % {
|
||||
'widget_id': self.widget_id,
|
||||
'settings_code': " ".join(settings_code),
|
||||
}
|
||||
return html
|
||||
|
||||
def _get_setting(self, context, context_key, setting=None, default=None):
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ TRACKING_CODE = """
|
|||
)();
|
||||
%(custom_commands)s
|
||||
</script>
|
||||
"""
|
||||
|
||||
""" # noqa
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ def uservoice(parser, token):
|
|||
|
||||
class UserVoiceNode(Node):
|
||||
def __init__(self):
|
||||
self.default_widget_key = get_required_setting('USERVOICE_WIDGET_KEY',
|
||||
WIDGET_KEY_RE, "must be an alphanumeric string")
|
||||
self.default_widget_key = get_required_setting(
|
||||
'USERVOICE_WIDGET_KEY', WIDGET_KEY_RE, "must be an alphanumeric string")
|
||||
|
||||
def render(self, context):
|
||||
widget_key = context.get('uservoice_widget_key')
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from analytical.utils import (
|
|||
get_identity,
|
||||
get_required_setting,
|
||||
get_user_from_context,
|
||||
get_user_is_authenticated,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ TRACKING_CODE = """
|
|||
woopra.identify(woo_visitor);
|
||||
woopra.track();
|
||||
</script>
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
register = Library()
|
||||
|
||||
|
|
@ -138,7 +139,7 @@ class WoopraNode(Node):
|
|||
params[var[7:]] = val
|
||||
if 'name' not in params and 'email' not in params:
|
||||
user = get_user_from_context(context)
|
||||
if user is not None and user.is_authenticated():
|
||||
if user is not None and get_user_is_authenticated(user):
|
||||
params['name'] = get_identity(
|
||||
context, 'woopra', self._identify, user)
|
||||
if user.email:
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ COUNTER_CODE = """
|
|||
})(document, window, "yandex_metrika_callbacks");
|
||||
</script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/%(counter_id)s" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||
"""
|
||||
""" # noqa
|
||||
|
||||
|
||||
register = Library()
|
||||
|
|
|
|||
|
|
@ -22,3 +22,10 @@ MIDDLEWARE_CLASSES = (
|
|||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'APP_DIRS': True,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ def _location_node(location):
|
|||
return "<!-- dummy_%s -->" % location
|
||||
return DummyNode
|
||||
|
||||
|
||||
_location_nodes = dict((l, _location_node(l)) for l in TAG_LOCATIONS)
|
||||
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ def _location_tag(location):
|
|||
return _location_nodes[location]
|
||||
return dummy_tag
|
||||
|
||||
|
||||
for loc in TAG_LOCATIONS:
|
||||
register.tag('dummy_%s' % loc, _location_tag(loc))
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,7 @@ class AnalyticsTagTestCase(TagTestCase):
|
|||
def render_location_tag(self, location, vars=None):
|
||||
if vars is None:
|
||||
vars = {}
|
||||
t = Template("{%% load analytical %%}{%% analytical_%s %%}"
|
||||
% location)
|
||||
t = Template("{%% load analytical %%}{%% analytical_%s %%}" % location)
|
||||
return t.render(Context(vars))
|
||||
|
||||
def test_location_tags(self):
|
||||
|
|
|
|||
|
|
@ -39,10 +39,8 @@ class ChartbeatTagTestCaseWithSites(TestCase):
|
|||
site = Site.objects.create(domain="test.com", name="test")
|
||||
with override_settings(SITE_ID=site.id):
|
||||
r = ChartbeatBottomNode().render(Context())
|
||||
self.assertTrue(re.search(
|
||||
'var _sf_async_config={.*"uid": "12345".*};', r), r)
|
||||
self.assertTrue(re.search(
|
||||
'var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
||||
self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r)
|
||||
self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
||||
|
||||
@override_settings(CHARTBEAT_AUTO_DOMAIN=False)
|
||||
def test_auto_domain_false(self):
|
||||
|
|
@ -62,30 +60,26 @@ class ChartbeatTagTestCase(TagTestCase):
|
|||
"""
|
||||
|
||||
def test_top_tag(self):
|
||||
r = self.render_tag('chartbeat', 'chartbeat_top',
|
||||
{'chartbeat_domain': "test.com"})
|
||||
r = self.render_tag('chartbeat', 'chartbeat_top', {'chartbeat_domain': "test.com"})
|
||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
||||
|
||||
def test_bottom_tag(self):
|
||||
r = self.render_tag('chartbeat', 'chartbeat_bottom',
|
||||
{'chartbeat_domain': "test.com"})
|
||||
self.assertTrue(re.search(
|
||||
'var _sf_async_config={.*"uid": "12345".*};', r), r)
|
||||
self.assertTrue(re.search(
|
||||
'var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
||||
r = self.render_tag('chartbeat', 'chartbeat_bottom', {'chartbeat_domain': "test.com"})
|
||||
self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r)
|
||||
self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
||||
|
||||
def test_top_node(self):
|
||||
r = ChartbeatTopNode().render(
|
||||
Context({'chartbeat_domain': "test.com"}))
|
||||
r = ChartbeatTopNode().render(Context({
|
||||
'chartbeat_domain': "test.com",
|
||||
}))
|
||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
||||
|
||||
def test_bottom_node(self):
|
||||
r = ChartbeatBottomNode().render(
|
||||
Context({'chartbeat_domain': "test.com"}))
|
||||
self.assertTrue(re.search(
|
||||
'var _sf_async_config={.*"uid": "12345".*};', r), r)
|
||||
self.assertTrue(re.search(
|
||||
'var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
||||
r = ChartbeatBottomNode().render(Context({
|
||||
'chartbeat_domain': "test.com",
|
||||
}))
|
||||
self.assertTrue(re.search('var _sf_async_config={.*"uid": "12345".*};', r), r)
|
||||
self.assertTrue(re.search('var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
||||
|
||||
@override_settings(CHARTBEAT_USER_ID=None)
|
||||
def test_no_user_id(self):
|
||||
|
|
|
|||
|
|
@ -23,14 +23,12 @@ class ClickyTagTestCase(TagTestCase):
|
|||
def test_tag(self):
|
||||
r = self.render_tag('clicky', 'clicky')
|
||||
self.assertTrue('clicky_site_ids.push(12345678);' in r, r)
|
||||
self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r,
|
||||
r)
|
||||
self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = ClickyNode().render(Context({}))
|
||||
self.assertTrue('clicky_site_ids.push(12345678);' in r, r)
|
||||
self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r,
|
||||
r)
|
||||
self.assertTrue('src="//in.getclicky.com/12345678ns.gif"' in r, r)
|
||||
|
||||
@override_settings(CLICKY_SITE_ID=None)
|
||||
def test_no_site_id(self):
|
||||
|
|
@ -43,9 +41,7 @@ class ClickyTagTestCase(TagTestCase):
|
|||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
r = ClickyNode().render(Context({'user': User(username='test')}))
|
||||
self.assertTrue(
|
||||
'var clicky_custom = {"session": {"username": "test"}};' in r,
|
||||
r)
|
||||
self.assertTrue('var clicky_custom = {"session": {"username": "test"}};' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
|
|
@ -53,10 +49,13 @@ class ClickyTagTestCase(TagTestCase):
|
|||
self.assertFalse('var clicky_custom = {"session": {"username":' in r, r)
|
||||
|
||||
def test_custom(self):
|
||||
r = ClickyNode().render(Context({'clicky_var1': 'val1',
|
||||
'clicky_var2': 'val2'}))
|
||||
self.assertTrue(re.search('var clicky_custom = {.*'
|
||||
'"var1": "val1", "var2": "val2".*};', r), r)
|
||||
r = ClickyNode().render(Context({
|
||||
'clicky_var1': 'val1',
|
||||
'clicky_var2': 'val2',
|
||||
}))
|
||||
self.assertTrue(
|
||||
re.search(r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r),
|
||||
r)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
|
|||
114
analytical/tests/test_tag_facebook_pixel.py
Normal file
114
analytical/tests/test_tag_facebook_pixel.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"""
|
||||
Tests for the Facebook Pixel template tags.
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context, Template, TemplateSyntaxError
|
||||
from django.test import override_settings
|
||||
|
||||
from analytical.templatetags.analytical import _load_template_nodes
|
||||
from analytical.templatetags.facebook_pixel import FacebookPixelHeadNode, FacebookPixelBodyNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
expected_head_html = """\
|
||||
<script>
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '1234567890');
|
||||
fbq('track', 'PageView');
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
expected_body_html = """\
|
||||
<noscript><img height="1" width="1" style="display:none"
|
||||
src="https://www.facebook.com/tr?id=1234567890&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
"""
|
||||
|
||||
|
||||
@override_settings(FACEBOOK_PIXEL_ID='1234567890')
|
||||
class FacebookPixelTagTestCase(TagTestCase):
|
||||
|
||||
maxDiff = None
|
||||
|
||||
def test_head_tag(self):
|
||||
html = self.render_tag('facebook_pixel', 'facebook_pixel_head')
|
||||
self.assertEqual(expected_head_html, html)
|
||||
|
||||
def test_head_node(self):
|
||||
html = FacebookPixelHeadNode().render(Context({}))
|
||||
self.assertEqual(expected_head_html, html)
|
||||
|
||||
def test_body_tag(self):
|
||||
html = self.render_tag('facebook_pixel', 'facebook_pixel_body')
|
||||
self.assertEqual(expected_body_html, html)
|
||||
|
||||
def test_body_node(self):
|
||||
html = FacebookPixelBodyNode().render(Context({}))
|
||||
self.assertEqual(expected_body_html, html)
|
||||
|
||||
def test_tags_take_no_args(self):
|
||||
self.assertRaisesRegexp(
|
||||
TemplateSyntaxError,
|
||||
r"^'facebook_pixel_head' takes no arguments$",
|
||||
lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_head "arg" %}')
|
||||
.render(Context({}))),
|
||||
)
|
||||
self.assertRaisesRegexp(
|
||||
TemplateSyntaxError,
|
||||
r"^'facebook_pixel_body' takes no arguments$",
|
||||
lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_body "arg" %}')
|
||||
.render(Context({}))),
|
||||
)
|
||||
|
||||
@override_settings(FACEBOOK_PIXEL_ID=None)
|
||||
def test_no_id(self):
|
||||
expected_pattern = r'^FACEBOOK_PIXEL_ID setting is not set$'
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelHeadNode)
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelBodyNode)
|
||||
|
||||
@override_settings(FACEBOOK_PIXEL_ID='invalid')
|
||||
def test_invalid_id(self):
|
||||
expected_pattern = (
|
||||
r"^FACEBOOK_PIXEL_ID setting: must be \(a string containing\) a number: 'invalid'$")
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelHeadNode)
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelBodyNode)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
request = HttpRequest()
|
||||
request.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': request})
|
||||
|
||||
def _disabled(html):
|
||||
return '\n'.join([
|
||||
'<!-- Facebook Pixel disabled on internal IP address',
|
||||
html,
|
||||
'-->',
|
||||
])
|
||||
|
||||
head_html = FacebookPixelHeadNode().render(context)
|
||||
self.assertEqual(_disabled(expected_head_html), head_html)
|
||||
|
||||
body_html = FacebookPixelBodyNode().render(context)
|
||||
self.assertEqual(_disabled(expected_body_html), body_html)
|
||||
|
||||
def test_contribute_to_analytical(self):
|
||||
"""
|
||||
`facebook_pixel.contribute_to_analytical` registers the head and body nodes.
|
||||
"""
|
||||
template_nodes = _load_template_nodes()
|
||||
self.assertEqual({
|
||||
'head_top': [],
|
||||
'head_bottom': [FacebookPixelHeadNode],
|
||||
'body_top': [],
|
||||
'body_bottom': [FacebookPixelBodyNode],
|
||||
}, template_nodes)
|
||||
|
|
@ -32,8 +32,7 @@ class GaugesTagTestCase(TagTestCase):
|
|||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
""",
|
||||
self.render_tag('gauges', 'gauges'))
|
||||
""", self.render_tag('gauges', 'gauges'))
|
||||
|
||||
def test_node(self):
|
||||
self.assertEqual(
|
||||
|
|
@ -51,8 +50,7 @@ class GaugesTagTestCase(TagTestCase):
|
|||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
""",
|
||||
GaugesNode().render(Context()))
|
||||
""", GaugesNode().render(Context()))
|
||||
|
||||
@override_settings(GAUGES_SITE_ID=None)
|
||||
def test_no_account_number(self):
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from analytical.utils import AnalyticalException
|
|||
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7',
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN)
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN)
|
||||
class GoogleAnalyticsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``google_analytics`` template tag.
|
||||
|
|
@ -38,16 +38,15 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
|
|||
def test_wrong_property_id(self):
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode)
|
||||
|
||||
@override_settings(
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
@override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
def test_track_multiple_subdomains(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
def test_track_multiple_domains(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push(['_setDomainName', 'example.com']);" in r, r)
|
||||
|
|
@ -62,14 +61,10 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
|
|||
'google_analytics_var5': ('test5', 'qux', SCOPE_PAGE),
|
||||
})
|
||||
r = GoogleAnalyticsNode().render(context)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);"
|
||||
in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);"
|
||||
in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 4, 'test4', 'baz', 2]);"
|
||||
in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 5, 'test5', 'qux', 3]);"
|
||||
in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 4, 'test4', 'baz', 2]);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setCustomVar', 5, 'test5', 'qux', 3]);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED=True)
|
||||
def test_track_page_load_time(self):
|
||||
|
|
@ -97,96 +92,90 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
|
|||
@override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True)
|
||||
def test_anonymize_ip(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_gat._anonymizeIp']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_gat._anonymizeIp']);" in r, r)
|
||||
self.assertTrue(r.index('_gat._anonymizeIp') < r.index('_trackPageview'), r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=False)
|
||||
def test_anonymize_ip_not_present(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertFalse("_gaq.push (['_gat._anonymizeIp']);" in r, r)
|
||||
self.assertFalse("_gaq.push(['_gat._anonymizeIp']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=0.0)
|
||||
def test_set_sample_rate_min(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setSampleRate', '0.00']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setSampleRate', '0.00']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE='100.00')
|
||||
def test_set_sample_rate_max(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setSampleRate', '100.00']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setSampleRate', '100.00']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=-1)
|
||||
def test_exception_whenset_sample_rate_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=101)
|
||||
def test_exception_when_set_sample_rate_too_large(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0)
|
||||
def test_set_site_speed_sample_rate_min(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setSiteSpeedSampleRate', '0.00']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setSiteSpeedSampleRate', '0.00']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00')
|
||||
def test_set_site_speed_sample_rate_max(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setSiteSpeedSampleRate', '100.00']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setSiteSpeedSampleRate', '100.00']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1)
|
||||
def test_exception_whenset_site_speed_sample_rate_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=101)
|
||||
def test_exception_when_set_site_speed_sample_rate_too_large(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT=0)
|
||||
def test_set_session_cookie_timeout_min(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setSessionCookieTimeout', '0']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setSessionCookieTimeout', '0']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT='10000')
|
||||
def test_set_session_cookie_timeout_as_string(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setSessionCookieTimeout', '10000']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setSessionCookieTimeout', '10000']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT=-1)
|
||||
def test_exception_when_set_session_cookie_timeout_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT=0)
|
||||
def test_set_visitor_cookie_timeout_min(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setVisitorCookieTimeout', '0']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setVisitorCookieTimeout', '0']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT='10000')
|
||||
def test_set_visitor_cookie_timeout_as_string(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push (['_setVisitorCookieTimeout', '10000']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setVisitorCookieTimeout', '10000']);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT=-1)
|
||||
def test_exception_when_set_visitor_cookie_timeout_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7',
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN=None,
|
||||
ANALYTICAL_DOMAIN=None)
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN=None,
|
||||
ANALYTICAL_DOMAIN=None)
|
||||
class NoDomainTestCase(TestCase):
|
||||
def test_exception_without_domain(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render,
|
||||
context)
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode().render, context)
|
||||
|
|
|
|||
169
analytical/tests/test_tag_google_analytics_js.py
Normal file
169
analytical/tests/test_tag_google_analytics_js.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
"""
|
||||
Tests for the Google Analytics template tags and filters, using the new analytics.js library.
|
||||
"""
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from analytical.templatetags.google_analytics_js import GoogleAnalyticsJsNode, \
|
||||
TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS
|
||||
from analytical.tests.utils import TestCase, TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7',
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN)
|
||||
class GoogleAnalyticsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``google_analytics_js`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('google_analytics_js', 'google_analytics_js')
|
||||
self.assertTrue("""(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r, r)
|
||||
self.assertTrue("ga('create', 'UA-123456-7', 'auto', {});" in r, r)
|
||||
self.assertTrue("ga('send', 'pageview');" in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("""(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');""" in r, r)
|
||||
self.assertTrue("ga('create', 'UA-123456-7', 'auto', {});" in r, r)
|
||||
self.assertTrue("ga('send', 'pageview');" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID=None)
|
||||
def test_no_property_id(self):
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='wrong')
|
||||
def test_wrong_property_id(self):
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_SUBDOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
def test_track_multiple_subdomains(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue(
|
||||
"""ga('create', 'UA-123456-7', 'auto', {"legacyCookieDomain": "example.com"}""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
def test_track_multiple_domains(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("ga('create', 'UA-123456-7', 'auto', {" in r, r)
|
||||
self.assertTrue('"legacyCookieDomain": "example.com"' in r, r)
|
||||
self.assertTrue('"allowLinker\": true' in r, r)
|
||||
|
||||
def test_custom_vars(self):
|
||||
context = Context({
|
||||
'google_analytics_var1': ('test1', 'foo'),
|
||||
'google_analytics_var2': ('test2', 'bar'),
|
||||
'google_analytics_var4': ('test4', 1),
|
||||
'google_analytics_var5': ('test5', 2.2),
|
||||
})
|
||||
r = GoogleAnalyticsJsNode().render(context)
|
||||
self.assertTrue("ga('set', 'test1', 'foo');" in r, r)
|
||||
self.assertTrue("ga('set', 'test2', 'bar');" in r, r)
|
||||
self.assertTrue("ga('set', 'test4', 1);" in r, r)
|
||||
self.assertTrue("ga('set', 'test5', 2.2);" in r, r)
|
||||
|
||||
def test_display_advertising(self):
|
||||
with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {});
|
||||
ga('require', 'displayfeatures');
|
||||
ga('send', 'pageview');""" in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = GoogleAnalyticsJsNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Google Analytics disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=True)
|
||||
def test_anonymize_ip(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("ga('set', 'anonymizeIp', true);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_ANONYMIZE_IP=False)
|
||||
def test_anonymize_ip_not_present(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertFalse("ga('set', 'anonymizeIp', true);" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=0.0)
|
||||
def test_set_sample_rate_min(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"sampleRate": 0});""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE='100.00')
|
||||
def test_set_sample_rate_max(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"sampleRate": 100});""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=-1)
|
||||
def test_exception_whenset_sample_rate_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SAMPLE_RATE=101)
|
||||
def test_exception_when_set_sample_rate_too_large(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=0.0)
|
||||
def test_set_site_speed_sample_rate_min(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue(
|
||||
"""ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 0});""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE='100.00')
|
||||
def test_set_site_speed_sample_rate_max(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue(
|
||||
"""ga('create', 'UA-123456-7', 'auto', {"siteSpeedSampleRate": 100});""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=-1)
|
||||
def test_exception_whenset_site_speed_sample_rate_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE=101)
|
||||
def test_exception_when_set_site_speed_sample_rate_too_large(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION=0)
|
||||
def test_set_cookie_expiration_min(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue("""ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 0});""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION='10000')
|
||||
def test_set_cookie_expiration_as_string(self):
|
||||
r = GoogleAnalyticsJsNode().render(Context())
|
||||
self.assertTrue(
|
||||
"""ga('create', 'UA-123456-7', 'auto', {"cookieExpires": 10000});""" in r, r)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_COOKIE_EXPIRATION=-1)
|
||||
def test_exception_when_set_cookie_expiration_too_small(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context)
|
||||
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_JS_PROPERTY_ID='UA-123456-7',
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN=None,
|
||||
ANALYTICAL_DOMAIN=None)
|
||||
class NoDomainTestCase(TestCase):
|
||||
def test_exception_without_domain(self):
|
||||
context = Context()
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsJsNode().render, context)
|
||||
|
|
@ -36,8 +36,9 @@ class GoSquaredTagTestCase(TagTestCase):
|
|||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_auto_identify(self):
|
||||
r = GoSquaredNode().render(Context({'user': User(username='test',
|
||||
first_name='Test', last_name='User')}))
|
||||
r = GoSquaredNode().render(Context({
|
||||
'user': User(username='test', first_name='Test', last_name='User'),
|
||||
}))
|
||||
self.assertTrue('GoSquared.UserName = "Test User";' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
|
|
|
|||
84
analytical/tests/test_tag_hotjar.py
Normal file
84
analytical/tests/test_tag_hotjar.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
"""
|
||||
Tests for the Hotjar template tags.
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context, Template, TemplateSyntaxError
|
||||
from django.test import override_settings
|
||||
|
||||
from analytical.templatetags.analytical import _load_template_nodes
|
||||
from analytical.templatetags.hotjar import HotjarNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
expected_html = """\
|
||||
<script>
|
||||
(function(h,o,t,j,a,r){
|
||||
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
||||
h._hjSettings={hjid:123456789,hjsv:6};
|
||||
a=o.getElementsByTagName('head')[0];
|
||||
r=o.createElement('script');r.async=1;
|
||||
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
@override_settings(HOTJAR_SITE_ID='123456789')
|
||||
class HotjarTagTestCase(TagTestCase):
|
||||
|
||||
maxDiff = None
|
||||
|
||||
def test_tag(self):
|
||||
html = self.render_tag('hotjar', 'hotjar')
|
||||
self.assertEqual(expected_html, html)
|
||||
|
||||
def test_node(self):
|
||||
html = HotjarNode().render(Context({}))
|
||||
self.assertEqual(expected_html, html)
|
||||
|
||||
def test_tags_take_no_args(self):
|
||||
self.assertRaisesRegexp(
|
||||
TemplateSyntaxError,
|
||||
r"^'hotjar' takes no arguments$",
|
||||
lambda: (Template('{% load hotjar %}{% hotjar "arg" %}')
|
||||
.render(Context({}))),
|
||||
)
|
||||
|
||||
@override_settings(HOTJAR_SITE_ID=None)
|
||||
def test_no_id(self):
|
||||
expected_pattern = r'^HOTJAR_SITE_ID setting is not set$'
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, HotjarNode)
|
||||
|
||||
@override_settings(HOTJAR_SITE_ID='invalid')
|
||||
def test_invalid_id(self):
|
||||
expected_pattern = (
|
||||
r"^HOTJAR_SITE_ID setting: must be \(a string containing\) a number: 'invalid'$")
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, HotjarNode)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
request = HttpRequest()
|
||||
request.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': request})
|
||||
|
||||
actual_html = HotjarNode().render(context)
|
||||
disabled_html = '\n'.join([
|
||||
'<!-- Hotjar disabled on internal IP address',
|
||||
expected_html,
|
||||
'-->',
|
||||
])
|
||||
self.assertEqual(disabled_html, actual_html)
|
||||
|
||||
def test_contribute_to_analytical(self):
|
||||
"""
|
||||
`hotjar.contribute_to_analytical` registers the head and body nodes.
|
||||
"""
|
||||
template_nodes = _load_template_nodes()
|
||||
self.assertEqual({
|
||||
'head_top': [],
|
||||
'head_bottom': [HotjarNode],
|
||||
'body_top': [],
|
||||
'body_bottom': [],
|
||||
}, template_nodes)
|
||||
|
|
@ -19,13 +19,13 @@ class HubSpotTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('hubspot', 'hubspot')
|
||||
self.assertTrue("n.id=i;n.src='//js.hs-analytics.net/analytics/'+(Math.ceil(new Date()/r)*r)+'/1234.js';"
|
||||
in r, r)
|
||||
self.assertTrue("n.id=i;n.src='//js.hs-analytics.net/analytics/'"
|
||||
"+(Math.ceil(new Date()/r)*r)+'/1234.js';" in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = HubSpotNode().render(Context())
|
||||
self.assertTrue("n.id=i;n.src='//js.hs-analytics.net/analytics/'+(Math.ceil(new Date()/r)*r)+'/1234.js';"
|
||||
in r, r)
|
||||
self.assertTrue("n.id=i;n.src='//js.hs-analytics.net/analytics/'"
|
||||
"+(Math.ceil(new Date()/r)*r)+'/1234.js';" in r, r)
|
||||
|
||||
@override_settings(HUBSPOT_PORTAL_ID=None)
|
||||
def test_no_portal_id(self):
|
||||
|
|
|
|||
|
|
@ -4,16 +4,17 @@ Tests for the intercom template tags and filters.
|
|||
|
||||
import datetime
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from analytical.templatetags.intercom import IntercomNode
|
||||
from analytical.templatetags.intercom import IntercomNode, intercom_user_hash, _timestamp
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(INTERCOM_APP_ID='1234567890abcdef0123456789')
|
||||
@override_settings(INTERCOM_APP_ID="abc123xyz")
|
||||
class IntercomTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``intercom`` template tag.
|
||||
|
|
@ -21,25 +22,25 @@ class IntercomTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
rendered_tag = self.render_tag('intercom', 'intercom')
|
||||
self.assertTrue(rendered_tag.startswith('<!-- Intercom disabled on internal IP address'))
|
||||
self.assertTrue(rendered_tag.strip().startswith('<script id="IntercomSettingsScriptTag">'))
|
||||
|
||||
def test_node(self):
|
||||
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
|
||||
rendered_tag = IntercomNode().render(Context({
|
||||
'user': User(
|
||||
username='test',
|
||||
first_name='Firstname',
|
||||
last_name='Lastname',
|
||||
email="test@example.com",
|
||||
date_joined=now)
|
||||
}))
|
||||
user = User.objects.create(
|
||||
username='test',
|
||||
first_name='Firstname',
|
||||
last_name='Lastname',
|
||||
email="test@example.com",
|
||||
date_joined=now,
|
||||
)
|
||||
rendered_tag = IntercomNode().render(Context({'user': user}))
|
||||
# Because the json isn't predictably ordered, we can't just test the whole thing verbatim.
|
||||
self.assertEqual("""
|
||||
<script id="IntercomSettingsScriptTag">
|
||||
window.intercomSettings = {"app_id": "1234567890abcdef0123456789", "created_at": 1397074500, "email": "test@example.com", "name": "Firstname Lastname"};
|
||||
window.intercomSettings = {"app_id": "abc123xyz", "created_at": 1397074500, "email": "test@example.com", "name": "Firstname Lastname", "user_id": %(user_id)s};
|
||||
</script>
|
||||
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
|
||||
""", rendered_tag)
|
||||
""" % {'user_id': user.pk}, rendered_tag) # noqa
|
||||
|
||||
@override_settings(INTERCOM_APP_ID=None)
|
||||
def test_no_account_number(self):
|
||||
|
|
@ -51,13 +52,21 @@ class IntercomTagTestCase(TagTestCase):
|
|||
|
||||
def test_identify_name_email_and_created_at(self):
|
||||
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
|
||||
r = IntercomNode().render(Context({'user': User(username='test',
|
||||
first_name='Firstname', last_name='Lastname',
|
||||
email="test@example.com", date_joined=now)}))
|
||||
self.assertTrue(
|
||||
"""window.intercomSettings = {"app_id": "1234567890abcdef0123456789", "created_at": 1397074500, "email": "test@example.com", "name": "Firstname Lastname"};"""\
|
||||
in r
|
||||
user = User.objects.create(
|
||||
username='test',
|
||||
first_name='Firstname',
|
||||
last_name='Lastname',
|
||||
email="test@example.com",
|
||||
date_joined=now,
|
||||
)
|
||||
r = IntercomNode().render(Context({
|
||||
'user': user,
|
||||
}))
|
||||
self.assertTrue('window.intercomSettings = {'
|
||||
'"app_id": "abc123xyz", "created_at": 1397074500, '
|
||||
'"email": "test@example.com", "name": "Firstname Lastname", '
|
||||
'"user_id": %(user_id)s'
|
||||
'};' % {'user_id': user.pk} in r, msg=r)
|
||||
|
||||
def test_custom(self):
|
||||
r = IntercomNode().render(Context({
|
||||
|
|
@ -68,10 +77,11 @@ class IntercomTagTestCase(TagTestCase):
|
|||
|
||||
def test_identify_name_and_email(self):
|
||||
r = IntercomNode().render(Context({
|
||||
'user': User(username='test',
|
||||
first_name='Firstname',
|
||||
last_name='Lastname',
|
||||
email="test@example.com")
|
||||
'user': User(
|
||||
username='test',
|
||||
first_name='Firstname',
|
||||
last_name='Lastname',
|
||||
email="test@example.com"),
|
||||
}))
|
||||
self.assertTrue('"email": "test@example.com", "name": "Firstname Lastname"' in r)
|
||||
|
||||
|
|
@ -80,15 +90,83 @@ class IntercomTagTestCase(TagTestCase):
|
|||
self.assertTrue('"name": "test"' in r, r)
|
||||
|
||||
def test_no_identify_when_explicit_name(self):
|
||||
r = IntercomNode().render(Context({'intercom_name': 'explicit',
|
||||
'user': User(username='implicit')}))
|
||||
r = IntercomNode().render(Context({
|
||||
'intercom_name': 'explicit',
|
||||
'user': User(username='implicit'),
|
||||
}))
|
||||
self.assertTrue('"name": "explicit"' in r, r)
|
||||
|
||||
def test_no_identify_when_explicit_email(self):
|
||||
r = IntercomNode().render(Context({'intercom_email': 'explicit',
|
||||
'user': User(username='implicit')}))
|
||||
r = IntercomNode().render(Context({
|
||||
'intercom_email': 'explicit',
|
||||
'user': User(username='implicit'),
|
||||
}))
|
||||
self.assertTrue('"email": "explicit"' in r, r)
|
||||
|
||||
def test_disable_for_anonymous_users(self):
|
||||
r = IntercomNode().render(Context({'user': AnonymousUser()}))
|
||||
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
|
||||
def test_user_hash__without_user_details(self):
|
||||
"""
|
||||
No `user_hash` without `user_id` or `email`.
|
||||
"""
|
||||
attrs = IntercomNode()._get_custom_attrs(Context())
|
||||
self.assertEqual({
|
||||
'created_at': None,
|
||||
}, attrs)
|
||||
|
||||
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
|
||||
def test_user_hash__with_user(self):
|
||||
"""
|
||||
'user_hash' of default `user_id`.
|
||||
"""
|
||||
user = User.objects.create(
|
||||
email='test@example.com',
|
||||
) # type: User
|
||||
attrs = IntercomNode()._get_custom_attrs(Context({'user': user}))
|
||||
self.assertEqual({
|
||||
'created_at': int(_timestamp(user.date_joined)),
|
||||
'email': 'test@example.com',
|
||||
'name': '',
|
||||
'user_hash': intercom_user_hash(str(user.pk)),
|
||||
'user_id': user.pk,
|
||||
}, attrs)
|
||||
|
||||
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
|
||||
def test_user_hash__with_explicit_user_id(self):
|
||||
"""
|
||||
'user_hash' of context-provided `user_id`.
|
||||
"""
|
||||
attrs = IntercomNode()._get_custom_attrs(Context({
|
||||
'intercom_email': 'test@example.com',
|
||||
'intercom_user_id': '5',
|
||||
}))
|
||||
self.assertEqual({
|
||||
'created_at': None,
|
||||
'email': 'test@example.com',
|
||||
# HMAC for user_id:
|
||||
'user_hash': 'd3123a7052b42272d9b520235008c248a5aff3221cc0c530b754702ad91ab102',
|
||||
'user_id': '5',
|
||||
}, attrs)
|
||||
|
||||
@override_settings(INTERCOM_HMAC_SECRET_KEY='secret')
|
||||
def test_user_hash__with_explicit_email(self):
|
||||
"""
|
||||
'user_hash' of context-provided `email`.
|
||||
"""
|
||||
attrs = IntercomNode()._get_custom_attrs(Context({
|
||||
'intercom_email': 'test@example.com',
|
||||
}))
|
||||
self.assertEqual({
|
||||
'created_at': None,
|
||||
'email': 'test@example.com',
|
||||
# HMAC for email:
|
||||
'user_hash': '49e43229ee99dca2565241719b8341b04e71dd4de0628f991b5bea30a526e153',
|
||||
}, attrs)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = IntercomNode().render(context)
|
||||
self.assertTrue(r.startswith('<!-- Intercom disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ from analytical.tests.utils import TagTestCase
|
|||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER='12345',
|
||||
KISS_INSIGHTS_SITE_CODE='abc')
|
||||
@override_settings(KISS_INSIGHTS_ACCOUNT_NUMBER='12345', KISS_INSIGHTS_SITE_CODE='abc')
|
||||
class KissInsightsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``kiss_insights`` template tag.
|
||||
|
|
@ -53,6 +52,5 @@ class KissInsightsTagTestCase(TagTestCase):
|
|||
self.assertFalse("_kiq.push(['identify', " in r, r)
|
||||
|
||||
def test_show_survey(self):
|
||||
r = KissInsightsNode().render(
|
||||
Context({'kiss_insights_show_survey': 1234}))
|
||||
r = KissInsightsNode().render(Context({'kiss_insights_show_survey': 1234}))
|
||||
self.assertTrue("_kiq.push(['showSurvey', 1234]);" in r, r)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ from analytical.tests.utils import TagTestCase
|
|||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef'
|
||||
'01234567')
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef01234567')
|
||||
class KissMetricsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``kiss_metrics`` template tag.
|
||||
|
|
@ -21,25 +20,23 @@ class KissMetricsTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('kiss_metrics', 'kiss_metrics')
|
||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/0123456789abcdef012345"
|
||||
"6789abcdef01234567.1.js" in r, r)
|
||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/"
|
||||
"0123456789abcdef0123456789abcdef01234567.1.js" in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = KissMetricsNode().render(Context())
|
||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/0123456789abcdef012345"
|
||||
"6789abcdef01234567.1.js" in r, r)
|
||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/"
|
||||
"0123456789abcdef0123456789abcdef01234567.1.js" in r, r)
|
||||
|
||||
@override_settings(KISS_METRICS_API_KEY=None)
|
||||
def test_no_api_key(self):
|
||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
||||
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef'
|
||||
'0123456')
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef0123456')
|
||||
def test_api_key_too_short(self):
|
||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
||||
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef'
|
||||
'012345678')
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef012345678')
|
||||
def test_api_key_too_long(self):
|
||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
||||
|
||||
|
|
@ -54,20 +51,23 @@ class KissMetricsTagTestCase(TagTestCase):
|
|||
self.assertFalse("_kmq.push(['identify', " in r, r)
|
||||
|
||||
def test_event(self):
|
||||
r = KissMetricsNode().render(Context({'kiss_metrics_event':
|
||||
('test_event', {'prop1': 'val1', 'prop2': 'val2'})}))
|
||||
r = KissMetricsNode().render(Context({
|
||||
'kiss_metrics_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}),
|
||||
}))
|
||||
self.assertTrue("_kmq.push(['record', 'test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
|
||||
def test_property(self):
|
||||
r = KissMetricsNode().render(Context({'kiss_metrics_properties':
|
||||
{'prop1': 'val1', 'prop2': 'val2'}}))
|
||||
r = KissMetricsNode().render(Context({
|
||||
'kiss_metrics_properties': {'prop1': 'val1', 'prop2': 'val2'},
|
||||
}))
|
||||
self.assertTrue("_kmq.push([\'set\', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
|
||||
def test_alias(self):
|
||||
r = KissMetricsNode().render(Context({'kiss_metrics_alias':
|
||||
{'test': 'test_alias'}}))
|
||||
r = KissMetricsNode().render(Context({
|
||||
'kiss_metrics_alias': {'test': 'test_alias'},
|
||||
}))
|
||||
self.assertTrue("_kmq.push(['alias', 'test', 'test_alias']);" in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
|
|
|
|||
|
|
@ -20,15 +20,11 @@ class MixpanelTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('mixpanel', 'mixpanel')
|
||||
self.assertIn(
|
||||
"mixpanel.init('0123456789abcdef0123456789abcdef');", r,
|
||||
)
|
||||
self.assertIn("mixpanel.init('0123456789abcdef0123456789abcdef');", r)
|
||||
|
||||
def test_node(self):
|
||||
r = MixpanelNode().render(Context())
|
||||
self.assertIn(
|
||||
"mixpanel.init('0123456789abcdef0123456789abcdef');", r,
|
||||
)
|
||||
self.assertIn("mixpanel.init('0123456789abcdef0123456789abcdef');", r)
|
||||
|
||||
@override_settings(MIXPANEL_API_TOKEN=None)
|
||||
def test_no_token(self):
|
||||
|
|
@ -53,8 +49,9 @@ class MixpanelTagTestCase(TagTestCase):
|
|||
self.assertFalse("mixpanel.register_once({distinct_id:" in r, r)
|
||||
|
||||
def test_event(self):
|
||||
r = MixpanelNode().render(Context({'mixpanel_event':
|
||||
('test_event', {'prop1': 'val1', 'prop2': 'val2'})}))
|
||||
r = MixpanelNode().render(Context({
|
||||
'mixpanel_event': ('test_event', {'prop1': 'val1', 'prop2': 'val2'}),
|
||||
}))
|
||||
self.assertTrue("mixpanel.track('test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"});' in r, r)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,10 +35,11 @@ class OlarkTestCase(TagTestCase):
|
|||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
r = OlarkNode().render(Context({'user':
|
||||
User(username='test', first_name='Test', last_name='User')}))
|
||||
r = OlarkNode().render(Context({
|
||||
'user': User(username='test', first_name='Test', last_name='User'),
|
||||
}))
|
||||
self.assertTrue("olark('api.chat.updateVisitorNickname', "
|
||||
"{snippet: 'Test User (test)'});" in r, r)
|
||||
"{snippet: 'Test User (test)'});" in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
|
|
@ -48,18 +49,19 @@ class OlarkTestCase(TagTestCase):
|
|||
def test_nickname(self):
|
||||
r = OlarkNode().render(Context({'olark_nickname': 'testnick'}))
|
||||
self.assertTrue("olark('api.chat.updateVisitorNickname', "
|
||||
"{snippet: 'testnick'});" in r, r)
|
||||
"{snippet: 'testnick'});" in r, r)
|
||||
|
||||
def test_status_string(self):
|
||||
r = OlarkNode().render(Context({'olark_status': 'teststatus'}))
|
||||
self.assertTrue("olark('api.chat.updateVisitorStatus', "
|
||||
'{snippet: "teststatus"});' in r, r)
|
||||
'{snippet: "teststatus"});' in r, r)
|
||||
|
||||
def test_status_string_list(self):
|
||||
r = OlarkNode().render(Context({'olark_status':
|
||||
['teststatus1', 'teststatus2']}))
|
||||
r = OlarkNode().render(Context({
|
||||
'olark_status': ['teststatus1', 'teststatus2'],
|
||||
}))
|
||||
self.assertTrue("olark('api.chat.updateVisitorStatus', "
|
||||
'{snippet: ["teststatus1", "teststatus2"]});' in r, r)
|
||||
'{snippet: ["teststatus1", "teststatus2"]});' in r, r)
|
||||
|
||||
def test_messages(self):
|
||||
messages = [
|
||||
|
|
@ -88,5 +90,4 @@ class OlarkTestCase(TagTestCase):
|
|||
vars = dict(('olark_%s' % m, m) for m in messages)
|
||||
r = OlarkNode().render(Context(vars))
|
||||
for m in messages:
|
||||
self.assertTrue("olark.configure('locale.%s', \"%s\");" % (m, m)
|
||||
in r, r)
|
||||
self.assertTrue("olark.configure('locale.%s', \"%s\");" % (m, m) in r, r)
|
||||
|
|
|
|||
|
|
@ -20,38 +20,35 @@ class PiwikTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('piwik', 'piwik')
|
||||
self.assertTrue(' ? "https" : "http") + "://example.com/";' in r, r)
|
||||
self.assertTrue('"//example.com/"' in r, r)
|
||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
||||
self.assertTrue('img src="http://example.com/piwik.php?idsite=345"'
|
||||
self.assertTrue('img src="//example.com/piwik.php?idsite=345"'
|
||||
in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = PiwikNode().render(Context({}))
|
||||
self.assertTrue(' ? "https" : "http") + "://example.com/";' in r, r)
|
||||
self.assertTrue('"//example.com/";' in r, r)
|
||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
||||
self.assertTrue('img src="http://example.com/piwik.php?idsite=345"'
|
||||
self.assertTrue('img src="//example.com/piwik.php?idsite=345"'
|
||||
in r, r)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com/piwik',
|
||||
PIWIK_SITE_ID='345')
|
||||
def test_domain_path_valid(self):
|
||||
r = self.render_tag('piwik', 'piwik')
|
||||
self.assertTrue(' ? "https" : "http") + "://example.com/piwik/";' in r,
|
||||
r)
|
||||
self.assertTrue('"//example.com/piwik/"' in r, r)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com:1234',
|
||||
PIWIK_SITE_ID='345')
|
||||
def test_domain_port_valid(self):
|
||||
r = self.render_tag('piwik', 'piwik')
|
||||
self.assertTrue(' ? "https" : "http") + "://example.com:1234/";' in r,
|
||||
r)
|
||||
self.assertTrue('"//example.com:1234/";' in r, r)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com:1234/piwik',
|
||||
PIWIK_SITE_ID='345')
|
||||
def test_domain_port_path_valid(self):
|
||||
r = self.render_tag('piwik', 'piwik')
|
||||
self.assertTrue(' ? "https" : "http") + "://example.com:1234/piwik/";' in r,
|
||||
r)
|
||||
self.assertTrue('"//example.com:1234/piwik/"' in r, r)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH=None)
|
||||
def test_no_domain(self):
|
||||
|
|
@ -148,3 +145,8 @@ class PiwikTagTestCase(TagTestCase):
|
|||
msg = 'Incorrect Piwik user tracking rendering.\nFound:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertNotIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
@override_settings(PIWIK_DISABLE_COOKIES=True)
|
||||
def test_disable_cookies(self):
|
||||
r = PiwikNode().render(Context({}))
|
||||
self.assertTrue("_paq.push(['disableCookies']);" in r, r)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
Tests for the Rating@Mail.ru template tags and filters.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
|
|
|
|||
|
|
@ -50,16 +50,19 @@ class SnapEngageTestCase(TagTestCase):
|
|||
self.assertRaises(AnalyticalException, SnapEngageNode)
|
||||
|
||||
def test_no_button(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_button': BUTTON_STYLE_NONE}))
|
||||
self.assertTrue('SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")'
|
||||
in r, r)
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button': BUTTON_STYLE_NONE,
|
||||
}))
|
||||
self.assertTrue('SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r, r)
|
||||
with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_NONE):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.init("ec329c69-0bf0-4db8-9b77-3f8150fb977e")' in r, r)
|
||||
|
||||
def test_live_button(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_button': BUTTON_STYLE_LIVE}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button': BUTTON_STYLE_LIVE,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%",true);' in r, r)
|
||||
|
|
@ -71,7 +74,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_custom_button(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button': "http://www.example.com/button.png"}))
|
||||
'snapengage_button': "http://www.example.com/button.png",
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%");' in r, r)
|
||||
|
|
@ -89,12 +93,12 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_button_location_right(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location': BUTTON_LOCATION_RIGHT}))
|
||||
'snapengage_button_location': BUTTON_LOCATION_RIGHT,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",'
|
||||
'"55%");' in r, r)
|
||||
with override_settings(
|
||||
SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_RIGHT):
|
||||
with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_RIGHT):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",'
|
||||
|
|
@ -102,7 +106,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_button_location_top(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location': BUTTON_LOCATION_TOP}))
|
||||
'snapengage_button_location': BUTTON_LOCATION_TOP,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",'
|
||||
'"55%");' in r, r)
|
||||
|
|
@ -114,7 +119,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_button_location_bottom(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location': BUTTON_LOCATION_BOTTOM}))
|
||||
'snapengage_button_location': BUTTON_LOCATION_BOTTOM,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",'
|
||||
'"55%");' in r, r)
|
||||
|
|
@ -127,7 +133,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_button_offset(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location_offset': "30%"}))
|
||||
'snapengage_button_location_offset': "30%",
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"30%");' in r, r)
|
||||
|
|
@ -139,7 +146,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_button_effect(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_effect': "-4px"}))
|
||||
'snapengage_button_effect': "-4px",
|
||||
}))
|
||||
self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r)
|
||||
with override_settings(SNAPENGAGE_BUTTON_EFFECT="-4px"):
|
||||
r = SnapEngageNode().render(Context())
|
||||
|
|
@ -147,7 +155,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_form_position(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_form_position': FORM_POSITION_TOP_LEFT}))
|
||||
'snapengage_form_position': FORM_POSITION_TOP_LEFT,
|
||||
}))
|
||||
self.assertTrue('SnapABug.setChatFormPosition("tl");' in r, r)
|
||||
with override_settings(SNAPENGAGE_FORM_POSITION=FORM_POSITION_TOP_LEFT):
|
||||
r = SnapEngageNode().render(Context())
|
||||
|
|
@ -155,7 +164,8 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
def test_form_top_position(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_form_top_position': 40}))
|
||||
'snapengage_form_top_position': 40,
|
||||
}))
|
||||
self.assertTrue('SnapABug.setFormTopPosition(40);' in r, r)
|
||||
with override_settings(SNAPENGAGE_FORM_TOP_POSITION=40):
|
||||
r = SnapEngageNode().render(Context())
|
||||
|
|
@ -178,7 +188,9 @@ class SnapEngageTestCase(TagTestCase):
|
|||
self.assertTrue('SnapABug.setSecureConnexion();' in r, r)
|
||||
|
||||
def test_show_offline(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_show_offline': False}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_show_offline': False,
|
||||
}))
|
||||
self.assertTrue('SnapABug.allowOffline(false);' in r, r)
|
||||
with override_settings(SNAPENGAGE_SHOW_OFFLINE=False):
|
||||
r = SnapEngageNode().render(Context())
|
||||
|
|
@ -190,15 +202,18 @@ class SnapEngageTestCase(TagTestCase):
|
|||
self.assertTrue('SnapABug.allowProactiveChat(false);' in r, r)
|
||||
|
||||
def test_screenshot(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_screenshots': False}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_screenshots': False,
|
||||
}))
|
||||
self.assertTrue('SnapABug.allowScreenshot(false);' in r, r)
|
||||
with override_settings(SNAPENGAGE_SCREENSHOTS=False):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.allowScreenshot(false);' in r, r)
|
||||
|
||||
def test_offline_screenshots(self):
|
||||
r = SnapEngageNode().render(Context(
|
||||
{'snapengage_offline_screenshots': False}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_offline_screenshots': False,
|
||||
}))
|
||||
self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r)
|
||||
with override_settings(SNAPENGAGE_OFFLINE_SCREENSHOTS=False):
|
||||
r = SnapEngageNode().render(Context())
|
||||
|
|
@ -213,30 +228,35 @@ class SnapEngageTestCase(TagTestCase):
|
|||
|
||||
@override_settings(SNAPENGAGE_READONLY_EMAIL=False)
|
||||
def test_email(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_email':
|
||||
'test@example.com'}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_email': 'test@example.com',
|
||||
}))
|
||||
self.assertTrue('SnapABug.setUserEmail("test@example.com");' in r, r)
|
||||
|
||||
def test_email_readonly(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_email':
|
||||
'test@example.com', 'snapengage_readonly_email': True}))
|
||||
self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r,
|
||||
r)
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_email': 'test@example.com',
|
||||
'snapengage_readonly_email': True,
|
||||
}))
|
||||
self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r, r)
|
||||
with override_settings(SNAPENGAGE_READONLY_EMAIL=True):
|
||||
r = SnapEngageNode().render(Context({'snapengage_email':
|
||||
'test@example.com'}))
|
||||
self.assertTrue('SnapABug.setUserEmail("test@example.com",true);'
|
||||
in r, r)
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_email': 'test@example.com',
|
||||
}))
|
||||
self.assertTrue('SnapABug.setUserEmail("test@example.com",true);' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
r = SnapEngageNode().render(Context({'user':
|
||||
User(username='test', email='test@example.com')}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'user': User(username='test', email='test@example.com'),
|
||||
}))
|
||||
self.assertTrue('SnapABug.setUserEmail("test@example.com");' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
r = SnapEngageNode().render(Context({'user': AnonymousUser()}))
|
||||
r = SnapEngageNode().render(Context({
|
||||
'user': AnonymousUser(),
|
||||
}))
|
||||
self.assertFalse('SnapABug.setUserEmail(' in r, r)
|
||||
|
||||
def test_language(self):
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
Tests for the Spring Metrics template tags and filters.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
|
|
@ -38,10 +36,10 @@ class SpringMetricsTagTestCase(TagTestCase):
|
|||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
r = SpringMetricsNode().render(Context({'user':
|
||||
User(email='test@test.com')}))
|
||||
self.assertTrue("_springMetq.push(['setdata', "
|
||||
"{'email': 'test@test.com'}]);" in r, r)
|
||||
r = SpringMetricsNode().render(Context({
|
||||
'user': User(email='test@test.com'),
|
||||
}))
|
||||
self.assertTrue("_springMetq.push(['setdata', {'email': 'test@test.com'}]);" in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
|
|
@ -49,12 +47,12 @@ class SpringMetricsTagTestCase(TagTestCase):
|
|||
self.assertFalse("_springMetq.push(['setdata', {'email':" in r, r)
|
||||
|
||||
def test_custom(self):
|
||||
r = SpringMetricsNode().render(Context({'spring_metrics_var1': 'val1',
|
||||
'spring_metrics_var2': 'val2'}))
|
||||
self.assertTrue("_springMetq.push(['setdata', {'var1': 'val1'}]);" in r,
|
||||
r)
|
||||
self.assertTrue("_springMetq.push(['setdata', {'var2': 'val2'}]);" in r,
|
||||
r)
|
||||
r = SpringMetricsNode().render(Context({
|
||||
'spring_metrics_var1': 'val1',
|
||||
'spring_metrics_var2': 'val2',
|
||||
}))
|
||||
self.assertTrue("_springMetq.push(['setdata', {'var1': 'val1'}]);" in r, r)
|
||||
self.assertTrue("_springMetq.push(['setdata', {'var2': 'val2'}]);" in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
|
|||
|
|
@ -40,14 +40,7 @@ class UserVoiceTagTestCase(TagTestCase):
|
|||
|
||||
@override_settings(USERVOICE_WIDGET_KEY='')
|
||||
def test_empty_key(self):
|
||||
r = UserVoiceNode().render(Context())
|
||||
self.assertEqual(r, "")
|
||||
|
||||
@override_settings(USERVOICE_WIDGET_KEY='')
|
||||
def test_overridden_empty_key(self):
|
||||
vars = {'uservoice_widget_key': 'bcdefghijklmnopqrstu'}
|
||||
r = UserVoiceNode().render(Context(vars))
|
||||
self.assertIn("widget.uservoice.com/bcdefghijklmnopqrstu.js", r)
|
||||
self.assertRaises(AnalyticalException, UserVoiceNode)
|
||||
|
||||
def test_overridden_key(self):
|
||||
vars = {'uservoice_widget_key': 'defghijklmnopqrstuvw'}
|
||||
|
|
@ -65,7 +58,7 @@ class UserVoiceTagTestCase(TagTestCase):
|
|||
r = UserVoiceNode().render(Context(data))
|
||||
self.assertIn("""UserVoice.push(['set', {"key1": "val2"}]);""", r)
|
||||
|
||||
def test_auto_trigger(self):
|
||||
def test_auto_trigger_default(self):
|
||||
r = UserVoiceNode().render(Context())
|
||||
self.assertTrue("UserVoice.push(['addTrigger', {}]);" in r, r)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,22 +37,26 @@ class WoopraTagTestCase(TagTestCase):
|
|||
@override_settings(WOOPRA_IDLE_TIMEOUT=1234)
|
||||
def test_idle_timeout(self):
|
||||
r = WoopraNode().render(Context({}))
|
||||
self.assertTrue('var woo_settings = {"domain": "example.com", '
|
||||
'"idle_timeout": "1234"};' in r, r)
|
||||
self.assertTrue('var woo_settings = '
|
||||
'{"domain": "example.com", "idle_timeout": "1234"};' in r, r)
|
||||
|
||||
def test_custom(self):
|
||||
r = WoopraNode().render(Context({'woopra_var1': 'val1',
|
||||
'woopra_var2': 'val2'}))
|
||||
self.assertTrue('var woo_visitor = {"var1": "val1", "var2": "val2"};'
|
||||
in r, r)
|
||||
r = WoopraNode().render(Context({
|
||||
'woopra_var1': 'val1',
|
||||
'woopra_var2': 'val2',
|
||||
}))
|
||||
self.assertTrue('var woo_visitor = {"var1": "val1", "var2": "val2"};' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_name_and_email(self):
|
||||
r = WoopraNode().render(Context({'user': User(username='test',
|
||||
first_name='Firstname', last_name='Lastname',
|
||||
email="test@example.com")}))
|
||||
self.assertTrue('var woo_visitor = {"email": "test@example.com", '
|
||||
'"name": "Firstname Lastname"};' in r, r)
|
||||
r = WoopraNode().render(Context({
|
||||
'user': User(username='test',
|
||||
first_name='Firstname',
|
||||
last_name='Lastname',
|
||||
email="test@example.com"),
|
||||
}))
|
||||
self.assertTrue('var woo_visitor = '
|
||||
'{"email": "test@example.com", "name": "Firstname Lastname"};' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_username_no_email(self):
|
||||
|
|
@ -61,14 +65,18 @@ class WoopraTagTestCase(TagTestCase):
|
|||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_no_identify_when_explicit_name(self):
|
||||
r = WoopraNode().render(Context({'woopra_name': 'explicit',
|
||||
'user': User(username='implicit')}))
|
||||
r = WoopraNode().render(Context({
|
||||
'woopra_name': 'explicit',
|
||||
'user': User(username='implicit'),
|
||||
}))
|
||||
self.assertTrue('var woo_visitor = {"name": "explicit"};' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_no_identify_when_explicit_email(self):
|
||||
r = WoopraNode().render(Context({'woopra_email': 'explicit',
|
||||
'user': User(username='implicit')}))
|
||||
r = WoopraNode().render(Context({
|
||||
'woopra_email': 'explicit',
|
||||
'user': User(username='implicit'),
|
||||
}))
|
||||
self.assertTrue('var woo_visitor = {"email": "explicit"};' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
Tests for the Yandex.Metrica template tags and filters.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ class SettingDeletedTestCase(TestCase):
|
|||
|
||||
# available in python >= 3.2
|
||||
if hasattr(self, 'assertRaisesRegex'):
|
||||
with self.assertRaisesRegex(AnalyticalException, "^USER_ID setting is set to None$"):
|
||||
user_id = get_required_setting("USER_ID", "\d+", "invalid USER_ID")
|
||||
with self.assertRaisesRegex(AnalyticalException, "^USER_ID setting is not set$"):
|
||||
get_required_setting("USER_ID", r"\d+", "invalid USER_ID")
|
||||
# available in python >= 2.7, deprecated in 3.2
|
||||
elif hasattr(self, 'assertRaisesRegexp'):
|
||||
with self.assertRaisesRegexp(AnalyticalException, "^USER_ID setting is set to None$"):
|
||||
user_id = get_required_setting("USER_ID", "\d+", "invalid USER_ID")
|
||||
with self.assertRaisesRegexp(AnalyticalException, "^USER_ID setting is not set$"):
|
||||
get_required_setting("USER_ID", r"\d+", "invalid USER_ID")
|
||||
else:
|
||||
self.assertRaises(AnalyticalException,
|
||||
get_required_setting, "USER_ID", "\d+", "invalid USER_ID")
|
||||
get_required_setting, "USER_ID", r"\d+", "invalid USER_ID")
|
||||
|
||||
|
||||
class MyUser(AbstractBaseUser):
|
||||
|
|
@ -72,10 +72,9 @@ class GetDomainTestCase(TestCase):
|
|||
|
||||
|
||||
# FIXME: enable Django apps dynamically and enable test again
|
||||
#@with_apps('django.contrib.sites')
|
||||
#@override_settings(TEST_DOMAIN=SETTING_DELETED,
|
||||
# ANALYTICAL_DOMAIN=SETTING_DELETED)
|
||||
#class GetDomainTestCaseWithSites(TestCase):
|
||||
# @with_apps('django.contrib.sites')
|
||||
# @override_settings(TEST_DOMAIN=SETTING_DELETED, ANALYTICAL_DOMAIN=SETTING_DELETED)
|
||||
# class GetDomainTestCaseWithSites(TestCase):
|
||||
# def test_get_domain_from_site(self):
|
||||
# site = Site.objects.create(domain="example.com", name="test")
|
||||
# with override_settings(SITE_ID=site.id):
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ def get_required_setting(setting, value_re, invalid_msg):
|
|||
value = getattr(settings, setting)
|
||||
except AttributeError:
|
||||
raise AnalyticalException("%s setting: not found" % setting)
|
||||
if value is None:
|
||||
raise AnalyticalException("%s setting is set to None" % setting)
|
||||
if not value:
|
||||
raise AnalyticalException("%s setting is not set" % setting)
|
||||
value = str(value)
|
||||
if not value_re.search(value):
|
||||
raise AnalyticalException("%s setting: %s: '%s'"
|
||||
|
|
@ -49,6 +49,19 @@ def get_user_from_context(context):
|
|||
return None
|
||||
|
||||
|
||||
def get_user_is_authenticated(user):
|
||||
"""Check if the user is authenticated.
|
||||
|
||||
This is a compatibility function needed to support both Django 1.x and 2.x;
|
||||
Django 2.x turns the function into a proper boolean so function calls will
|
||||
fail.
|
||||
"""
|
||||
if callable(user.is_authenticated):
|
||||
return user.is_authenticated()
|
||||
else:
|
||||
return user.is_authenticated
|
||||
|
||||
|
||||
def get_identity(context, prefix=None, identity_func=None, user=None):
|
||||
"""
|
||||
Get the identity of a logged in user from a template context.
|
||||
|
|
@ -71,7 +84,7 @@ def get_identity(context, prefix=None, identity_func=None, user=None):
|
|||
try:
|
||||
if user is None:
|
||||
user = get_user_from_context(context)
|
||||
if user.is_authenticated():
|
||||
if get_user_is_authenticated(user):
|
||||
if identity_func is not None:
|
||||
return identity_func(user)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import sys
|
|||
sys.path.append(os.path.join(os.path.abspath('.'), '_ext'))
|
||||
sys.path.append(os.path.dirname(os.path.abspath('.')))
|
||||
|
||||
import analytical
|
||||
import analytical # noqa
|
||||
|
||||
|
||||
# -- General configuration --------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ initialization code if the client IP address is detected as one from the
|
|||
:data:`ANALYTICAL_INTERNAL_IPS` setting. The default value for this
|
||||
setting is :data:`INTERNAL_IPS`.
|
||||
|
||||
Example::
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANALYTICAL_INTERNAL_IPS = ['192.168.1.45', '192.168.1.57']
|
||||
|
||||
|
|
@ -45,7 +47,9 @@ logged in through the standard Django authentication system and the
|
|||
current user is accessible in the template context, the username can be
|
||||
passed to the analytics services that support identifying users. This
|
||||
feature is configured by the :data:`ANALYTICAL_AUTO_IDENTIFY` setting
|
||||
and is enabled by default. To disable::
|
||||
and is enabled by default. To disable:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANALYTICAL_AUTO_IDENTIFY = False
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ version numbers. Patch-level increments indicate bug fixes, minor
|
|||
version increments indicate new functionality and major version
|
||||
increments indicate backwards incompatible changes.
|
||||
|
||||
Version 1.0.0 is the last to support Django < 1.7. Users of older django
|
||||
Version 1.0.0 is the last to support Django < 1.7. Users of older Django
|
||||
versions should stick to 1.0.0, and are encouraged to upgrade their setups.
|
||||
Starting with 2.0.0, dropping support for obsolete Django versions is not
|
||||
considered to be a backward-incompatible change.
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ django-analytical
|
|||
The django-analytical application integrates analytics services into a
|
||||
Django_ project.
|
||||
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
.. _Django: https://www.djangoproject.com/
|
||||
|
||||
:Package: http://pypi.python.org/pypi/django-analytical/
|
||||
:Source: http://github.com/jcassee/django-analytical
|
||||
:Package: https://pypi.python.org/pypi/django-analytical/
|
||||
:Source: https://github.com/jazzband/django-analytical
|
||||
|
||||
|
||||
Overview
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ get the development code:
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone https://github.com/jcassee/django-analytical.git
|
||||
$ git clone https://github.com/jazzband/django-analytical.git
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/django-analytical/
|
||||
.. _GitHub: http://github.com/jcassee/django-analytical
|
||||
.. _GitHub: http://github.com/jazzband/django-analytical
|
||||
|
||||
Then install the package by running the setup script:
|
||||
|
||||
|
|
@ -74,26 +74,26 @@ HTML page, django-analytical provides four general-purpose template tags
|
|||
that will render the code needed for the services you are using. Your
|
||||
base template should look like this:
|
||||
|
||||
.. code-block:: html
|
||||
.. code-block:: django
|
||||
|
||||
{% load analytical %}
|
||||
<!DOCTYPE ... >
|
||||
<html>
|
||||
<head>
|
||||
{% analytical_head_top %}
|
||||
{% load analytical %}
|
||||
<!DOCTYPE ... >
|
||||
<html>
|
||||
<head>
|
||||
{% analytical_head_top %}
|
||||
|
||||
...
|
||||
...
|
||||
|
||||
{% analytical_head_bottom %}
|
||||
</head>
|
||||
<body>
|
||||
{% analytical_body_top %}
|
||||
{% analytical_head_bottom %}
|
||||
</head>
|
||||
<body>
|
||||
{% analytical_body_top %}
|
||||
|
||||
...
|
||||
...
|
||||
|
||||
{% analytical_body_bottom %}
|
||||
</body>
|
||||
</html>
|
||||
{% analytical_body_bottom %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Instead of using the generic tags, you can also just use tags specific
|
||||
for the analytics service(s) you are using. See :ref:`services` for
|
||||
|
|
@ -125,6 +125,10 @@ settings required to enable each service are listed here:
|
|||
|
||||
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
|
||||
|
||||
* :doc:`Facebook Pixel <services/facebook_pixel>`::
|
||||
|
||||
FACEBOOK_PIXEL_ID = '1234567890'
|
||||
|
||||
* :doc:`Gaug.es <services/gauges>`::
|
||||
|
||||
GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef'
|
||||
|
|
@ -167,7 +171,7 @@ settings required to enable each service are listed here:
|
|||
|
||||
PERFORMABLE_API_KEY = '123abc'
|
||||
|
||||
* :doc:`Piwik <services/piwik>`::
|
||||
* :doc:`Matomo (formerly Piwik) <services/piwik>`::
|
||||
|
||||
PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path'
|
||||
PIWIK_SITE_ID = '123'
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ If you would like to have another analytics service supported by
|
|||
django-analytical, please create an issue on the project
|
||||
`issue tracker`_. See also :ref:`helping-out`.
|
||||
|
||||
.. _`issue tracker`: http://github.com/jcassee/django-analytical/issues
|
||||
.. _`issue tracker`: http://github.com/jazzband/django-analytical/issues
|
||||
|
||||
|
||||
Currently supported services:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
services/*
|
||||
services/*
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ contains a line that looks like this::
|
|||
Here, ``XXXXX`` is your User ID. Set :const:`CHARTBEAT_USER_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
||||
CHARTBEAT_SITE_ID = 'XXXXX'
|
||||
CHARTBEAT_USER_ID = 'XXXXX'
|
||||
|
||||
If you do not set a User ID, the tracking code will not be rendered.
|
||||
|
||||
|
|
|
|||
84
docs/services/facebook_pixel.rst
Normal file
84
docs/services/facebook_pixel.rst
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
=======================================
|
||||
Facebook Pixel -- advertising analytics
|
||||
=======================================
|
||||
|
||||
`Facebook Pixel`_ is Facebook's tool for conversion tracking, optimisation and remarketing.
|
||||
|
||||
.. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/
|
||||
|
||||
|
||||
.. facebook-pixel-installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Facebook Pixel 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 Facebook Pixel template tag to your templates.
|
||||
This step is only needed if you are not using the generic
|
||||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`facebook-pixel-configuration`.
|
||||
|
||||
The Facebook Pixel code is inserted into templates using template tags.
|
||||
Because every page that you want to track must have the tag,
|
||||
it is useful to add it to your base template.
|
||||
At the top of the template, load the :mod:`facebook_pixel` template tag library.
|
||||
Then insert the :ttag:`facebook_pixel_head` tag at the bottom of the head section,
|
||||
and optionally insert the :ttag:`facebook_pixel_body` tag at the bottom of the body section::
|
||||
|
||||
{% load facebook_pixel %}
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
{% facebook_pixel_head %}
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
{% facebook_pixel_body %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
.. note::
|
||||
The :ttag:`facebook_pixel_body` tag code will only be used for browsers with JavaScript disabled.
|
||||
It can be omitted if you don't need to support them.
|
||||
|
||||
|
||||
.. _facebook-pixel-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Facebook Pixel integration,
|
||||
you must first set your Pixel ID.
|
||||
|
||||
|
||||
.. _facebook-pixel-id:
|
||||
|
||||
Setting the Pixel ID
|
||||
--------------------
|
||||
|
||||
Each Facebook Adverts account you have can have a Pixel ID,
|
||||
and the :mod:`facebook_pixel` tags will include it in the rendered page.
|
||||
You can find the Pixel ID on the "Pixels" section of your Facebook Adverts account.
|
||||
Set :const:`FACEBOOK_PIXEL_ID` in the project :file:`settings.py` file::
|
||||
|
||||
FACEBOOK_PIXEL_ID = 'XXXXXXXXXX'
|
||||
|
||||
If you do not set a Pixel ID, the code will not be rendered.
|
||||
|
||||
|
||||
.. _facebook-pixel-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:`FACEBOOK_PIXEL_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.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
======================================
|
||||
Google Analytics -- traffic analysis
|
||||
Google Analytics (legacy) -- traffic analysis
|
||||
======================================
|
||||
|
||||
`Google Analytics`_ is the well-known web analytics service from
|
||||
|
|
@ -15,7 +15,7 @@ features.
|
|||
Installation
|
||||
============
|
||||
|
||||
To start using the Google Analytics integration, you must have installed
|
||||
To start using the Google Analytics (legacy) 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.
|
||||
|
|
@ -72,7 +72,7 @@ Tracking multiple domains
|
|||
|
||||
The default code is suitable for tracking a single domain. If you track
|
||||
multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE`
|
||||
setting to one of the :const:`analytical.templatetags.google_analytics.SCOPE_*`
|
||||
setting to one of the :const:`analytical.templatetags.google_analytics.TRACK_*`
|
||||
constants:
|
||||
|
||||
============================= ===== =============================================
|
||||
|
|
|
|||
227
docs/services/google_analytics_js.rst
Normal file
227
docs/services/google_analytics_js.rst
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
======================================
|
||||
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/
|
||||
|
||||
|
||||
.. google-analytics-installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Google Analytics 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 Google Analytics template tag to your
|
||||
templates. This step is only needed if you are not using the generic
|
||||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`google-analytics-configuration`.
|
||||
|
||||
The Google Analytics tracking code is inserted into templates using a
|
||||
template tag. Load the :mod:`google_analytics_js` template tag library and
|
||||
insert the :ttag:`google_analytics_js` tag. Because every page that you
|
||||
want to track must have the tag, it is useful to add it to your base
|
||||
template. Insert the tag at the bottom of the HTML head::
|
||||
|
||||
{% load google_analytics_js %}
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
{% google_analytics_js %}
|
||||
</head>
|
||||
...
|
||||
|
||||
|
||||
.. _google-analytics-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Google Analytics integration, you must first set
|
||||
your website property ID. If you track multiple domains with the same
|
||||
code, you also need to set-up the domain. Finally, you can add custom
|
||||
segments for Google Analytics to track.
|
||||
|
||||
|
||||
.. _google-analytics-property-id:
|
||||
|
||||
Setting the property ID
|
||||
-----------------------
|
||||
|
||||
Every website you track with Google Analytics gets its own property ID,
|
||||
and the :ttag:`google_analytics_js` tag will include it in the rendered
|
||||
Javascript code. You can find the web property ID on the overview page
|
||||
of your account. Set :const:`GOOGLE_ANALYTICS_JS_PROPERTY_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
||||
GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-XXXXXX-X'
|
||||
|
||||
If you do not set a property ID, the tracking code will not be rendered.
|
||||
|
||||
|
||||
Tracking multiple domains
|
||||
-------------------------
|
||||
|
||||
The default code is suitable for tracking a single domain. If you track
|
||||
multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE`
|
||||
setting to one of the :const:`analytical.templatetags.google_analytics_js.TRACK_*`
|
||||
constants:
|
||||
|
||||
============================= ===== =============================================
|
||||
Constant Value Description
|
||||
============================= ===== =============================================
|
||||
``TRACK_SINGLE_DOMAIN`` 1 Track one domain.
|
||||
``TRACK_MULTIPLE_SUBDOMAINS`` 2 Track multiple subdomains of the same top
|
||||
domain (e.g. `fr.example.com` and
|
||||
`nl.example.com`).
|
||||
``TRACK_MULTIPLE_DOMAINS`` 3 Track multiple top domains (e.g. `example.fr`
|
||||
and `example.nl`).
|
||||
============================= ===== =============================================
|
||||
|
||||
As noted, the default tracking style is
|
||||
:const:`~analytical.templatetags.google_analytics_js.TRACK_SINGLE_DOMAIN`.
|
||||
|
||||
When you track multiple (sub)domains, django-analytical needs to know
|
||||
what domain name to pass to Google Analytics. If you use the contrib
|
||||
sites app, the domain is automatically picked up from the current
|
||||
:const:`~django.contrib.sites.models.Site` instance. Otherwise, you may
|
||||
either pass the domain to the template tag through the context variable
|
||||
:const:`google_analytics_domain` (fallback: :const:`analytical_domain`)
|
||||
or set it in the project :file:`settings.py` file using
|
||||
:const:`GOOGLE_ANALYTICS_DOMAIN` (fallback: :const:`ANALYTICAL_DOMAIN`).
|
||||
|
||||
Display Advertising
|
||||
-------------------
|
||||
|
||||
Display Advertising allows you to view Demographics and Interests reports,
|
||||
add Remarketing Lists and support DoubleClick Campain Manager integration.
|
||||
|
||||
You can enable `Display Advertising features`_ by setting the
|
||||
:const:`GOOGLE_ANALYTICS_DISPLAY_ADVERTISING` configuration setting::
|
||||
|
||||
GOOGLE_ANALYTICS_DISPLAY_ADVERTISING = True
|
||||
|
||||
By default, display advertising features are disabled.
|
||||
|
||||
.. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482
|
||||
|
||||
|
||||
.. _google-analytics-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:`GOOGLE_ANALYTICS_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.
|
||||
|
||||
|
||||
.. _google-analytics-custom-variables:
|
||||
|
||||
Custom variables
|
||||
----------------
|
||||
|
||||
As described in the Google Analytics `custom variables`_ documentation
|
||||
page, you can define custom segments. Using template context variables
|
||||
``google_analytics_var1`` through ``google_analytics_var5``, you can let
|
||||
the :ttag:`google_analytics_js` tag pass custom variables to Google
|
||||
Analytics automatically. You can set the context variables in your view
|
||||
when your render a template containing the tracking code::
|
||||
|
||||
context = RequestContext({'google_analytics_var1': ('gender', 'female'),
|
||||
'google_analytics_var2': ('visit', 1)})
|
||||
return some_template.render(context)
|
||||
|
||||
The value of the context variable is a tuple *(name, value)*.
|
||||
|
||||
You may want to set custom variables in a context processor that you add
|
||||
to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`::
|
||||
|
||||
def google_analytics_segment_language(request):
|
||||
try:
|
||||
return {'google_analytics_var3': request.LANGUAGE_CODE}
|
||||
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.
|
||||
|
||||
.. _`custom variables`: https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#custom-vars
|
||||
|
||||
|
||||
.. _google-analytics-anonimyze-ips:
|
||||
|
||||
Anonymize IPs
|
||||
-------------
|
||||
|
||||
You can enable the `IP anonymization`_ feature by setting the
|
||||
:const:`GOOGLE_ANALYTICS_ANONYMIZE_IP` configuration setting::
|
||||
|
||||
GOOGLE_ANALYTICS_ANONYMIZE_IP = True
|
||||
|
||||
This may be mandatory for deployments in countries that have a firm policies
|
||||
concerning data privacy (e.g. Germany).
|
||||
|
||||
By default, IPs are not anonymized.
|
||||
|
||||
.. _`IP anonymization`: https://support.google.com/analytics/bin/answer.py?hl=en&answer=2763052
|
||||
|
||||
|
||||
.. _google-analytics-sample-rate:
|
||||
|
||||
Sample Rate
|
||||
-----------
|
||||
|
||||
You can configure the `Sample Rate`_ feature by setting the
|
||||
:const:`GOOGLE_ANALYTICS_SAMPLE_RATE` configuration setting::
|
||||
|
||||
GOOGLE_ANALYTICS_SAMPLE_RATE = 10
|
||||
|
||||
The value is a percentage and can be between 0 and 100 and can be a string or
|
||||
integer value.
|
||||
|
||||
.. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#sampleRate
|
||||
|
||||
|
||||
.. _google-analytics-site-speed-sample-rate:
|
||||
|
||||
Site Speed Sample Rate
|
||||
----------------------
|
||||
|
||||
You can configure the `Site Speed Sample Rate`_ feature by setting the
|
||||
:const:`GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE` configuration setting::
|
||||
|
||||
GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE = 10
|
||||
|
||||
The value is a percentage and can be between 0 and 100 and can be a string or
|
||||
integer value.
|
||||
|
||||
.. _`Site Speed Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#siteSpeedSampleRate
|
||||
|
||||
|
||||
.. _google-analytics-cookie-expiration:
|
||||
|
||||
Cookie Expiration
|
||||
----------------------
|
||||
|
||||
You can configure the `Cookie Expiration`_ feature by setting the
|
||||
:const:`GOOGLE_ANALYTICS_COOKIE_EXPIRATION` configuration setting::
|
||||
|
||||
GOOGLE_ANALYTICS_COOKIE_EXPIRATION = 3600000
|
||||
|
||||
The value is the cookie expiration in seconds or 0 to delete the cookie when the browser is closed.
|
||||
|
||||
.. _`Cookie Expiration`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout
|
||||
73
docs/services/hotjar.rst
Normal file
73
docs/services/hotjar.rst
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
=====================================
|
||||
Hotjar -- analytics and user feedback
|
||||
=====================================
|
||||
|
||||
`Hotjar`_ is a website analytics and user feedback tool.
|
||||
|
||||
.. _`Hotjar`: https://www.hotjar.com/
|
||||
|
||||
|
||||
.. hotjar-installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Hotjar 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 Hotjar template tag to your templates.
|
||||
This step is only needed if you are not using the generic
|
||||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`hotjar-configuration`.
|
||||
|
||||
The Hotjar code is inserted into templates using template tags.
|
||||
Because every page that you want to track must have the tag,
|
||||
it is useful to add it to your base template.
|
||||
At the top of the template, load the :mod:`hotjar` template tag library.
|
||||
Then insert the :ttag:`hotjar` tag at the bottom of the head section::
|
||||
|
||||
{% load hotjar %}
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
{% hotjar %}
|
||||
</head>
|
||||
...
|
||||
</html>
|
||||
|
||||
|
||||
.. _hotjar-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Hotjar integration, you must first set your Site ID.
|
||||
|
||||
|
||||
.. _hotjar-id:
|
||||
|
||||
Setting the Hotjar Site ID
|
||||
--------------------------
|
||||
|
||||
You can find the Hotjar Site ID in the "Sites & Organizations" section of your Hotjar account.
|
||||
Set :const:`HOTJAR_SITE_ID` in the project :file:`settings.py` file::
|
||||
|
||||
HOTJAR_SITE_ID = 'XXXXXXXXX'
|
||||
|
||||
If you do not set a Hotjar Site ID, the code will not be rendered.
|
||||
|
||||
|
||||
.. _hotjar-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:`HOTJAR_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.
|
||||
|
|
@ -120,22 +120,41 @@ Context variable Description
|
|||
-------------------- -------------------------------------------
|
||||
``intercom_email`` The visitor's email address.
|
||||
-------------------- -------------------------------------------
|
||||
``intercom_user_id`` The visitor's user id.
|
||||
-------------------- -------------------------------------------
|
||||
``created_at`` The date the visitor created an account
|
||||
==================== ===========================================
|
||||
|
||||
|
||||
.. _`custom visitor data`: http://docs.intercom.io/custom-data/adding-custom-data
|
||||
.. _`custom visitor data`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/customize-intercom-to-be-about-your-users/send-custom-user-attributes-to-intercom
|
||||
|
||||
|
||||
Identifying authenticated users
|
||||
-------------------------------
|
||||
|
||||
If you have not set the ``intercom_name`` or ``intercom_email`` variables
|
||||
If you have not set the ``intercom_name``, ``intercom_email``, or ``intercom_user_id`` variables
|
||||
explicitly, the username and email address of an authenticated user are
|
||||
passed to Intercom automatically. See :ref:`identifying-visitors`.
|
||||
|
||||
.. _intercom-internal-ips:
|
||||
|
||||
|
||||
Verifying identified users
|
||||
--------------------------
|
||||
|
||||
Intercom supports HMAC authentication of users identified by user ID or email, in order to prevent impersonation.
|
||||
For more information, see `Enable identity verification on your web product`_ in the Intercom documentation.
|
||||
|
||||
To enable this, configure your Intercom account's HMAC secret key::
|
||||
|
||||
INTERCOM_HMAC_SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
||||
|
||||
(You can find this secret key under the "Identity verification" section of your Intercom account settings page.)
|
||||
|
||||
.. _`Enable identity verification on your web product`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
|
||||
|
||||
|
||||
|
||||
Internal IP addresses
|
||||
---------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
==================================
|
||||
Piwik -- open source web analytics
|
||||
Matomo (formerly Piwik) -- open source web analytics
|
||||
==================================
|
||||
|
||||
Piwik_ is an open analytics platform currently used by individuals,
|
||||
|
|
@ -142,6 +142,13 @@ set the context variable ``analytical_identity`` (for global configuration) or
|
|||
|
||||
.. _`track individual users`: http://developer.piwik.org/guides/tracking-javascript-guide#user-id
|
||||
|
||||
Disabling cookies
|
||||
-----------------
|
||||
|
||||
If you want to `disable cookies`_, set :data:`PIWIKI_DISABLE_COOKIES` to
|
||||
:const:`True`. This is disabled by default.
|
||||
|
||||
.. _`disable cookies`: https://matomo.org/faq/general/faq_157/
|
||||
|
||||
Internal IP addresses
|
||||
---------------------
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ Here's a full list of all available settings, in alphabetical order, and
|
|||
their default values.
|
||||
|
||||
|
||||
|
||||
.. data:: ANALYTICAL_AUTO_IDENTIFY
|
||||
|
||||
Default: ``True``
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ Setting up basic tracking
|
|||
|
||||
To get started with django-analytical, the package must first be
|
||||
installed. You can download and install the latest stable package from
|
||||
the Python Package Index automatically by using ``easy_install``::
|
||||
the Python Package Index automatically by using ``easy_install``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ easy_install django-analytical
|
||||
|
||||
|
|
@ -37,7 +39,9 @@ For more ways to install django-analytical, see
|
|||
:ref:`installing-the-package`.
|
||||
|
||||
After you install django-analytical, you need to add it to the list of
|
||||
installed applications in the ``settings.py`` file of your project::
|
||||
installed applications in the ``settings.py`` file of your project:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
|
|
@ -46,7 +50,9 @@ installed applications in the ``settings.py`` file of your project::
|
|||
]
|
||||
|
||||
Then you have to add the general-purpose django-analytical template tags
|
||||
to your base template::
|
||||
to your base template:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% load analytical %}
|
||||
<!DOCTYPE ... >
|
||||
|
|
@ -69,7 +75,9 @@ to your base template::
|
|||
|
||||
Finally, you need to configure the Clicky Site ID and the Crazy Egg
|
||||
account number. Add the following to your project :file:`settings.py`
|
||||
file (replacing the ``x``'s with your own codes)::
|
||||
file (replacing the ``x``'s with your own codes):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
CLICKY_SITE_ID = 'xxxxxxxx'
|
||||
CRAZY_EGG_ACCOUNT_NUMBER = 'xxxxxxxx'
|
||||
|
|
@ -110,7 +118,9 @@ protocol version.
|
|||
|
||||
In order to filter on protocol version in Crazy Egg, you need to
|
||||
include the visitor IP protocol version in the Crazy Egg tracking code.
|
||||
The easiest way to do this is by using a context processor::
|
||||
The easiest way to do this is by using a context processor:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def track_ip_proto(request):
|
||||
addr = request.META.get('HTTP_X_FORWARDED_FOR', '')
|
||||
|
|
|
|||
35
setup.py
35
setup.py
|
|
@ -16,13 +16,6 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from sphinx_pypi_upload import UploadDoc
|
||||
|
||||
cmdclass['upload_sphinx'] = UploadDoc
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class TestCommand(Command):
|
||||
description = "run package tests"
|
||||
|
|
@ -43,7 +36,7 @@ class TestCommand(Command):
|
|||
cmdclass['test'] = TestCommand
|
||||
|
||||
|
||||
def read(fname):
|
||||
def read_file(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
|
||||
|
|
@ -60,16 +53,16 @@ except ImportError:
|
|||
"This is fine, unless you intend to run unit tests."
|
||||
)
|
||||
|
||||
import analytical
|
||||
import analytical as package # noqa
|
||||
|
||||
setup(
|
||||
name='django-analytical',
|
||||
version=analytical.__version__,
|
||||
license=analytical.__license__,
|
||||
description='Analytics service integration for Django projects',
|
||||
long_description=read('README.rst'),
|
||||
author=analytical.__author__,
|
||||
author_email=analytical.__email__,
|
||||
version=package.__version__,
|
||||
license=package.__license__,
|
||||
description=package.__doc__.strip(),
|
||||
long_description=read_file('README.rst'),
|
||||
author=package.__author__,
|
||||
author_email=package.__email__,
|
||||
packages=[
|
||||
'analytical',
|
||||
'analytical.templatetags',
|
||||
|
|
@ -84,6 +77,10 @@ setup(
|
|||
'Framework :: Django :: 1.7',
|
||||
'Framework :: Django :: 1.8',
|
||||
'Framework :: Django :: 1.9',
|
||||
'Framework :: Django :: 1.10',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.0',
|
||||
'Framework :: Django :: 2.1',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
|
|
@ -93,14 +90,14 @@ setup(
|
|||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
],
|
||||
platforms=['any'],
|
||||
url='https://github.com/jcassee/django-analytical',
|
||||
download_url='https://github.com/jcassee/django-analytical/archive/master.zip',
|
||||
url='https://github.com/jazzband/django-analytical',
|
||||
download_url='https://github.com/jazzband/django-analytical/archive/master.zip',
|
||||
cmdclass=cmdclass,
|
||||
install_requires=[
|
||||
'Django>=1.7.0',
|
||||
|
|
|
|||
39
tox.ini
39
tox.ini
|
|
@ -1,19 +1,50 @@
|
|||
[tox]
|
||||
envlist =
|
||||
py{27,32,33,34}-django17,
|
||||
py{27,32,33,34,35}-django18,
|
||||
# Python/Django combinations that are officially supported
|
||||
py{27,34}-django17
|
||||
py{27,34,35}-django18
|
||||
py{27,34,35}-django19
|
||||
py{27,34,35}-django110
|
||||
py{27,34,35,36,37}-django111
|
||||
py{34,35,36,37}-django20
|
||||
py{35,36,37}-django21
|
||||
flake8
|
||||
readme
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
coverage run setup.py test
|
||||
sh -c 'coveralls | true'
|
||||
deps =
|
||||
coverage==3.7.1
|
||||
coverage
|
||||
coveralls
|
||||
django17: Django>=1.7,<1.8
|
||||
django18: Django>=1.8,<1.9
|
||||
django19: Django>=1.9,<1.10
|
||||
virtualenv<14.0.0
|
||||
django110: Django>=1.10,<1.11
|
||||
django111: Django>=1.11,<2.0
|
||||
django20: Django>=2.0,<2.1
|
||||
django21: Django>=2.1,<2.2
|
||||
passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
|
||||
whitelist_externals = sh
|
||||
|
||||
[testenv:flake8]
|
||||
deps = flake8
|
||||
commands = flake8
|
||||
|
||||
[testenv:readme]
|
||||
deps = readme_renderer
|
||||
commands = python setup.py check --restructuredtext --strict
|
||||
|
||||
[travis:env]
|
||||
DJANGO =
|
||||
1.7: django17
|
||||
1.8: django18
|
||||
1.9: django19
|
||||
1.10: django110
|
||||
1.11: django111
|
||||
2.0: django20
|
||||
2.1: django21
|
||||
|
||||
[flake8]
|
||||
max-line-length = 100
|
||||
|
|
|
|||
Loading…
Reference in a new issue