mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Merge remote-tracking branch 'upstream/main' into jayhaluska-master
This commit is contained in:
commit
0e826aa1bd
123 changed files with 3517 additions and 2533 deletions
1
.bandit
1
.bandit
|
|
@ -1 +0,0 @@
|
|||
tox.ini
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[run]
|
||||
source = analytical
|
||||
omit = analytical/tests/*
|
||||
35
.github/workflows/check.yml
vendored
Normal file
35
.github/workflows/check.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
name: Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
env:
|
||||
- lint
|
||||
- format
|
||||
- audit
|
||||
- package
|
||||
- docs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install prerequisites
|
||||
run: python -m pip install tox
|
||||
|
||||
- name: Run ${{ matrix.env }}
|
||||
run: tox -e ${{ matrix.env }}
|
||||
44
.github/workflows/release.yml
vendored
Normal file
44
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: '1'
|
||||
PY_COLORS: '1'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'jazzband/django-analytical'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
**/pyproject.toml
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install tox
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
tox -e package
|
||||
|
||||
- name: Upload packages to Jazzband
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: jazzband
|
||||
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
||||
repository_url: https://jazzband.co/projects/django-analytical/upload
|
||||
58
.github/workflows/test.yml
vendored
Normal file
58
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: '1'
|
||||
PY_COLORS: '1'
|
||||
|
||||
jobs:
|
||||
python-django:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 5
|
||||
matrix:
|
||||
python-version:
|
||||
- '3.10'
|
||||
- '3.11'
|
||||
- '3.12'
|
||||
- '3.13'
|
||||
django-version:
|
||||
- '4.2'
|
||||
- '5.1'
|
||||
- '5.2'
|
||||
include:
|
||||
- { python-version: '3.9', django-version: '4.2' }
|
||||
exclude:
|
||||
- { python-version: '3.13', django-version: '4.2' }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
**/pyproject.toml
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install tox tox-gh-actions
|
||||
|
||||
- name: Tox tests (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }})
|
||||
run: tox
|
||||
env:
|
||||
DJANGO: ${{ matrix.django-version }}
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
name: Python ${{ matrix.python-version }}
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -4,23 +4,21 @@
|
|||
/.tox
|
||||
/.vscode
|
||||
/.envrc
|
||||
/playground
|
||||
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
/.coverage
|
||||
/coverage.xml
|
||||
/tests/*-report.json
|
||||
/tests/*-report.xml
|
||||
|
||||
/build
|
||||
/dist
|
||||
/docs/_build
|
||||
/MANIFEST
|
||||
/playground
|
||||
|
||||
/docs/_templates/layout.html
|
||||
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
/MANIFEST
|
||||
*.egg-info
|
||||
|
||||
/requirements.txt
|
||||
|
|
|
|||
1
.pre-commit-config.yaml
Normal file
1
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
repos: []
|
||||
18
.readthedocs.yaml
Normal file
18
.readthedocs.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Read the Docs configuration file
|
||||
# https://docs.readthedocs.io/en/stable/config-file/v2.html
|
||||
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# We recommend specifying your dependencies to enable reproducible builds:
|
||||
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
# python:
|
||||
# install:
|
||||
# - requirements: docs/requirements.txt
|
||||
57
.travis.yml
57
.travis.yml
|
|
@ -1,57 +0,0 @@
|
|||
dist: xenial
|
||||
sudo: true
|
||||
|
||||
language: python
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
|
||||
env:
|
||||
- DJANGO=1.11
|
||||
- DJANGO=2.1
|
||||
- DJANGO=2.2
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: TOXENV=bandit
|
||||
exclude:
|
||||
# Python/Django combinations that aren't officially supported
|
||||
- { env: DJANGO=1.11, python: 3.7 }
|
||||
- { env: DJANGO=2.1, python: 2.7 }
|
||||
- { env: DJANGO=2.1, python: 3.4 }
|
||||
- { env: DJANGO=2.2, python: 2.7 }
|
||||
- { env: DJANGO=2.2, python: 3.4 }
|
||||
|
||||
install:
|
||||
- pip install tox-travis
|
||||
script:
|
||||
- tox
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- deploy
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- { stage: lint, env: TOXENV=flake8, python: 3.7 }
|
||||
- { stage: lint, env: TOXENV=bandit, python: 3.7 }
|
||||
- { stage: lint, env: TOXENV=readme, python: 3.7 }
|
||||
- stage: deploy
|
||||
env:
|
||||
python: 3.7
|
||||
install: skip
|
||||
script: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
server: https://jazzband.co/projects/django-analytical/upload
|
||||
distributions: sdist bdist_wheel
|
||||
user: jazzband
|
||||
password:
|
||||
secure: JCr5hRjAeXuiISodCJf8HWd4BTJMpl2eiHI8NciPaSM9WwOOeUXxmlcP8+lWlXxgM4BYUC/O7Q90fkwj5x06n+z4oyJSEVerTvCDcpeZ68KMMG1tR1jTbHcxfEKoEvcs2J0fThJ9dIMtfbtUbIpzusJHkZPjsIy8HAJDw8knnJs=
|
||||
on:
|
||||
tags: true
|
||||
repo: jazzband/django-analytical
|
||||
50
AUTHORS.rst
50
AUTHORS.rst
|
|
@ -1,50 +0,0 @@
|
|||
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`_, `Scott Karlin`_
|
||||
and others.
|
||||
|
||||
Included Javascript code snippets for integration of the analytics
|
||||
services were written by the respective service providers.
|
||||
|
||||
The application was inspired by and uses ideas from Analytical_, Joshua
|
||||
Krall's all-purpose analytics front-end for Rails.
|
||||
|
||||
The work on Crazy Egg was made possible by `Bateau Knowledge`_.
|
||||
The work on Intercom was made possible by `GreenKahuna`_.
|
||||
|
||||
.. _`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
|
||||
.. _`Pi Delport`: https://github.com/pjdelport
|
||||
.. _`Sandra Mau`: https://github.com/xthepoet
|
||||
.. _`Simon Ye`: https://github.com/yesimon
|
||||
.. _`Tinnet Coronam`: https://github.com/tinnet
|
||||
.. _`Philippe O. Wagner`: mailto:admin@arteria.ch
|
||||
.. _`Max Arnold`: https://github.com/max-arnold
|
||||
.. _`Martín Gaitán`: https://github.com/mgaitan
|
||||
.. _`Craig Bruce`: https://github.com/craigbruce
|
||||
.. _`Peter Bittner`: https://github.com/bittner
|
||||
.. _`Scott Adams`: https://github.com/7wonders
|
||||
.. _`Eric Amador`: https://github.com/amadornimbis
|
||||
.. _`Alexandre Pocquet`: https://github.com/apocquet
|
||||
.. _`Brad Pitcher`: https://github.com/brad
|
||||
.. _`Hugo Osvaldo Barrera`: https://github.com/hobarrera
|
||||
.. _`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/
|
||||
.. _`Scott Karlin`: https://github.com/sckarlin
|
||||
|
|
@ -1,8 +1,53 @@
|
|||
(unreleased)
|
||||
------------
|
||||
* Fix GA gtag user_id setup and add support for custom dimensions (Erick Massip)
|
||||
* Change spelling of "JavaScript" across all files in docstrings and docs
|
||||
(Peter Bittner)
|
||||
|
||||
Version 3.2.0
|
||||
-------------
|
||||
* Remove deprecated Piwik integration. Use Matomo instead! (Peter Bittner)
|
||||
* Migrate packaging from setup.py to pyproject.toml with Ruff for linting
|
||||
and formatting (Peter Bittner)
|
||||
* Remove obsolete type attribute in script tags for JavaScript (Peter Bittner)
|
||||
* Drop the end-of-life Python 3.8 as required by changed semantics of the
|
||||
license metadata field (Peter Bittner)
|
||||
* Remove AUTHORS file to avoid confusion; this is now metadata maintained
|
||||
in pyproject.toml (Peter Bittner)
|
||||
* Add more configuration options for Woopra (Peter Bittner)
|
||||
|
||||
Version 3.1.0
|
||||
-------------
|
||||
* Rename default branch from master to main (Peter Bittner, Jannis Leidel)
|
||||
* Modernize packaging setup, add pyproject.toml (Peter Bittner)
|
||||
* Integrate isort, reorganize imports (David Smith)
|
||||
* Refactor test suite from Python unit tests to Pytest (David Smith)
|
||||
* Add Heap integration (Garrett Coakley)
|
||||
* Drop Django 3.1, cover Django 4.0 and Python 3.10 in test suite (David Smith)
|
||||
|
||||
Version 3.0.0
|
||||
-------------
|
||||
* Add support for Lucky Orange (Peter Bittner)
|
||||
* Add missing instructions in Installation chapter of the docs (Peter Bittner)
|
||||
* Migrate test setup to Pytest (David Smith, Peter Bittner, Pi Delport)
|
||||
* Support Django 3.1 and Python 3.9, drop Django 1.11 and Python 2.7/3.5 (David Smith)
|
||||
* Migrate from Travis CI to GitHub Actions (Jannis Leidel)
|
||||
* Update accepted patterns (regex) for Google Analytics GTag (Taha Rushain)
|
||||
* Scope Piwik warning to use of Piwik (Hugo Barrera)
|
||||
* Add ``user_id`` to Google Analytics GTag (Sean Wallace)
|
||||
|
||||
Version 2.6.0
|
||||
-------------
|
||||
* Support Django 3.0 and Python 3.8, drop Django 2.1
|
||||
* Add support for Google Analytics Tag Manager (Marc Bourqui)
|
||||
* Add Matomo, the renamed version of Piwik (Scott Karlin)
|
||||
* Move Joost's project over to the Jazzband
|
||||
|
||||
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)
|
||||
* Add support for Hotjar (Pi Delport)
|
||||
* Make sure _trackPageview happens before other settings in Google Analytics
|
||||
(Diederik van der Boor)
|
||||
|
||||
|
|
@ -172,7 +217,7 @@ Version 0.5.0
|
|||
-------------
|
||||
* Split off Geckoboard support into django-geckoboard_.
|
||||
|
||||
.. _django-geckoboard: http://pypi.python.org/pypi/django-geckoboard
|
||||
.. _django-geckoboard: https://pypi.org/project/django-geckoboard
|
||||
|
||||
Version 0.4.0
|
||||
-------------
|
||||
|
|
|
|||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Code of Conduct
|
||||
|
||||
As contributors and maintainers of the Jazzband projects, and in the interest of
|
||||
fostering an open and welcoming community, we pledge to respect all people who
|
||||
contribute through reporting issues, posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in the Jazzband a harassment-free experience
|
||||
for everyone, regardless of the level of experience, gender, gender identity and
|
||||
expression, sexual orientation, disability, personal appearance, body size, race,
|
||||
ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery
|
||||
- Personal attacks
|
||||
- Trolling or insulting/derogatory comments
|
||||
- Public or private harassment
|
||||
- Publishing other's private information, such as physical or electronic addresses,
|
||||
without explicit permission
|
||||
- Other unethical or unprofessional conduct
|
||||
|
||||
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are not
|
||||
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
||||
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
||||
consistently applying these principles to every aspect of managing the jazzband
|
||||
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
||||
removed from the Jazzband roadies.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces when an
|
||||
individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
||||
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
||||
investigated and will result in a response that is deemed necessary and appropriate to
|
||||
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
||||
reporter of an incident.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
||||
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
||||
|
||||
[homepage]: https://contributor-covenant.org
|
||||
[version]: https://contributor-covenant.org/version/1/3/0/
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
include LICENSE.txt *.rst
|
||||
recursive-include docs *.rst *.py
|
||||
recursive-include tests *.py
|
||||
|
|
|
|||
39
README.rst
39
README.rst
|
|
@ -1,14 +1,14 @@
|
|||
django-analytical |latest-version|
|
||||
==================================
|
||||
|
||||
|build-status| |coverage| |python-support| |license| |gitter| |jazzband|
|
||||
|build-status| |coverage| |python-support| |license| |jazzband|
|
||||
|
||||
The django-analytical application integrates analytics services into a
|
||||
Django_ project.
|
||||
|
||||
.. start docs include
|
||||
|
||||
Using an analytics service with a Django project means adding Javascript
|
||||
Using an analytics service with a Django project means adding JavaScript
|
||||
tracking code to the project templates. Of course, every service has
|
||||
its own specific installation instructions. Furthermore, you need to
|
||||
include your unique identifiers, which then end up in the templates.
|
||||
|
|
@ -19,31 +19,28 @@ behind a generic interface, and keeps personal information and
|
|||
configuration out of the templates. Its goal is to make the basic
|
||||
set-up very simple, while allowing advanced users to customize tracking.
|
||||
Each service is set up as recommended by the services themselves, using
|
||||
an asynchronous version of the Javascript code if possible.
|
||||
an asynchronous version of the JavaScript code if possible.
|
||||
|
||||
.. end docs include
|
||||
|
||||
.. |latest-version| image:: https://img.shields.io/pypi/v/django-analytical.svg
|
||||
:alt: Latest version on PyPI
|
||||
:target: https://pypi.org/project/django-analytical/
|
||||
.. |build-status| image:: https://img.shields.io/travis/jazzband/django-analytical/master.svg
|
||||
:alt: Build status
|
||||
:target: https://travis-ci.org/jazzband/django-analytical
|
||||
.. |coverage| image:: https://img.shields.io/coveralls/github/jazzband/django-analytical/master.svg
|
||||
.. |build-status| image:: https://github.com/jazzband/django-analytical/workflows/Test/badge.svg
|
||||
:target: https://github.com/jazzband/django-analytical/actions
|
||||
:alt: GitHub Actions
|
||||
.. |coverage| image:: https://codecov.io/gh/jazzband/django-analytical/branch/main/graph/badge.svg
|
||||
:alt: Test coverage
|
||||
:target: https://coveralls.io/r/jazzband/django-analytical
|
||||
:target: https://codecov.io/gh/jazzband/django-analytical
|
||||
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg
|
||||
:target: https://pypi.org/project/django-analytical/
|
||||
:alt: Python versions
|
||||
.. |license| image:: https://img.shields.io/pypi/l/django-analytical.svg
|
||||
:alt: Software license
|
||||
:target: https://github.com/jazzband/django-analytical/blob/master/LICENSE.txt
|
||||
.. |gitter| image:: https://img.shields.io/gitter/room/jazzband/django-analytical.svg
|
||||
:alt: Gitter chat room
|
||||
:target: https://gitter.im/jazzband/django-analytical
|
||||
:target: https://github.com/jazzband/django-analytical/blob/main/LICENSE.txt
|
||||
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
|
||||
:alt: Jazzband
|
||||
:target: https://jazzband.co/
|
||||
:target: https://jazzband.co/projects/django-analytical
|
||||
.. _`Django`: http://www.djangoproject.com/
|
||||
|
||||
Currently Supported Services
|
||||
|
|
@ -57,11 +54,13 @@ Currently Supported Services
|
|||
* `Gaug.es`_ real time web analytics
|
||||
* `Google Analytics`_ traffic analysis
|
||||
* `GoSquared`_ traffic monitoring
|
||||
* `Heap`_ analytics and events tracking
|
||||
* `Hotjar`_ analytics and user feedback
|
||||
* `HubSpot`_ inbound marketing
|
||||
* `Intercom`_ live chat and support
|
||||
* `KISSinsights`_ feedback surveys
|
||||
* `KISSmetrics`_ funnel analysis
|
||||
* `Lucky Orange`_ analytics and user feedback
|
||||
* `Mixpanel`_ event tracking
|
||||
* `Olark`_ visitor chat
|
||||
* `Optimizely`_ A/B testing
|
||||
|
|
@ -75,18 +74,20 @@ Currently Supported Services
|
|||
* `Yandex.Metrica`_ web analytics
|
||||
|
||||
.. _`Chartbeat`: http://www.chartbeat.com/
|
||||
.. _`Clickmap`: http://getclickmap.com/
|
||||
.. _`Clickmap`: http://clickmap.ch/
|
||||
.. _`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/
|
||||
.. _`Heap`: https://heapanalytics.com/
|
||||
.. _`Hotjar`: https://www.hotjar.com/
|
||||
.. _`HubSpot`: http://www.hubspot.com/
|
||||
.. _`Intercom`: http://www.intercom.io/
|
||||
.. _`KISSinsights`: http://www.kissinsights.com/
|
||||
.. _`KISSmetrics`: http://www.kissmetrics.com/
|
||||
.. _`Lucky Orange`: http://www.luckyorange.com/
|
||||
.. _`Mixpanel`: http://www.mixpanel.com/
|
||||
.. _`Olark`: http://www.olark.com/
|
||||
.. _`Optimizely`: http://www.optimizely.com/
|
||||
|
|
@ -104,9 +105,7 @@ Documentation and Support
|
|||
|
||||
The documentation can be found in the ``docs`` directory or `read
|
||||
online`_. The source code and issue tracker are generously `hosted by
|
||||
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`_.
|
||||
GitHub`_.
|
||||
|
||||
.. _`read online`: https://django-analytical.readthedocs.io/
|
||||
.. _`hosted by GitHub`: https://github.com/jazzband/django-analytical
|
||||
|
|
@ -124,11 +123,17 @@ 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.
|
||||
|
||||
At the end, don't forget to add yourself to the `list of authors`_ and
|
||||
update the `changelog`_ with a short description of your contribution.
|
||||
We want you to stand out from the crowd as an open source superstar! ✦
|
||||
|
||||
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
|
||||
.. _`list of authors`: https://github.com/jazzband/django-analytical/blob/main/pyproject.toml
|
||||
.. _`changelog`: https://github.com/jazzband/django-analytical/blob/main/CHANGELOG.rst
|
||||
.. _`Jazzband`: https://jazzband.co
|
||||
.. _`Contributor Code of Conduct`: https://jazzband.co/about/conduct
|
||||
.. _`guidelines`: https://jazzband.co/about/guidelines
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
"""
|
||||
Analytics service integration for Django projects
|
||||
Analytics service integration for Django projects.
|
||||
"""
|
||||
|
||||
__author__ = "Joost Cassee"
|
||||
__email__ = "joost@cassee.net"
|
||||
__version__ = "2.5.0"
|
||||
__copyright__ = "Copyright (C) 2011-2019 Joost Cassee and contributors"
|
||||
__license__ = "MIT"
|
||||
__version__ = '3.2.0'
|
||||
|
|
|
|||
|
|
@ -2,17 +2,14 @@
|
|||
Analytical template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
from importlib import import_module
|
||||
|
||||
from django import template
|
||||
from django.template import Node, TemplateSyntaxError
|
||||
from importlib import import_module
|
||||
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
TAG_LOCATIONS = ['head_top', 'head_bottom', 'body_top', 'body_bottom']
|
||||
TAG_POSITIONS = ['first', None, 'last']
|
||||
TAG_MODULES = [
|
||||
|
|
@ -24,18 +21,20 @@ TAG_MODULES = [
|
|||
'analytical.gauges',
|
||||
'analytical.google_analytics',
|
||||
'analytical.google_analytics_js',
|
||||
'analytical.google_analytics_gtag',
|
||||
'analytical.gosquared',
|
||||
'analytical.heap',
|
||||
'analytical.hotjar',
|
||||
'analytical.hubspot',
|
||||
'analytical.intercom',
|
||||
'analytical.kiss_insights',
|
||||
'analytical.kiss_metrics',
|
||||
'analytical.luckyorange',
|
||||
'analytical.matomo',
|
||||
'analytical.mixpanel',
|
||||
'analytical.olark',
|
||||
'analytical.optimizely',
|
||||
'analytical.performable',
|
||||
'analytical.piwik',
|
||||
'analytical.rating_mailru',
|
||||
'analytical.snapengage',
|
||||
'analytical.spring_metrics',
|
||||
|
|
@ -67,12 +66,11 @@ class AnalyticalNode(Node):
|
|||
self.nodes = [node_cls() for node_cls in template_nodes[location]]
|
||||
|
||||
def render(self, context):
|
||||
return "".join([node.render(context) for node in self.nodes])
|
||||
return ''.join([node.render(context) for node in self.nodes])
|
||||
|
||||
|
||||
def _load_template_nodes():
|
||||
template_nodes = dict((l, dict((p, []) for p in TAG_POSITIONS))
|
||||
for l in TAG_LOCATIONS)
|
||||
template_nodes = {loc: {pos: [] for pos in TAG_POSITIONS} for loc in TAG_LOCATIONS}
|
||||
|
||||
def add_node_cls(location, node, position=None):
|
||||
template_nodes[location][position].append(node)
|
||||
|
|
@ -84,14 +82,15 @@ def _load_template_nodes():
|
|||
except AnalyticalException as e:
|
||||
logger.debug("not loading tags from '%s': %s", path, e)
|
||||
for location in TAG_LOCATIONS:
|
||||
template_nodes[location] = sum((template_nodes[location][p]
|
||||
for p in TAG_POSITIONS), [])
|
||||
template_nodes[location] = sum(
|
||||
(template_nodes[location][p] for p in TAG_POSITIONS), []
|
||||
)
|
||||
return template_nodes
|
||||
|
||||
|
||||
def _import_tag_module(path):
|
||||
app_name, lib_name = path.rsplit('.', 1)
|
||||
return import_module("%s.templatetags.%s" % (app_name, lib_name))
|
||||
return import_module('%s.templatetags.%s' % (app_name, lib_name))
|
||||
|
||||
|
||||
template_nodes = _load_template_nodes()
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
Chartbeat template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
|
|
@ -11,13 +9,12 @@ from django.conf import settings
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
USER_ID_RE = re.compile(r'^\d+$')
|
||||
INIT_CODE = """<script type="text/javascript">var _sf_startpt=(new Date()).getTime()</script>"""
|
||||
INIT_CODE = """<script>var _sf_startpt=(new Date()).getTime()</script>"""
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _sf_async_config=%(config)s;
|
||||
(function(){
|
||||
function loadChartbeat() {
|
||||
|
|
@ -47,7 +44,7 @@ def chartbeat_top(parser, token):
|
|||
"""
|
||||
Top Chartbeat template tag.
|
||||
|
||||
Render the top Javascript code for Chartbeat.
|
||||
Render the top JavaScript code for Chartbeat.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
|
|
@ -58,7 +55,7 @@ def chartbeat_top(parser, token):
|
|||
class ChartbeatTopNode(Node):
|
||||
def render(self, context):
|
||||
if is_internal_ip(context):
|
||||
return disable_html(INIT_CODE, "Chartbeat")
|
||||
return disable_html(INIT_CODE, 'Chartbeat')
|
||||
return INIT_CODE
|
||||
|
||||
|
||||
|
|
@ -67,7 +64,7 @@ def chartbeat_bottom(parser, token):
|
|||
"""
|
||||
Bottom Chartbeat template tag.
|
||||
|
||||
Render the bottom Javascript code for Chartbeat. You must supply
|
||||
Render the bottom JavaScript code for Chartbeat. You must supply
|
||||
your Chartbeat User ID (as a string) in the ``CHARTBEAT_USER_ID``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -79,8 +76,9 @@ 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")
|
||||
self.user_id = get_required_setting(
|
||||
'CHARTBEAT_USER_ID', USER_ID_RE, 'must be (a string containing) a number'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
config = {'uid': self.user_id}
|
||||
|
|
@ -109,6 +107,7 @@ def _get_domain(context):
|
|||
return
|
||||
elif getattr(settings, 'CHARTBEAT_AUTO_DOMAIN', True):
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
try:
|
||||
return Site.objects.get_current().domain
|
||||
except (ImproperlyConfigured, Site.DoesNotExist): # pylint: disable=E1101
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@
|
|||
Clickmap template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
CLICKMAP_TRACKER_ID_RE = re.compile(r'^\w+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var clickmapConfig = {tracker: '%(tracker_id)s', version:'2'};
|
||||
window.clickmapAsyncInit = function(){ __clickmap.init(clickmapConfig); };
|
||||
(function() { var _cmf = document.createElement('script'); _cmf.async = true;
|
||||
|
|
@ -33,7 +30,7 @@ def clickmap(parser, token):
|
|||
"""
|
||||
Clickmap tracker template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your clickmap tracker ID (as a string) in the ``CLICKMAP_TRACKER_ID``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -45,9 +42,11 @@ def clickmap(parser, token):
|
|||
|
||||
class ClickmapNode(Node):
|
||||
def __init__(self):
|
||||
self.tracker_id = get_required_setting('CLICKMAP_TRACKER_ID',
|
||||
CLICKMAP_TRACKER_ID_RE,
|
||||
"must be an alphanumeric string")
|
||||
self.tracker_id = get_required_setting(
|
||||
'CLICKMAP_TRACKER_ID',
|
||||
CLICKMAP_TRACKER_ID_RE,
|
||||
'must be an alphanumeric string',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = TRACKING_CODE % {'tracker_id': self.tracker_id}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@
|
|||
Clicky template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_identity, is_internal_ip, disable_html, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
SITE_ID_RE = re.compile(r'^\d+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var clicky = { log: function(){ return; }, goal: function(){ return; }};
|
||||
var clicky_site_ids = clicky_site_ids || [];
|
||||
clicky_site_ids.push(%(site_id)s);
|
||||
|
|
@ -39,7 +40,7 @@ def clicky(parser, token):
|
|||
"""
|
||||
Clicky tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your Clicky Site ID (as a string) in the ``CLICKY_SITE_ID``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -52,8 +53,8 @@ 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")
|
||||
'CLICKY_SITE_ID', SITE_ID_RE, 'must be a (string containing) a number'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
custom = {}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,17 @@
|
|||
Crazy Egg template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
||||
SETUP_CODE = '<script type="text/javascript" src="{placeholder_url}">' \
|
||||
'</script>'.\
|
||||
format(placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/'
|
||||
'%(account_nr_1)s/%(account_nr_2)s.js')
|
||||
SETUP_CODE = '<script src="{placeholder_url}"></script>'.format(
|
||||
placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/'
|
||||
'%(account_nr_1)s/%(account_nr_2)s.js'
|
||||
)
|
||||
USERVAR_CODE = "CE2.set(%(varnr)d, '%(value)s');"
|
||||
|
||||
|
||||
|
|
@ -27,7 +24,7 @@ def crazy_egg(parser, token):
|
|||
"""
|
||||
Crazy Egg tracking template tag.
|
||||
|
||||
Renders Javascript code to track page clicks. You must supply
|
||||
Renders JavaScript code to track page clicks. You must supply
|
||||
your Crazy Egg account number (as a string) in the
|
||||
``CRAZY_EGG_ACCOUNT_NUMBER`` setting.
|
||||
"""
|
||||
|
|
@ -41,7 +38,8 @@ class CrazyEggNode(Node):
|
|||
def __init__(self):
|
||||
self.account_nr = get_required_setting(
|
||||
'CRAZY_EGG_ACCOUNT_NUMBER',
|
||||
ACCOUNT_NUMBER_RE, "must be (a string containing) a number"
|
||||
ACCOUNT_NUMBER_RE,
|
||||
'must be (a string containing) a number',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
|
|
@ -52,12 +50,15 @@ class CrazyEggNode(Node):
|
|||
values = (context.get('crazy_egg_var%d' % i) for i in range(1, 6))
|
||||
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
|
||||
if params:
|
||||
js = " ".join(USERVAR_CODE % {
|
||||
'varnr': varnr,
|
||||
'value': value,
|
||||
} for (varnr, value) in params)
|
||||
html = '%s\n' \
|
||||
'<script type="text/javascript">%s</script>' % (html, js)
|
||||
js = ' '.join(
|
||||
USERVAR_CODE
|
||||
% {
|
||||
'varnr': varnr,
|
||||
'value': value,
|
||||
}
|
||||
for (varnr, value) in params
|
||||
)
|
||||
html = '%s\n<script>%s</script>' % (html, js)
|
||||
if is_internal_ip(context, 'CRAZY_EGG'):
|
||||
html = disable_html(html, 'Crazy Egg')
|
||||
return html
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
"""
|
||||
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
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
FACEBOOK_PIXEL_HEAD_CODE = """\
|
||||
<script>
|
||||
|
|
@ -62,11 +60,12 @@ 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",
|
||||
'must be (a string containing) a number',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
|
|
|
|||
|
|
@ -2,17 +2,15 @@
|
|||
Gaug.es template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_required_setting
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
SITE_ID_RE = re.compile(r'[\da-f]+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _gauges = _gauges || [];
|
||||
(function() {
|
||||
var t = document.createElement('script');
|
||||
|
|
@ -35,7 +33,7 @@ def gauges(parser, token):
|
|||
"""
|
||||
Gaug.es template tag.
|
||||
|
||||
Renders Javascript code to gaug.es testing. You must supply
|
||||
Renders JavaScript code to gaug.es testing. You must supply
|
||||
your Site ID account number in the ``GAUGES_SITE_ID``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -48,8 +46,8 @@ def gauges(parser, token):
|
|||
class GaugesNode(Node):
|
||||
def __init__(self):
|
||||
self.site_id = get_required_setting(
|
||||
'GAUGES_SITE_ID', SITE_ID_RE,
|
||||
"must be a string looking like 'XXXXXXX'")
|
||||
'GAUGES_SITE_ID', SITE_ID_RE, "must be a string looking like 'XXXXXXX'"
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = TRACKING_CODE % {'site_id': self.site_id}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ Google Analytics template tags and filters.
|
|||
DEPRECATED
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import decimal
|
||||
import re
|
||||
|
||||
|
|
@ -30,7 +28,7 @@ SCOPE_PAGE = 3
|
|||
|
||||
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '%(property_id)s']);
|
||||
|
|
@ -47,8 +45,9 @@ 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]);"
|
||||
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']);"
|
||||
|
|
@ -56,7 +55,10 @@ 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'")
|
||||
DISPLAY_ADVERTISING_SOURCE = (
|
||||
"'https://' : 'http://'",
|
||||
"'stats.g.doubleclick.net/dc.js'",
|
||||
)
|
||||
|
||||
ZEROPLACES = decimal.Decimal('0')
|
||||
TWOPLACES = decimal.Decimal('0.01')
|
||||
|
|
@ -69,7 +71,7 @@ def google_analytics(parser, token):
|
|||
"""
|
||||
Google Analytics tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your website property ID (as a string) in the
|
||||
``GOOGLE_ANALYTICS_PROPERTY_ID`` setting.
|
||||
"""
|
||||
|
|
@ -82,8 +84,10 @@ def google_analytics(parser, token):
|
|||
class GoogleAnalyticsNode(Node):
|
||||
def __init__(self):
|
||||
self.property_id = get_required_setting(
|
||||
'GOOGLE_ANALYTICS_PROPERTY_ID', PROPERTY_ID_RE,
|
||||
"must be a string looking like 'UA-XXXXXX-Y'")
|
||||
'GOOGLE_ANALYTICS_PROPERTY_ID',
|
||||
PROPERTY_ID_RE,
|
||||
"must be a string looking like 'UA-XXXXXX-Y'",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
commands = self._get_domain_commands(context)
|
||||
|
|
@ -96,7 +100,7 @@ class GoogleAnalyticsNode(Node):
|
|||
source = DEFAULT_SOURCE
|
||||
html = SETUP_CODE % {
|
||||
'property_id': self.property_id,
|
||||
'commands': " ".join(commands),
|
||||
'commands': ' '.join(commands),
|
||||
'source_scheme': source[0],
|
||||
'source_url': source[1],
|
||||
}
|
||||
|
|
@ -106,15 +110,17 @@ class GoogleAnalyticsNode(Node):
|
|||
|
||||
def _get_domain_commands(self, context):
|
||||
commands = []
|
||||
tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE',
|
||||
TRACK_SINGLE_DOMAIN)
|
||||
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")
|
||||
'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:
|
||||
|
|
@ -122,9 +128,7 @@ class GoogleAnalyticsNode(Node):
|
|||
return commands
|
||||
|
||||
def _get_custom_var_commands(self, context):
|
||||
values = (
|
||||
context.get('google_analytics_var%s' % i) for i in range(1, 6)
|
||||
)
|
||||
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 index, var in params:
|
||||
|
|
@ -134,12 +138,15 @@ class GoogleAnalyticsNode(Node):
|
|||
scope = var[2]
|
||||
except IndexError:
|
||||
scope = SCOPE_PAGE
|
||||
commands.append(CUSTOM_VAR_CODE % {
|
||||
'index': index,
|
||||
'name': name,
|
||||
'value': value,
|
||||
'scope': scope,
|
||||
})
|
||||
commands.append(
|
||||
CUSTOM_VAR_CODE
|
||||
% {
|
||||
'index': index,
|
||||
'name': name,
|
||||
'value': value,
|
||||
'scope': scope,
|
||||
}
|
||||
)
|
||||
return commands
|
||||
|
||||
def _get_other_commands(self, context):
|
||||
|
|
@ -154,29 +161,42 @@ class GoogleAnalyticsNode(Node):
|
|||
if sampleRate is not False:
|
||||
value = decimal.Decimal(sampleRate)
|
||||
if not 0 <= value <= 100:
|
||||
raise AnalyticalException("'GOOGLE_ANALYTICS_SAMPLE_RATE' must be >= 0 and <= 100")
|
||||
raise AnalyticalException(
|
||||
"'GOOGLE_ANALYTICS_SAMPLE_RATE' must be >= 0 and <= 100"
|
||||
)
|
||||
commands.append(SAMPLE_RATE_CODE % value.quantize(TWOPLACES))
|
||||
|
||||
siteSpeedSampleRate = getattr(settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False)
|
||||
siteSpeedSampleRate = getattr(
|
||||
settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False
|
||||
)
|
||||
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")
|
||||
"'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)
|
||||
sessionCookieTimeout = getattr(
|
||||
settings, 'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT', False
|
||||
)
|
||||
if sessionCookieTimeout is not False:
|
||||
value = decimal.Decimal(sessionCookieTimeout)
|
||||
if value < 0:
|
||||
raise AnalyticalException("'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT' must be >= 0")
|
||||
raise AnalyticalException(
|
||||
"'GOOGLE_ANALYTICS_SESSION_COOKIE_TIMEOUT' must be >= 0"
|
||||
)
|
||||
commands.append(SESSION_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES))
|
||||
|
||||
visitorCookieTimeout = getattr(settings, 'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT', False)
|
||||
visitorCookieTimeout = getattr(
|
||||
settings, 'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT', False
|
||||
)
|
||||
if visitorCookieTimeout is not False:
|
||||
value = decimal.Decimal(visitorCookieTimeout)
|
||||
if value < 0:
|
||||
raise AnalyticalException("'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT' must be >= 0")
|
||||
raise AnalyticalException(
|
||||
"'GOOGLE_ANALYTICS_VISITOR_COOKIE_TIMEOUT' must be >= 0"
|
||||
)
|
||||
commands.append(VISITOR_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES))
|
||||
return commands
|
||||
|
||||
|
|
|
|||
78
analytical/templatetags/google_analytics_gtag.py
Normal file
78
analytical/templatetags/google_analytics_gtag.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
"""
|
||||
Google Analytics template tags and filters, using the new gtag.js library.
|
||||
https://developers.google.com/tag-platform/gtagjs/reference
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
PROPERTY_ID_RE = re.compile(
|
||||
r'^UA-\d+-\d+$|^G-[a-zA-Z0-9]+$|^AW-[a-zA-Z0-9]+$|^DC-[a-zA-Z0-9]+$'
|
||||
)
|
||||
SETUP_CODE = """
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={property_id}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){{dataLayer.push(arguments);}}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '{property_id}', {custom_dimensions});
|
||||
</script>
|
||||
"""
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.tag
|
||||
def google_analytics_gtag(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_GTAG_PROPERTY_ID`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
return GoogleAnalyticsGTagNode()
|
||||
|
||||
|
||||
class GoogleAnalyticsGTagNode(Node):
|
||||
def __init__(self):
|
||||
self.property_id = get_required_setting(
|
||||
'GOOGLE_ANALYTICS_GTAG_PROPERTY_ID',
|
||||
PROPERTY_ID_RE,
|
||||
"""must be a string looking like one of these patterns
|
||||
('UA-XXXXXX-Y' , 'AW-XXXXXXXXXX',
|
||||
'G-XXXXXXXX', 'DC-XXXXXXXX')""",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
custom_dimensions = context.get('google_analytics_custom_dimensions', {})
|
||||
|
||||
identity = get_identity(context, prefix='google_analytics_gtag')
|
||||
if identity is not None:
|
||||
custom_dimensions['user_id'] = identity
|
||||
|
||||
html = SETUP_CODE.format(
|
||||
property_id=self.property_id,
|
||||
custom_dimensions=json.dumps(custom_dimensions),
|
||||
)
|
||||
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
||||
html = disable_html(html, 'Google Analytics')
|
||||
return html
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
GoogleAnalyticsGTagNode() # ensure properly configured
|
||||
add_node('head_top', GoogleAnalyticsGTagNode)
|
||||
|
|
@ -2,10 +2,9 @@
|
|||
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
|
||||
|
||||
|
|
@ -27,7 +26,7 @@ SETUP_CODE = """
|
|||
(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');
|
||||
}})(window,document,'script','{js_source}','ga');
|
||||
|
||||
ga('create', '{property_id}', 'auto', {create_fields});
|
||||
{commands}ga('send', 'pageview');
|
||||
|
|
@ -45,7 +44,7 @@ def google_analytics_js(parser, token):
|
|||
"""
|
||||
Google Analytics tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
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.
|
||||
"""
|
||||
|
|
@ -58,23 +57,35 @@ def google_analytics_js(parser, token):
|
|||
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'")
|
||||
'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)
|
||||
display_features = getattr(
|
||||
settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False
|
||||
)
|
||||
if display_features:
|
||||
commands.insert(0, REQUIRE_DISPLAY_FEATURES)
|
||||
|
||||
js_source = getattr(
|
||||
settings,
|
||||
'GOOGLE_ANALYTICS_JS_SOURCE',
|
||||
'https://www.google-analytics.com/analytics.js',
|
||||
)
|
||||
|
||||
html = SETUP_CODE.format(
|
||||
property_id=self.property_id,
|
||||
create_fields=json.dumps(create_fields),
|
||||
commands="".join(commands),
|
||||
commands=''.join(commands),
|
||||
js_source=js_source,
|
||||
)
|
||||
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
||||
html = disable_html(html, 'Google Analytics')
|
||||
|
|
@ -82,14 +93,17 @@ class GoogleAnalyticsJsNode(Node):
|
|||
|
||||
def _get_domain_fields(self, context):
|
||||
domain_fields = {}
|
||||
tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', TRACK_SINGLE_DOMAIN)
|
||||
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")
|
||||
'tracking multiple domains with Google Analytics requires a domain name'
|
||||
)
|
||||
domain_fields['legacyCookieDomain'] = domain
|
||||
if tracking_type == TRACK_MULTIPLE_DOMAINS:
|
||||
domain_fields['allowLinker'] = True
|
||||
|
|
@ -98,34 +112,39 @@ class GoogleAnalyticsJsNode(Node):
|
|||
def _get_other_create_fields(self, context):
|
||||
other_fields = {}
|
||||
|
||||
site_speed_sample_rate = getattr(settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False)
|
||||
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")
|
||||
"'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")
|
||||
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")
|
||||
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)
|
||||
)
|
||||
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:
|
||||
|
|
@ -134,11 +153,13 @@ class GoogleAnalyticsJsNode(Node):
|
|||
try:
|
||||
float(value)
|
||||
except ValueError:
|
||||
value = "'{}'".format(value)
|
||||
commands.append(CUSTOM_VAR_CODE.format(
|
||||
name=name,
|
||||
value=value,
|
||||
))
|
||||
value = f"'{value}'"
|
||||
commands.append(
|
||||
CUSTOM_VAR_CODE.format(
|
||||
name=name,
|
||||
value=value,
|
||||
)
|
||||
)
|
||||
return commands
|
||||
|
||||
def _get_other_commands(self, context):
|
||||
|
|
|
|||
|
|
@ -2,19 +2,20 @@
|
|||
GoSquared template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_identity, \
|
||||
is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
TOKEN_RE = re.compile(r'^\S+-\S+-\S+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var GoSquared={};
|
||||
%(config)s
|
||||
(function(w){
|
||||
|
|
@ -39,7 +40,7 @@ def gosquared(parser, token):
|
|||
"""
|
||||
GoSquared tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your GoSquared site token in the ``GOSQUARED_SITE_TOKEN`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
|
|
@ -51,8 +52,10 @@ def gosquared(parser, token):
|
|||
class GoSquaredNode(Node):
|
||||
def __init__(self):
|
||||
self.site_token = get_required_setting(
|
||||
'GOSQUARED_SITE_TOKEN', TOKEN_RE,
|
||||
"must be a string looking like XXX-XXXXXX-X")
|
||||
'GOSQUARED_SITE_TOKEN',
|
||||
TOKEN_RE,
|
||||
'must be a string looking like XXX-XXXXXX-X',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
configs = [TOKEN_CODE % self.site_token]
|
||||
|
|
|
|||
57
analytical/templatetags/heap.py
Normal file
57
analytical/templatetags/heap.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"""
|
||||
Heap template tags and filters.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
HEAP_TRACKER_ID_RE = re.compile(r'^\d+$')
|
||||
TRACKING_CODE = """
|
||||
<script>
|
||||
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[p[o]]=n(p[o])};
|
||||
heap.load("%(tracker_id)s");
|
||||
</script>
|
||||
|
||||
""" # noqa
|
||||
|
||||
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 heap(parser, token):
|
||||
"""
|
||||
Heap tracker template tag.
|
||||
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your heap tracker ID (as a string) in the ``HEAP_TRACKER_ID``
|
||||
setting.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return HeapNode()
|
||||
|
||||
|
||||
class HeapNode(Node):
|
||||
def __init__(self):
|
||||
self.tracker_id = get_required_setting(
|
||||
'HEAP_TRACKER_ID', HEAP_TRACKER_ID_RE, 'must be an numeric string'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = TRACKING_CODE % {'tracker_id': self.tracker_id}
|
||||
if is_internal_ip(context, 'HEAP'):
|
||||
html = disable_html(html, 'Heap')
|
||||
return html
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
HeapNode() # ensure properly configured
|
||||
add_node('head_bottom', HeapNode)
|
||||
|
|
@ -1,25 +1,23 @@
|
|||
"""
|
||||
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
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
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=');
|
||||
(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>
|
||||
"""
|
||||
|
||||
|
|
@ -43,12 +41,11 @@ def hotjar(parser, token):
|
|||
|
||||
|
||||
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",
|
||||
'must be (a string containing) a number',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
|
|
|
|||
|
|
@ -2,19 +2,16 @@
|
|||
HubSpot template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
PORTAL_ID_RE = re.compile(r'^\d+$')
|
||||
TRACKING_CODE = """
|
||||
<!-- Start of Async HubSpot Analytics Code -->
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
(function(d,s,i,r) {
|
||||
if (d.getElementById(i)){return;}
|
||||
var n=d.createElement(s),e=d.getElementsByTagName(s)[0];
|
||||
|
|
@ -33,7 +30,7 @@ def hubspot(parser, token):
|
|||
"""
|
||||
HubSpot tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your portal ID (as a string) in the ``HUBSPOT_PORTAL_ID`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
|
|
@ -44,8 +41,9 @@ 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}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@
|
|||
intercom.io template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
import time
|
||||
|
||||
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, \
|
||||
get_user_is_authenticated
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
get_user_from_context,
|
||||
get_user_is_authenticated,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
APP_ID_RE = re.compile(r'[\da-z]+$')
|
||||
TRACKING_CODE = """
|
||||
|
|
@ -29,14 +30,6 @@ TRACKING_CODE = """
|
|||
register = Library()
|
||||
|
||||
|
||||
def _timestamp(when):
|
||||
"""
|
||||
Python 2 compatibility for `datetime.timestamp()`.
|
||||
"""
|
||||
return (time.mktime(when.timetuple()) if sys.version_info < (3,) else
|
||||
when.timestamp())
|
||||
|
||||
|
||||
def _hashable_bytes(data):
|
||||
"""
|
||||
Coerce strings to hashable bytes.
|
||||
|
|
@ -70,7 +63,7 @@ def intercom(parser, token):
|
|||
"""
|
||||
Intercom.io template tag.
|
||||
|
||||
Renders Javascript code to intercom.io testing. You must supply
|
||||
Renders JavaScript code to intercom.io testing. You must supply
|
||||
your APP ID account number in the ``INTERCOM_APP_ID``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -83,8 +76,8 @@ def intercom(parser, token):
|
|||
class IntercomNode(Node):
|
||||
def __init__(self):
|
||||
self.app_id = get_required_setting(
|
||||
'INTERCOM_APP_ID', APP_ID_RE,
|
||||
"must be a string looking like 'XXXXXXX'")
|
||||
'INTERCOM_APP_ID', APP_ID_RE, "must be a string looking like 'XXXXXXX'"
|
||||
)
|
||||
|
||||
def _identify(self, user):
|
||||
name = user.get_full_name()
|
||||
|
|
@ -102,14 +95,13 @@ class IntercomNode(Node):
|
|||
user = get_user_from_context(context)
|
||||
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)
|
||||
params['name'] = get_identity(context, 'intercom', self._identify, user)
|
||||
if 'email' not in params and user.email:
|
||||
params['email'] = user.email
|
||||
|
||||
params.setdefault('user_id', user.pk)
|
||||
|
||||
params['created_at'] = int(_timestamp(user.date_joined))
|
||||
params['created_at'] = int(user.date_joined.timestamp())
|
||||
else:
|
||||
params['created_at'] = None
|
||||
|
||||
|
|
@ -127,10 +119,8 @@ class IntercomNode(Node):
|
|||
|
||||
def render(self, context):
|
||||
params = self._get_custom_attrs(context)
|
||||
params["app_id"] = self.app_id
|
||||
html = TRACKING_CODE % {
|
||||
"settings_json": json.dumps(params, sort_keys=True)
|
||||
}
|
||||
params['app_id'] = self.app_id
|
||||
html = TRACKING_CODE % {'settings_json': json.dumps(params, sort_keys=True)}
|
||||
|
||||
if is_internal_ip(context, 'INTERCOM'):
|
||||
html = disable_html(html, 'Intercom')
|
||||
|
|
|
|||
|
|
@ -2,20 +2,17 @@
|
|||
KISSinsights template tags.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_identity, get_required_setting
|
||||
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
||||
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>
|
||||
<script>var _kiq = _kiq || []; %(commands)s</script>
|
||||
<script 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]);"
|
||||
|
|
@ -30,7 +27,7 @@ def kiss_insights(parser, token):
|
|||
"""
|
||||
KISSinsights set-up template tag.
|
||||
|
||||
Renders Javascript code to set-up surveys. You must supply
|
||||
Renders JavaScript code to set-up surveys. You must supply
|
||||
your account number and site code in the
|
||||
``KISS_INSIGHTS_ACCOUNT_NUMBER`` and ``KISS_INSIGHTS_SITE_CODE``
|
||||
settings.
|
||||
|
|
@ -44,11 +41,15 @@ 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")
|
||||
'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_SITE_CODE',
|
||||
SITE_CODE_RE,
|
||||
'must be a string containing three characters',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
commands = []
|
||||
|
|
@ -62,7 +63,7 @@ class KissInsightsNode(Node):
|
|||
html = SETUP_CODE % {
|
||||
'account_number': self.account_number,
|
||||
'site_code': self.site_code,
|
||||
'commands': " ".join(commands),
|
||||
'commands': ' '.join(commands),
|
||||
}
|
||||
return html
|
||||
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@
|
|||
KISSmetrics template tags.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _kmq = _kmq || [];
|
||||
%(commands)s
|
||||
function _kms(u){
|
||||
|
|
@ -49,7 +50,7 @@ def kiss_metrics(parser, token):
|
|||
"""
|
||||
KISSinsights tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your KISSmetrics API key in the ``KISS_METRICS_API_KEY``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -62,8 +63,10 @@ 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")
|
||||
'KISS_METRICS_API_KEY',
|
||||
API_KEY_RE,
|
||||
'must be a string containing a 40-digit hexadecimal number',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
commands = []
|
||||
|
|
@ -78,22 +81,28 @@ 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),
|
||||
})
|
||||
commands.append(
|
||||
PROPERTY_CODE
|
||||
% {
|
||||
'properties': json.dumps(properties, sort_keys=True),
|
||||
}
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
html = TRACKING_CODE % {
|
||||
'api_key': self.api_key,
|
||||
'commands': " ".join(commands),
|
||||
'commands': ' '.join(commands),
|
||||
}
|
||||
if is_internal_ip(context, 'KISS_METRICS'):
|
||||
html = disable_html(html, 'KISSmetrics')
|
||||
|
|
|
|||
60
analytical/templatetags/luckyorange.py
Normal file
60
analytical/templatetags/luckyorange.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
Lucky Orange template tags and filters.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
LUCKYORANGE_TRACKING_CODE = """\
|
||||
<script type='text/javascript'>
|
||||
window.__lo_site_id = %(LUCKYORANGE_SITE_ID)s;
|
||||
(function() {
|
||||
var wa = document.createElement('script'); wa.type = 'text/javascript'; wa.async = true;
|
||||
wa.src = 'https://d10lpsik1i8c69.cloudfront.net/w.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(wa, s);
|
||||
})();
|
||||
</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 luckyorange(parser, token):
|
||||
"""
|
||||
Lucky Orange template tag.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return LuckyOrangeNode()
|
||||
|
||||
|
||||
class LuckyOrangeNode(Node):
|
||||
def __init__(self):
|
||||
self.site_id = get_required_setting(
|
||||
'LUCKYORANGE_SITE_ID',
|
||||
re.compile(r'^\d+$'),
|
||||
'must be (a string containing) a number',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = LUCKYORANGE_TRACKING_CODE % {'LUCKYORANGE_SITE_ID': self.site_id}
|
||||
if is_internal_ip(context, 'LUCKYORANGE'):
|
||||
return disable_html(html, 'Lucky Orange')
|
||||
else:
|
||||
return html
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
# ensure properly configured
|
||||
LuckyOrangeNode()
|
||||
add_node('head_bottom', LuckyOrangeNode)
|
||||
|
|
@ -2,18 +2,19 @@
|
|||
Matomo template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
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,
|
||||
get_required_setting, get_identity)
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
# domain name (characters separated by a dot), optional port, optional URI path, no slash
|
||||
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$')
|
||||
|
|
@ -22,7 +23,7 @@ DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)
|
|||
SITEID_RE = re.compile(r'^\d+$')
|
||||
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _paq = window._paq || [];
|
||||
%(variables)s
|
||||
%(commands)s
|
||||
|
|
@ -36,10 +37,12 @@ TRACKING_CODE = """
|
|||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img src="//%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript>
|
||||
<noscript><p><img src="//%(url)s/matomo.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
|
||||
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']);"
|
||||
|
||||
|
|
@ -80,7 +83,7 @@ def matomo(parser, token):
|
|||
"""
|
||||
Matomo tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your Matomo domain (plus optional URI path), and tracked site ID
|
||||
in the ``MATOMO_DOMAIN_PATH`` and the ``MATOMO_SITE_ID`` setting.
|
||||
|
||||
|
|
@ -98,23 +101,27 @@ def matomo(parser, token):
|
|||
|
||||
class MatomoNode(Node):
|
||||
def __init__(self):
|
||||
self.domain_path = \
|
||||
get_required_setting('MATOMO_DOMAIN_PATH', DOMAINPATH_RE,
|
||||
"must be a domain name, optionally followed "
|
||||
"by an URI path, no trailing slash (e.g. "
|
||||
"matomo.example.com or my.matomo.server/path)")
|
||||
self.site_id = \
|
||||
get_required_setting('MATOMO_SITE_ID', SITEID_RE,
|
||||
"must be a (string containing a) number")
|
||||
self.domain_path = get_required_setting(
|
||||
'MATOMO_DOMAIN_PATH',
|
||||
DOMAINPATH_RE,
|
||||
'must be a domain name, optionally followed '
|
||||
'by an URI path, no trailing slash (e.g. '
|
||||
'matomo.example.com or my.matomo.server/path)',
|
||||
)
|
||||
self.site_id = get_required_setting(
|
||||
'MATOMO_SITE_ID', SITEID_RE, 'must be a (string containing a) number'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
custom_variables = context.get('matomo_vars', ())
|
||||
|
||||
complete_variables = (var if len(var) >= 4 else var + (DEFAULT_SCOPE,)
|
||||
for var in custom_variables)
|
||||
complete_variables = (
|
||||
var if len(var) >= 4 else var + (DEFAULT_SCOPE,) for var in custom_variables
|
||||
)
|
||||
|
||||
variables_code = (VARIABLE_CODE % MatomoVar(*var)._asdict()
|
||||
for var in complete_variables)
|
||||
variables_code = (
|
||||
VARIABLE_CODE % MatomoVar(*var)._asdict() for var in complete_variables
|
||||
)
|
||||
|
||||
commands = []
|
||||
if getattr(settings, 'MATOMO_DISABLE_COOKIES', False):
|
||||
|
|
@ -125,15 +132,15 @@ class MatomoNode(Node):
|
|||
|
||||
userid = get_identity(context, 'matomo')
|
||||
if userid is not None:
|
||||
variables_code = chain(variables_code, (
|
||||
IDENTITY_CODE % {'userid': userid},
|
||||
))
|
||||
variables_code = chain(
|
||||
variables_code, (IDENTITY_CODE % {'userid': userid},)
|
||||
)
|
||||
|
||||
html = TRACKING_CODE % {
|
||||
'url': self.domain_path,
|
||||
'siteid': self.site_id,
|
||||
'variables': '\n '.join(variables_code),
|
||||
'commands': '\n '.join(commands)
|
||||
'commands': '\n '.join(commands),
|
||||
}
|
||||
if is_internal_ip(context, 'MATOMO'):
|
||||
html = disable_html(html, 'Matomo')
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@
|
|||
Mixpanel template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
MIXPANEL_API_TOKEN_RE = re.compile(r'^[0-9a-f]{32}$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
|
||||
<script>(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==
|
||||
typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,
|
||||
e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
|
||||
mixpanel.init('%(token)s');
|
||||
|
|
@ -24,7 +25,7 @@ e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
|
|||
</script>
|
||||
""" # noqa
|
||||
IDENTIFY_CODE = "mixpanel.identify('%s');"
|
||||
IDENTIFY_PROPERTIES = "mixpanel.people.set(%s);"
|
||||
IDENTIFY_PROPERTIES = 'mixpanel.people.set(%s);'
|
||||
EVENT_CODE = "mixpanel.track('%(name)s', %(properties)s);"
|
||||
EVENT_CONTEXT_KEY = 'mixpanel_event'
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ def mixpanel(parser, token):
|
|||
"""
|
||||
Mixpanel tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your Mixpanel token in the ``MIXPANEL_API_TOKEN`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
|
|
@ -48,29 +49,38 @@ def mixpanel(parser, token):
|
|||
class MixpanelNode(Node):
|
||||
def __init__(self):
|
||||
self._token = get_required_setting(
|
||||
'MIXPANEL_API_TOKEN', MIXPANEL_API_TOKEN_RE,
|
||||
"must be a string containing a 32-digit hexadecimal number")
|
||||
'MIXPANEL_API_TOKEN',
|
||||
MIXPANEL_API_TOKEN_RE,
|
||||
'must be a string containing a 32-digit hexadecimal number',
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
commands = []
|
||||
identity = get_identity(context, 'mixpanel')
|
||||
if identity is not None:
|
||||
if isinstance(identity, dict):
|
||||
commands.append(IDENTIFY_CODE % identity.get('id', identity.get('username')))
|
||||
commands.append(IDENTIFY_PROPERTIES % json.dumps(identity, sort_keys=True))
|
||||
commands.append(
|
||||
IDENTIFY_CODE % identity.get('id', identity.get('username'))
|
||||
)
|
||||
commands.append(
|
||||
IDENTIFY_PROPERTIES % json.dumps(identity, sort_keys=True)
|
||||
)
|
||||
else:
|
||||
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),
|
||||
'commands': ' '.join(commands),
|
||||
}
|
||||
if is_internal_ip(context, 'MIXPANEL'):
|
||||
html = disable_html(html, 'Mixpanel')
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
Olark template tags.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
|
|
@ -11,7 +9,6 @@ from django.template import Library, Node, TemplateSyntaxError
|
|||
|
||||
from analytical.utils import get_identity, get_required_setting
|
||||
|
||||
|
||||
SITE_ID_RE = re.compile(r'^\d+-\d+-\d+-\d+$')
|
||||
SETUP_CODE = """
|
||||
<script type='text/javascript'>
|
||||
|
|
@ -27,16 +24,29 @@ EMAIL_CODE = "olark('api.visitor.updateEmailAddress', {{emailAddress: '{0}'}});"
|
|||
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_CODE = 'olark.configure(\'locale.%(key)s\', "%(msg)s");'
|
||||
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",
|
||||
'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()
|
||||
|
|
@ -47,7 +57,7 @@ def olark(parser, token):
|
|||
"""
|
||||
Olark set-up template tag.
|
||||
|
||||
Renders Javascript code to set-up Olark chat. You must supply
|
||||
Renders JavaScript code to set-up Olark chat. You must supply
|
||||
your site ID in the ``OLARK_SITE_ID`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
|
|
@ -59,8 +69,10 @@ 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'")
|
||||
'OLARK_SITE_ID',
|
||||
SITE_ID_RE,
|
||||
"must be a string looking like 'XXXX-XXX-XX-XXXX'",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
extra_code = []
|
||||
|
|
@ -79,21 +91,22 @@ 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),
|
||||
'extra_code': ' '.join(extra_code),
|
||||
}
|
||||
return html
|
||||
|
||||
def _get_nickname(self, user):
|
||||
name = user.get_full_name()
|
||||
if name:
|
||||
return "%s (%s)" % (name, user.username)
|
||||
return '%s (%s)' % (name, user.username)
|
||||
else:
|
||||
return user.username
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@
|
|||
Optimizely template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
||||
SETUP_CODE = """<script src="//cdn.optimizely.com/js/%(account_number)s.js"></script>"""
|
||||
|
|
@ -23,7 +20,7 @@ def optimizely(parser, token):
|
|||
"""
|
||||
Optimizely template tag.
|
||||
|
||||
Renders Javascript code to set-up A/B testing. You must supply
|
||||
Renders JavaScript code to set-up A/B testing. You must supply
|
||||
your Optimizely account number in the ``OPTIMIZELY_ACCOUNT_NUMBER``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -36,8 +33,10 @@ def optimizely(parser, token):
|
|||
class OptimizelyNode(Node):
|
||||
def __init__(self):
|
||||
self.account_number = get_required_setting(
|
||||
'OPTIMIZELY_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
||||
"must be a string looking like 'XXXXXXX'")
|
||||
'OPTIMIZELY_ACCOUNT_NUMBER',
|
||||
ACCOUNT_NUMBER_RE,
|
||||
"must be a string looking like 'XXXXXXX'",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = SETUP_CODE % {'account_number': self.account_number}
|
||||
|
|
|
|||
|
|
@ -2,30 +2,31 @@
|
|||
Performable template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
API_KEY_RE = re.compile(r'^\w+$')
|
||||
SETUP_CODE = """
|
||||
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/pax/%(api_key)s.js" type="text/javascript"></script>
|
||||
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/pax/%(api_key)s.js"></script>
|
||||
""" # noqa
|
||||
IDENTIFY_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _paq = _paq || [];
|
||||
_paq.push(["identify", {identity: "%s"}]);
|
||||
</script>
|
||||
"""
|
||||
EMBED_CODE = """
|
||||
<script type="text/javascript" src="//d1nu2rn22elx8m.cloudfront.net/performable/embed/page.js"></script>
|
||||
<script type="text/javascript">
|
||||
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/embed/page.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var $f = new PerformableEmbed();
|
||||
$f.initialize({'host': '%(hostname)s', 'page': '%(page_id)s'});
|
||||
|
|
@ -42,7 +43,7 @@ def performable(parser, token):
|
|||
"""
|
||||
Performable template tag.
|
||||
|
||||
Renders Javascript code to set-up Performable tracking. You must
|
||||
Renders JavaScript code to set-up Performable tracking. You must
|
||||
supply your Performable API key in the ``PERFORMABLE_API_KEY``
|
||||
setting.
|
||||
"""
|
||||
|
|
@ -55,14 +56,14 @@ 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'")
|
||||
'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}
|
||||
identity = get_identity(context, 'performable')
|
||||
if identity is not None:
|
||||
html = "%s%s" % (IDENTIFY_CODE % identity, html)
|
||||
html = '%s%s' % (IDENTIFY_CODE % identity, html)
|
||||
if is_internal_ip(context, 'PERFORMABLE'):
|
||||
html = disable_html(html, 'Performable')
|
||||
return html
|
||||
|
|
@ -73,10 +74,13 @@ 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):
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
"""
|
||||
Piwik template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
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,
|
||||
get_required_setting, get_identity)
|
||||
|
||||
import warnings
|
||||
warnings.warn('The Piwik module is deprecated; use the Matomo module.', DeprecationWarning)
|
||||
|
||||
# domain name (characters separated by a dot), optional port, optional URI path, no slash
|
||||
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$')
|
||||
|
||||
# numeric ID
|
||||
SITEID_RE = re.compile(r'^\d+$')
|
||||
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
%(variables)s
|
||||
%(commands)s
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
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.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<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']);"
|
||||
|
||||
GIVE_CONSENT_CLASS = "piwik_give_consent"
|
||||
REMOVE_CONSENT_CLASS = "piwik_remove_consent"
|
||||
ASK_FOR_CONSENT_CODE = """
|
||||
_paq.push(['requireConsent']);
|
||||
|
||||
var elements = document.getElementsByClassName("{}");
|
||||
for (var i = 0; i < elements.length; i++) {{
|
||||
elements[i].addEventListener("click",
|
||||
function () {{
|
||||
_paq.push(["forgetConsentGiven"]);
|
||||
}}
|
||||
);
|
||||
}}
|
||||
|
||||
var elements = document.getElementsByClassName("{}");
|
||||
for (var i = 0; i < elements.length; i++) {{
|
||||
elements[i].addEventListener("click",
|
||||
function () {{
|
||||
_paq.push(["rememberConsentGiven"]);
|
||||
}}
|
||||
);
|
||||
}}
|
||||
""".format(REMOVE_CONSENT_CLASS, GIVE_CONSENT_CLASS)
|
||||
|
||||
DEFAULT_SCOPE = 'page'
|
||||
|
||||
PiwikVar = namedtuple('PiwikVar', ('index', 'name', 'value', 'scope'))
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
@register.tag
|
||||
def piwik(parser, token):
|
||||
"""
|
||||
Piwik tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
your Piwik domain (plus optional URI path), and tracked site ID
|
||||
in the ``PIWIK_DOMAIN_PATH`` and the ``PIWIK_SITE_ID`` setting.
|
||||
|
||||
Custom variables can be passed in the ``piwik_vars`` context
|
||||
variable. It is an iterable of custom variables as tuples like:
|
||||
``(index, name, value[, scope])`` where scope may be ``'page'``
|
||||
(default) or ``'visit'``. Index should be an integer and the
|
||||
other parameters should be strings.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
return PiwikNode()
|
||||
|
||||
|
||||
class PiwikNode(Node):
|
||||
def __init__(self):
|
||||
self.domain_path = \
|
||||
get_required_setting('PIWIK_DOMAIN_PATH', DOMAINPATH_RE,
|
||||
"must be a domain name, optionally followed "
|
||||
"by an URI path, no trailing slash (e.g. "
|
||||
"piwik.example.com or my.piwik.server/path)")
|
||||
self.site_id = \
|
||||
get_required_setting('PIWIK_SITE_ID', SITEID_RE,
|
||||
"must be a (string containing a) number")
|
||||
|
||||
def render(self, context):
|
||||
custom_variables = context.get('piwik_vars', ())
|
||||
|
||||
complete_variables = (var if len(var) >= 4 else var + (DEFAULT_SCOPE,)
|
||||
for var in custom_variables)
|
||||
|
||||
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)
|
||||
|
||||
if getattr(settings, 'PIWIK_ASK_FOR_CONSENT', False):
|
||||
commands.append(ASK_FOR_CONSENT_CODE)
|
||||
|
||||
userid = get_identity(context, 'piwik')
|
||||
if userid is not None:
|
||||
variables_code = chain(variables_code, (
|
||||
IDENTITY_CODE % {'userid': userid},
|
||||
))
|
||||
|
||||
html = TRACKING_CODE % {
|
||||
'url': self.domain_path,
|
||||
'siteid': self.site_id,
|
||||
'variables': '\n '.join(variables_code),
|
||||
'commands': '\n '.join(commands)
|
||||
}
|
||||
if is_internal_ip(context, 'PIWIK'):
|
||||
html = disable_html(html, 'Piwik')
|
||||
return html
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
PiwikNode() # ensure properly configured
|
||||
add_node('body_bottom', PiwikNode)
|
||||
|
|
@ -2,19 +2,15 @@
|
|||
Rating@Mail.ru template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import is_internal_ip, disable_html, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
COUNTER_ID_RE = re.compile(r'^\d{7}$')
|
||||
COUNTER_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _tmr = window._tmr || (window._tmr = []);
|
||||
_tmr.push({id: "%(counter_id)s", type: "pageView", start: (new Date()).getTime()});
|
||||
(function (d, w, id) {
|
||||
|
|
@ -39,7 +35,7 @@ def rating_mailru(parser, token):
|
|||
"""
|
||||
Rating@Mail.ru counter template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your website counter ID (as a string) in the
|
||||
``RATING_MAILRU_COUNTER_ID`` setting.
|
||||
"""
|
||||
|
|
@ -52,8 +48,10 @@ 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 % {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
SnapEngage template tags.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
|
|
@ -26,17 +24,21 @@ FORM_POSITION_TOP_RIGHT = 'tr'
|
|||
FORM_POSITION_BOTTOM_LEFT = 'bl'
|
||||
FORM_POSITION_BOTTOM_RIGHT = 'br'
|
||||
|
||||
WIDGET_ID_RE = re.compile(r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$')
|
||||
WIDGET_ID_RE = re.compile(
|
||||
r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$'
|
||||
)
|
||||
SETUP_CODE = """
|
||||
<script type="text/javascript">
|
||||
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">
|
||||
<script>
|
||||
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>
|
||||
%(settings_code)s
|
||||
</script>
|
||||
""" # noqa
|
||||
DOMAIN_CODE = 'SnapABug.setDomain("%s");'
|
||||
SECURE_CONNECTION_CODE = 'SnapABug.setSecureConnexion();'
|
||||
INIT_CODE = 'SnapABug.init("%s");'
|
||||
ADDBUTTON_CODE = 'SnapABug.addButton("%(id)s","%(location)s","%(offset)s"%(dynamic_tail)s);'
|
||||
ADDBUTTON_CODE = (
|
||||
'SnapABug.addButton("%(id)s","%(location)s","%(offset)s"%(dynamic_tail)s);'
|
||||
)
|
||||
SETBUTTON_CODE = 'SnapABug.setButton("%s");'
|
||||
SETEMAIL_CODE = 'SnapABug.setUserEmail("%s"%s);'
|
||||
SETLOCALE_CODE = 'SnapABug.setLocale("%s");'
|
||||
|
|
@ -57,7 +59,7 @@ def snapengage(parser, token):
|
|||
"""
|
||||
SnapEngage set-up template tag.
|
||||
|
||||
Renders Javascript code to set-up SnapEngage chat. You must supply
|
||||
Renders JavaScript code to set-up SnapEngage chat. You must supply
|
||||
your widget ID in the ``SNAPENGAGE_WIDGET_ID`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
|
|
@ -69,21 +71,24 @@ 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'")
|
||||
'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')
|
||||
domain = self._get_setting(context, '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)
|
||||
secure_connection = self._get_setting(
|
||||
context,
|
||||
'snapengage_secure_connection',
|
||||
'SNAPENGAGE_SECURE_CONNECTION',
|
||||
False,
|
||||
)
|
||||
if secure_connection:
|
||||
settings_code.append(SECURE_CONNECTION_CODE)
|
||||
|
||||
|
|
@ -91,61 +96,70 @@ class SnapEngageNode(Node):
|
|||
if email is None:
|
||||
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):
|
||||
if self._get_setting(
|
||||
context, 'snapengage_readonly_email', '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')
|
||||
locale = self._get_setting(context, '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')
|
||||
form_position = self._get_setting(
|
||||
context, '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')
|
||||
form_top_position = self._get_setting(
|
||||
context, '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)
|
||||
show_offline = self._get_setting(
|
||||
context, 'snapengage_show_offline', 'SNAPENGAGE_SHOW_OFFLINE', True
|
||||
)
|
||||
if not show_offline:
|
||||
settings_code.append(DISABLE_OFFLINE_CODE)
|
||||
|
||||
screenshots = self._get_setting(context, 'snapengage_screenshots',
|
||||
'SNAPENGAGE_SCREENSHOTS', True)
|
||||
screenshots = self._get_setting(
|
||||
context, 'snapengage_screenshots', '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)
|
||||
offline_screenshots = self._get_setting(
|
||||
context,
|
||||
'snapengage_offline_screenshots',
|
||||
'SNAPENGAGE_OFFLINE_SCREENSHOTS',
|
||||
True,
|
||||
)
|
||||
if not offline_screenshots:
|
||||
settings_code.append(DISABLE_OFFLINE_SCREENSHOT_CODE)
|
||||
|
||||
if not context.get('snapengage_proactive_chat', True):
|
||||
settings_code.append(DISABLE_PROACTIVE_CHAT_CODE)
|
||||
|
||||
sounds = self._get_setting(context, 'snapengage_sounds',
|
||||
'SNAPENGAGE_SOUNDS', True)
|
||||
sounds = self._get_setting(
|
||||
context, 'snapengage_sounds', 'SNAPENGAGE_SOUNDS', True
|
||||
)
|
||||
if not sounds:
|
||||
settings_code.append(DISABLE_SOUNDS_CODE)
|
||||
|
||||
button_effect = self._get_setting(context, 'snapengage_button_effect',
|
||||
'SNAPENGAGE_BUTTON_EFFECT')
|
||||
button_effect = self._get_setting(
|
||||
context, '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)
|
||||
button = self._get_setting(
|
||||
context, 'snapengage_button', 'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT
|
||||
)
|
||||
if button == BUTTON_STYLE_NONE:
|
||||
settings_code.append(INIT_CODE % self.widget_id)
|
||||
else:
|
||||
|
|
@ -154,21 +168,28 @@ class SnapEngageNode(Node):
|
|||
settings_code.append(SETBUTTON_CODE % button)
|
||||
button_location = self._get_setting(
|
||||
context,
|
||||
'snapengage_button_location', 'SNAPENGAGE_BUTTON_LOCATION',
|
||||
BUTTON_LOCATION_LEFT)
|
||||
'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 '',
|
||||
})
|
||||
'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),
|
||||
'settings_code': ' '.join(settings_code),
|
||||
}
|
||||
return html
|
||||
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@
|
|||
Spring Metrics template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_identity, is_internal_ip, disable_html, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import (
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
is_internal_ip,
|
||||
)
|
||||
|
||||
TRACKING_ID_RE = re.compile(r'^[0-9a-f]+$')
|
||||
TRACKING_CODE = """
|
||||
|
|
@ -39,7 +40,7 @@ def spring_metrics(parser, token):
|
|||
"""
|
||||
Spring Metrics tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your Spring Metrics Tracking ID in the
|
||||
``SPRING_METRICS_TRACKING_ID`` setting.
|
||||
"""
|
||||
|
|
@ -52,8 +53,8 @@ def spring_metrics(parser, token):
|
|||
class SpringMetricsNode(Node):
|
||||
def __init__(self):
|
||||
self.tracking_id = get_required_setting(
|
||||
'SPRING_METRICS_TRACKING_ID',
|
||||
TRACKING_ID_RE, "must be a hexadecimal string")
|
||||
'SPRING_METRICS_TRACKING_ID', TRACKING_ID_RE, 'must be a hexadecimal string'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
custom = {}
|
||||
|
|
@ -62,8 +63,7 @@ class SpringMetricsNode(Node):
|
|||
if var.startswith('spring_metrics_'):
|
||||
custom[var[15:]] = val
|
||||
if 'email' not in custom:
|
||||
identity = get_identity(context, 'spring_metrics',
|
||||
lambda u: u.email)
|
||||
identity = get_identity(context, 'spring_metrics', lambda u: u.email)
|
||||
if identity is not None:
|
||||
custom['email'] = identity
|
||||
|
||||
|
|
@ -80,9 +80,11 @@ class SpringMetricsNode(Node):
|
|||
convert = params.pop('convert', None)
|
||||
if convert is not None:
|
||||
commands.append("_springMetq.push(['convert', '%s'])" % convert)
|
||||
commands.extend("_springMetq.push(['setdata', {'%s': '%s'}]);"
|
||||
% (var, val) for var, val in params.items())
|
||||
return " ".join(commands)
|
||||
commands.extend(
|
||||
"_springMetq.push(['setdata', {'%s': '%s'}]);" % (var, val)
|
||||
for var, val in params.items()
|
||||
)
|
||||
return ' '.join(commands)
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
|
|
|
|||
|
|
@ -2,19 +2,17 @@
|
|||
UserVoice template tags.
|
||||
"""
|
||||
|
||||
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 get_required_setting, get_identity
|
||||
|
||||
from analytical.utils import get_identity, get_required_setting
|
||||
|
||||
WIDGET_KEY_RE = re.compile(r'^[a-zA-Z0-9]*$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
|
||||
UserVoice=window.UserVoice||[];(function(){
|
||||
var uv=document.createElement('script');uv.type='text/javascript';
|
||||
|
|
@ -37,7 +35,7 @@ def uservoice(parser, token):
|
|||
"""
|
||||
UserVoice tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your UserVoice Widget Key in the ``USERVOICE_WIDGET_KEY``
|
||||
setting or the ``uservoice_widget_key`` template context variable.
|
||||
"""
|
||||
|
|
@ -50,7 +48,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")
|
||||
'USERVOICE_WIDGET_KEY', WIDGET_KEY_RE, 'must be an alphanumeric string'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
widget_key = context.get('uservoice_widget_key')
|
||||
|
|
@ -67,13 +66,16 @@ class UserVoiceNode(Node):
|
|||
if identity:
|
||||
identity = IDENTITY % {'options': json.dumps(identity, sort_keys=True)}
|
||||
|
||||
trigger = context.get('uservoice_add_trigger',
|
||||
getattr(settings, 'USERVOICE_ADD_TRIGGER', True))
|
||||
trigger = context.get(
|
||||
'uservoice_add_trigger', getattr(settings, 'USERVOICE_ADD_TRIGGER', True)
|
||||
)
|
||||
|
||||
html = TRACKING_CODE % {'widget_key': widget_key,
|
||||
'options': json.dumps(options, sort_keys=True),
|
||||
'trigger': TRIGGER if trigger else '',
|
||||
'identity': identity if identity else ''}
|
||||
html = TRACKING_CODE % {
|
||||
'widget_key': widget_key,
|
||||
'options': json.dumps(options, sort_keys=True),
|
||||
'trigger': TRIGGER if trigger else '',
|
||||
'identity': identity if identity else '',
|
||||
}
|
||||
return html
|
||||
|
||||
def _identify(self, user):
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
Woopra template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import re
|
||||
from contextlib import suppress
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import (
|
||||
AnalyticalException,
|
||||
disable_html,
|
||||
get_identity,
|
||||
get_required_setting,
|
||||
|
|
@ -21,7 +21,7 @@ from analytical.utils import (
|
|||
|
||||
DOMAIN_RE = re.compile(r'^\S+$')
|
||||
TRACKING_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var woo_settings = %(settings)s;
|
||||
var woo_visitor = %(visitor)s;
|
||||
!function(){var a,b,c,d=window,e=document,f=arguments,g="script",h=["config","track","trackForm","trackClick","identify","visit","push","call"],i=function(){var a,b=this,c=function(a){b[a]=function(){return b._e.push([a].concat(Array.prototype.slice.call(arguments,0))),b}};for(b._e=[],a=0;a<h.length;a++)c(h[a])};for(d.__woo=d.__woo||{},a=0;a<f.length;a++)d.__woo[f[a]]=d[f[a]]=d[f[a]]||new i;b=e.createElement(g),b.async=1,b.src="//static.woopra.com/js/w.js",c=e.getElementsByTagName(g)[0],c.parentNode.insertBefore(b,c)}("woopra");
|
||||
|
|
@ -39,7 +39,7 @@ def woopra(parser, token):
|
|||
"""
|
||||
Woopra tracking template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your Woopra domain in the ``WOOPRA_DOMAIN`` setting.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
|
|
@ -51,8 +51,8 @@ def woopra(parser, token):
|
|||
class WoopraNode(Node):
|
||||
def __init__(self):
|
||||
self.domain = get_required_setting(
|
||||
'WOOPRA_DOMAIN', DOMAIN_RE,
|
||||
"must be a domain name")
|
||||
'WOOPRA_DOMAIN', DOMAIN_RE, 'must be a domain name'
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
settings = self._get_settings(context)
|
||||
|
|
@ -68,10 +68,42 @@ class WoopraNode(Node):
|
|||
|
||||
def _get_settings(self, context):
|
||||
variables = {'domain': self.domain}
|
||||
try:
|
||||
variables['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT)
|
||||
except AttributeError:
|
||||
pass
|
||||
woopra_int_settings = {
|
||||
'idle_timeout': 'WOOPRA_IDLE_TIMEOUT',
|
||||
}
|
||||
woopra_str_settings = {
|
||||
'cookie_name': 'WOOPRA_COOKIE_NAME',
|
||||
'cookie_domain': 'WOOPRA_COOKIE_DOMAIN',
|
||||
'cookie_path': 'WOOPRA_COOKIE_PATH',
|
||||
'cookie_expire': 'WOOPRA_COOKIE_EXPIRE',
|
||||
}
|
||||
woopra_bool_settings = {
|
||||
'click_tracking': 'WOOPRA_CLICK_TRACKING',
|
||||
'download_tracking': 'WOOPRA_DOWNLOAD_TRACKING',
|
||||
'outgoing_tracking': 'WOOPRA_OUTGOING_TRACKING',
|
||||
'outgoing_ignore_subdomain': 'WOOPRA_OUTGOING_IGNORE_SUBDOMAIN',
|
||||
'ignore_query_url': 'WOOPRA_IGNORE_QUERY_URL',
|
||||
'hide_campaign': 'WOOPRA_HIDE_CAMPAIGN',
|
||||
}
|
||||
|
||||
for key, name in woopra_int_settings.items():
|
||||
with suppress(AttributeError):
|
||||
variables[key] = getattr(settings, name)
|
||||
if type(variables[key]) is not int:
|
||||
raise AnalyticalException(f'{name} must be an int value')
|
||||
|
||||
for key, name in woopra_str_settings.items():
|
||||
with suppress(AttributeError):
|
||||
variables[key] = getattr(settings, name)
|
||||
if type(variables[key]) is not str:
|
||||
raise AnalyticalException(f'{name} must be a string value')
|
||||
|
||||
for key, name in woopra_bool_settings.items():
|
||||
with suppress(AttributeError):
|
||||
variables[key] = getattr(settings, name)
|
||||
if type(variables[key]) is not bool:
|
||||
raise AnalyticalException(f'{name} must be a boolean value')
|
||||
|
||||
return variables
|
||||
|
||||
def _get_visitor(self, context):
|
||||
|
|
@ -83,8 +115,7 @@ class WoopraNode(Node):
|
|||
if 'name' not in params and 'email' not in params:
|
||||
user = get_user_from_context(context)
|
||||
if user is not None and get_user_is_authenticated(user):
|
||||
params['name'] = get_identity(
|
||||
context, 'woopra', self._identify, user)
|
||||
params['name'] = get_identity(context, 'woopra', self._identify, user)
|
||||
if user.email:
|
||||
params['email'] = user.email
|
||||
return params
|
||||
|
|
|
|||
|
|
@ -2,21 +2,17 @@
|
|||
Yandex.Metrica 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, \
|
||||
get_required_setting
|
||||
|
||||
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||
|
||||
COUNTER_ID_RE = re.compile(r'^\d{8}$')
|
||||
COUNTER_CODE = """
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
(function (d, w, c) {
|
||||
(w[c] = w[c] || []).push(function() {
|
||||
try {
|
||||
|
|
@ -48,7 +44,7 @@ def yandex_metrica(parser, token):
|
|||
"""
|
||||
Yandex.Metrica counter template tag.
|
||||
|
||||
Renders Javascript code to track page visits. You must supply
|
||||
Renders JavaScript code to track page visits. You must supply
|
||||
your website counter ID (as a string) in the
|
||||
``YANDEX_METRICA_COUNTER_ID`` setting.
|
||||
"""
|
||||
|
|
@ -61,15 +57,17 @@ def yandex_metrica(parser, token):
|
|||
class YandexMetricaNode(Node):
|
||||
def __init__(self):
|
||||
self.counter_id = get_required_setting(
|
||||
'YANDEX_METRICA_COUNTER_ID', COUNTER_ID_RE,
|
||||
"must be (a string containing) a number'")
|
||||
'YANDEX_METRICA_COUNTER_ID',
|
||||
COUNTER_ID_RE,
|
||||
"must be (a string containing) a number'",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
options = {
|
||||
'id': int(self.counter_id),
|
||||
'clickmap': True,
|
||||
'trackLinks': True,
|
||||
'accurateTrackBounce': True
|
||||
'accurateTrackBounce': True,
|
||||
}
|
||||
if getattr(settings, 'YANDEX_METRICA_WEBVISOR', False):
|
||||
options['webvisor'] = True
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
"""
|
||||
Tests for the KISSmetrics tags and filters.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
from analytical.templatetags.kiss_metrics import KissMetricsNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef01234567')
|
||||
class KissMetricsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``kiss_metrics`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('kiss_metrics', 'kiss_metrics')
|
||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/"
|
||||
"0123456789abcdef0123456789abcdef01234567.1.js" in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = KissMetricsNode().render(Context())
|
||||
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='0123456789abcdef0123456789abcdef0123456')
|
||||
def test_api_key_too_short(self):
|
||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
||||
|
||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef012345678')
|
||||
def test_api_key_too_long(self):
|
||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
r = KissMetricsNode().render(Context({'user': User(username='test')}))
|
||||
self.assertTrue("_kmq.push(['identify', 'test']);" in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
r = KissMetricsNode().render(Context({'user': AnonymousUser()}))
|
||||
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'}),
|
||||
}))
|
||||
self.assertTrue("_kmq.push(['record', 'test_event', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
|
||||
def test_property(self):
|
||||
r = KissMetricsNode().render(Context({
|
||||
'kiss_metrics_properties': {'prop1': 'val1', 'prop2': 'val2'},
|
||||
}))
|
||||
self.assertTrue("_kmq.push([\'set\', "
|
||||
'{"prop1": "val1", "prop2": "val2"}]);' in r, r)
|
||||
|
||||
def test_alias(self):
|
||||
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'])
|
||||
def test_render_internal_ip(self):
|
||||
req = HttpRequest()
|
||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = KissMetricsNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- KISSmetrics disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
"""
|
||||
Tests for the Matomo template tags and filters.
|
||||
"""
|
||||
|
||||
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.matomo import MatomoNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com', MATOMO_SITE_ID='345')
|
||||
class MatomoTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``matomo`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('matomo', 'matomo')
|
||||
self.assertTrue('"//example.com/"' in r, r)
|
||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
||||
self.assertTrue('img src="//example.com/piwik.php?idsite=345"'
|
||||
in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = MatomoNode().render(Context({}))
|
||||
self.assertTrue('"//example.com/";' in r, r)
|
||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
||||
self.assertTrue('img src="//example.com/piwik.php?idsite=345"'
|
||||
in r, r)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com/matomo',
|
||||
MATOMO_SITE_ID='345')
|
||||
def test_domain_path_valid(self):
|
||||
r = self.render_tag('matomo', 'matomo')
|
||||
self.assertTrue('"//example.com/matomo/"' in r, r)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com:1234',
|
||||
MATOMO_SITE_ID='345')
|
||||
def test_domain_port_valid(self):
|
||||
r = self.render_tag('matomo', 'matomo')
|
||||
self.assertTrue('"//example.com:1234/";' in r, r)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com:1234/matomo',
|
||||
MATOMO_SITE_ID='345')
|
||||
def test_domain_port_path_valid(self):
|
||||
r = self.render_tag('matomo', 'matomo')
|
||||
self.assertTrue('"//example.com:1234/matomo/"' in r, r)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH=None)
|
||||
def test_no_domain(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_SITE_ID=None)
|
||||
def test_no_siteid(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_SITE_ID='x')
|
||||
def test_siteid_not_a_number(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='http://www.example.com')
|
||||
def test_domain_protocol_invalid(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com/')
|
||||
def test_domain_slash_invalid(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com:123:456')
|
||||
def test_domain_multi_port(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com:')
|
||||
def test_domain_incomplete_port(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com:/matomo')
|
||||
def test_domain_uri_incomplete_port(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@override_settings(MATOMO_DOMAIN_PATH='example.com:12df')
|
||||
def test_domain_port_invalid(self):
|
||||
self.assertRaises(AnalyticalException, MatomoNode)
|
||||
|
||||
@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 = MatomoNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Matomo disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
||||
def test_uservars(self):
|
||||
context = Context({'matomo_vars': [(1, 'foo', 'foo_val'),
|
||||
(2, 'bar', 'bar_val', 'page'),
|
||||
(3, 'spam', 'spam_val', 'visit')]})
|
||||
r = MatomoNode().render(context)
|
||||
msg = 'Incorrect Matomo custom variable rendering. Expected:\n%s\nIn:\n%s'
|
||||
for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);',
|
||||
'_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);',
|
||||
'_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']:
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_default_usertrack(self):
|
||||
context = Context({
|
||||
'user': User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||
})
|
||||
r = MatomoNode().render(context)
|
||||
msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
def test_matomo_usertrack(self):
|
||||
context = Context({
|
||||
'matomo_identity': 'BDFL'
|
||||
})
|
||||
r = MatomoNode().render(context)
|
||||
msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
def test_analytical_usertrack(self):
|
||||
context = Context({
|
||||
'analytical_identity': 'BDFL'
|
||||
})
|
||||
r = MatomoNode().render(context)
|
||||
msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_disable_usertrack(self):
|
||||
context = Context({
|
||||
'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'),
|
||||
'matomo_identity': None
|
||||
})
|
||||
r = MatomoNode().render(context)
|
||||
msg = 'Incorrect Matomo 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(MATOMO_DISABLE_COOKIES=True)
|
||||
def test_disable_cookies(self):
|
||||
r = MatomoNode().render(Context({}))
|
||||
self.assertTrue("_paq.push(['disableCookies']);" in r, r)
|
||||
|
||||
@override_settings(MATOMO_ASK_FOR_CONSENT=True)
|
||||
def test_ask_for_consent(self):
|
||||
r = MatomoNode().render(Context({}))
|
||||
self.assertTrue("_paq.push(['requireConsent']);" in r, r)
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
"""
|
||||
Tests for the Olark template tags and filters.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from analytical.templatetags.olark import OlarkNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(OLARK_SITE_ID='1234-567-89-0123')
|
||||
class OlarkTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``olark`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('olark', 'olark')
|
||||
self.assertTrue("olark.identify('1234-567-89-0123');" in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = OlarkNode().render(Context())
|
||||
self.assertTrue("olark.identify('1234-567-89-0123');" in r, r)
|
||||
|
||||
@override_settings(OLARK_SITE_ID=None)
|
||||
def test_no_site_id(self):
|
||||
self.assertRaises(AnalyticalException, OlarkNode)
|
||||
|
||||
@override_settings(OLARK_SITE_ID='1234-567-8901234')
|
||||
def test_wrong_site_id(self):
|
||||
self.assertRaises(AnalyticalException, OlarkNode)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
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)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
r = OlarkNode().render(Context({'user': AnonymousUser()}))
|
||||
self.assertFalse("olark('api.chat.updateVisitorNickname', " in r, r)
|
||||
|
||||
def test_nickname(self):
|
||||
r = OlarkNode().render(Context({'olark_nickname': 'testnick'}))
|
||||
self.assertTrue("olark('api.chat.updateVisitorNickname', "
|
||||
"{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)
|
||||
|
||||
def test_status_string_list(self):
|
||||
r = OlarkNode().render(Context({
|
||||
'olark_status': ['teststatus1', 'teststatus2'],
|
||||
}))
|
||||
self.assertTrue("olark('api.chat.updateVisitorStatus', "
|
||||
'{snippet: ["teststatus1", "teststatus2"]});' in r, r)
|
||||
|
||||
def test_messages(self):
|
||||
messages = [
|
||||
"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",
|
||||
]
|
||||
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)
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
"""
|
||||
Tests for the Piwik template tags and filters.
|
||||
"""
|
||||
|
||||
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.piwik import PiwikNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com', PIWIK_SITE_ID='345')
|
||||
class PiwikTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``piwik`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('piwik', 'piwik')
|
||||
self.assertTrue('"//example.com/"' in r, r)
|
||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
||||
self.assertTrue('img src="//example.com/piwik.php?idsite=345"'
|
||||
in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = PiwikNode().render(Context({}))
|
||||
self.assertTrue('"//example.com/";' in r, r)
|
||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
||||
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('"//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('"//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('"//example.com:1234/piwik/"' in r, r)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH=None)
|
||||
def test_no_domain(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_SITE_ID=None)
|
||||
def test_no_siteid(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_SITE_ID='x')
|
||||
def test_siteid_not_a_number(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='http://www.example.com')
|
||||
def test_domain_protocol_invalid(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com/')
|
||||
def test_domain_slash_invalid(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com:123:456')
|
||||
def test_domain_multi_port(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com:')
|
||||
def test_domain_incomplete_port(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com:/piwik')
|
||||
def test_domain_uri_incomplete_port(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@override_settings(PIWIK_DOMAIN_PATH='example.com:12df')
|
||||
def test_domain_port_invalid(self):
|
||||
self.assertRaises(AnalyticalException, PiwikNode)
|
||||
|
||||
@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 = PiwikNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Piwik disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
||||
def test_uservars(self):
|
||||
context = Context({'piwik_vars': [(1, 'foo', 'foo_val'),
|
||||
(2, 'bar', 'bar_val', 'page'),
|
||||
(3, 'spam', 'spam_val', 'visit')]})
|
||||
r = PiwikNode().render(context)
|
||||
msg = 'Incorrect Piwik custom variable rendering. Expected:\n%s\nIn:\n%s'
|
||||
for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);',
|
||||
'_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);',
|
||||
'_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']:
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_default_usertrack(self):
|
||||
context = Context({
|
||||
'user': User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||
})
|
||||
r = PiwikNode().render(context)
|
||||
msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
def test_piwik_usertrack(self):
|
||||
context = Context({
|
||||
'piwik_identity': 'BDFL'
|
||||
})
|
||||
r = PiwikNode().render(context)
|
||||
msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
def test_analytical_usertrack(self):
|
||||
context = Context({
|
||||
'analytical_identity': 'BDFL'
|
||||
})
|
||||
r = PiwikNode().render(context)
|
||||
msg = 'Incorrect Piwik user tracking rendering.\nNot found:\n%s\nIn:\n%s'
|
||||
var_code = '_paq.push(["setUserId", "BDFL"]);'
|
||||
self.assertIn(var_code, r, msg % (var_code, r))
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_disable_usertrack(self):
|
||||
context = Context({
|
||||
'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'),
|
||||
'piwik_identity': None
|
||||
})
|
||||
r = PiwikNode().render(context)
|
||||
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)
|
||||
|
||||
@override_settings(PIWIK_ASK_FOR_CONSENT=True)
|
||||
def test_ask_for_consent(self):
|
||||
r = PiwikNode().render(Context({}))
|
||||
self.assertTrue("_paq.push(['requireConsent']);" in r, r)
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
"""
|
||||
Tests for the SnapEngage template tags and filters.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import translation
|
||||
|
||||
from analytical.templatetags.snapengage import SnapEngageNode, \
|
||||
BUTTON_STYLE_LIVE, BUTTON_STYLE_DEFAULT, BUTTON_STYLE_NONE, \
|
||||
BUTTON_LOCATION_LEFT, BUTTON_LOCATION_RIGHT, BUTTON_LOCATION_TOP, \
|
||||
BUTTON_LOCATION_BOTTOM, FORM_POSITION_TOP_LEFT
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
WIDGET_ID = 'ec329c69-0bf0-4db8-9b77-3f8150fb977e'
|
||||
|
||||
|
||||
@override_settings(
|
||||
SNAPENGAGE_WIDGET_ID=WIDGET_ID,
|
||||
SNAPENGAGE_BUTTON=BUTTON_STYLE_DEFAULT,
|
||||
SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_LEFT,
|
||||
SNAPENGAGE_BUTTON_OFFSET="55%",
|
||||
)
|
||||
class SnapEngageTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``snapengage`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('snapengage', 'snapengage')
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%");' in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%");' in r, r)
|
||||
|
||||
@override_settings(SNAPENGAGE_WIDGET_ID=None)
|
||||
def test_no_site_id(self):
|
||||
self.assertRaises(AnalyticalException, SnapEngageNode)
|
||||
|
||||
@override_settings(SNAPENGAGE_WIDGET_ID='abc')
|
||||
def test_wrong_site_id(self):
|
||||
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)
|
||||
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,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%",true);' in r, r)
|
||||
with override_settings(SNAPENGAGE_BUTTON=BUTTON_STYLE_LIVE):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%",true);' in r, r)
|
||||
|
||||
def test_custom_button(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button': "http://www.example.com/button.png",
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%");' in r, r)
|
||||
self.assertTrue(
|
||||
'SnapABug.setButton("http://www.example.com/button.png");' in r, r)
|
||||
with override_settings(
|
||||
SNAPENGAGE_BUTTON="http://www.example.com/button.png"):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"55%");' in r, r)
|
||||
self.assertTrue(
|
||||
'SnapABug.setButton("http://www.example.com/button.png");' in r,
|
||||
r)
|
||||
|
||||
def test_button_location_right(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'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):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","1",'
|
||||
'"55%");' in r, r)
|
||||
|
||||
def test_button_location_top(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location': BUTTON_LOCATION_TOP,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",'
|
||||
'"55%");' in r, r)
|
||||
with override_settings(SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_TOP):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","2",'
|
||||
'"55%");' in r, r)
|
||||
|
||||
def test_button_location_bottom(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location': BUTTON_LOCATION_BOTTOM,
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",'
|
||||
'"55%");' in r, r)
|
||||
with override_settings(
|
||||
SNAPENGAGE_BUTTON_LOCATION=BUTTON_LOCATION_BOTTOM):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","3",'
|
||||
'"55%");' in r, r)
|
||||
|
||||
def test_button_offset(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_location_offset': "30%",
|
||||
}))
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"30%");' in r, r)
|
||||
with override_settings(SNAPENGAGE_BUTTON_LOCATION_OFFSET="30%"):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue(
|
||||
'SnapABug.addButton("ec329c69-0bf0-4db8-9b77-3f8150fb977e","0",'
|
||||
'"30%");' in r, r)
|
||||
|
||||
def test_button_effect(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_button_effect': "-4px",
|
||||
}))
|
||||
self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r)
|
||||
with override_settings(SNAPENGAGE_BUTTON_EFFECT="-4px"):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.setButtonEffect("-4px");' in r, r)
|
||||
|
||||
def test_form_position(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'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())
|
||||
self.assertTrue('SnapABug.setChatFormPosition("tl");' in r, r)
|
||||
|
||||
def test_form_top_position(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'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())
|
||||
self.assertTrue('SnapABug.setFormTopPosition(40);' in r, r)
|
||||
|
||||
def test_domain(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_domain': "example.com"}))
|
||||
self.assertTrue('SnapABug.setDomain("example.com");' in r, r)
|
||||
with override_settings(SNAPENGAGE_DOMAIN="example.com"):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.setDomain("example.com");' in r, r)
|
||||
|
||||
def test_secure_connection(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_secure_connection': True}))
|
||||
self.assertTrue('SnapABug.setSecureConnexion();' in r, r)
|
||||
with override_settings(SNAPENGAGE_SECURE_CONNECTION=True):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.setSecureConnexion();' in r, r)
|
||||
|
||||
def test_show_offline(self):
|
||||
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())
|
||||
self.assertTrue('SnapABug.allowOffline(false);' in r, r)
|
||||
|
||||
def test_proactive_chat(self):
|
||||
r = SnapEngageNode().render(Context({
|
||||
'snapengage_proactive_chat': False}))
|
||||
self.assertTrue('SnapABug.allowProactiveChat(false);' in r, r)
|
||||
|
||||
def test_screenshot(self):
|
||||
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,
|
||||
}))
|
||||
self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r)
|
||||
with override_settings(SNAPENGAGE_OFFLINE_SCREENSHOTS=False):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.showScreenshotOption(false);' in r, r)
|
||||
|
||||
def test_sounds(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_sounds': False}))
|
||||
self.assertTrue('SnapABug.allowChatSound(false);' in r, r)
|
||||
with override_settings(SNAPENGAGE_SOUNDS=False):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.allowChatSound(false);' in r, r)
|
||||
|
||||
@override_settings(SNAPENGAGE_READONLY_EMAIL=False)
|
||||
def test_email(self):
|
||||
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)
|
||||
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)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
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(),
|
||||
}))
|
||||
self.assertFalse('SnapABug.setUserEmail(' in r, r)
|
||||
|
||||
def test_language(self):
|
||||
r = SnapEngageNode().render(Context({'snapengage_locale': 'fr'}))
|
||||
self.assertTrue('SnapABug.setLocale("fr");' in r, r)
|
||||
with override_settings(SNAPENGAGE_LOCALE='fr'):
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.setLocale("fr");' in r, r)
|
||||
|
||||
def test_automatic_language(self):
|
||||
real_get_language = translation.get_language
|
||||
try:
|
||||
translation.get_language = lambda: 'fr-ca'
|
||||
r = SnapEngageNode().render(Context())
|
||||
self.assertTrue('SnapABug.setLocale("fr_CA");' in r, r)
|
||||
finally:
|
||||
translation.get_language = real_get_language
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
"""
|
||||
Tests for the Woopra template tags and filters.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
from analytical.templatetags.woopra import WoopraNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(WOOPRA_DOMAIN='example.com')
|
||||
class WoopraTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``woopra`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('woopra', 'woopra')
|
||||
self.assertTrue('var woo_settings = {"domain": "example.com"};' in r, r)
|
||||
|
||||
def test_node(self):
|
||||
r = WoopraNode().render(Context({}))
|
||||
self.assertTrue('var woo_settings = {"domain": "example.com"};' in r, r)
|
||||
|
||||
@override_settings(WOOPRA_DOMAIN=None)
|
||||
def test_no_domain(self):
|
||||
self.assertRaises(AnalyticalException, WoopraNode)
|
||||
|
||||
@override_settings(WOOPRA_DOMAIN='this is not a domain')
|
||||
def test_wrong_domain(self):
|
||||
self.assertRaises(AnalyticalException, WoopraNode)
|
||||
|
||||
@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)
|
||||
|
||||
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)
|
||||
|
||||
@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)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_username_no_email(self):
|
||||
r = WoopraNode().render(Context({'user': User(username='test')}))
|
||||
self.assertTrue('var woo_visitor = {"name": "test"};' in r, r)
|
||||
|
||||
@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'),
|
||||
}))
|
||||
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'),
|
||||
}))
|
||||
self.assertTrue('var woo_visitor = {"email": "explicit"};' in r, r)
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
r = WoopraNode().render(Context({'user': AnonymousUser()}))
|
||||
self.assertTrue('var woo_visitor = {};' 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 = WoopraNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Woopra disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
|
|
@ -5,9 +5,7 @@ Utility function for django-analytical.
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
HTML_COMMENT = "<!-- %(service)s disabled on internal IP " \
|
||||
"address\n%(html)s\n-->"
|
||||
HTML_COMMENT = '<!-- %(service)s disabled on internal IP address\n%(html)s\n-->'
|
||||
|
||||
|
||||
def get_required_setting(setting, value_re, invalid_msg):
|
||||
|
|
@ -20,13 +18,14 @@ def get_required_setting(setting, value_re, invalid_msg):
|
|||
try:
|
||||
value = getattr(settings, setting)
|
||||
except AttributeError:
|
||||
raise AnalyticalException("%s setting: not found" % setting)
|
||||
raise AnalyticalException('%s setting: not found' % setting)
|
||||
if not value:
|
||||
raise AnalyticalException("%s setting is not set" % setting)
|
||||
raise AnalyticalException('%s setting is not set' % setting)
|
||||
value = str(value)
|
||||
if not value_re.search(value):
|
||||
raise AnalyticalException("%s setting: %s: '%s'"
|
||||
% (setting, invalid_msg, value))
|
||||
raise AnalyticalException(
|
||||
"%s setting: %s: '%s'" % (setting, invalid_msg, value)
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -113,6 +112,7 @@ def get_domain(context, prefix):
|
|||
if domain is None:
|
||||
if 'django.contrib.sites' in settings.INSTALLED_APPS:
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
try:
|
||||
domain = Site.objects.get_current().domain
|
||||
except (ImproperlyConfigured, Site.DoesNotExist):
|
||||
|
|
@ -163,4 +163,5 @@ class AnalyticalException(Exception):
|
|||
Raised when an exception occurs in any django-analytical code that should
|
||||
be silenced in templates.
|
||||
"""
|
||||
|
||||
silent_variable_failure = True
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
def setup(app):
|
||||
app.add_crossref_type(
|
||||
directivename="setting",
|
||||
rolename="setting",
|
||||
indextemplate="pair: %s; setting",
|
||||
directivename='setting',
|
||||
rolename='setting',
|
||||
indextemplate='pair: %s; setting',
|
||||
)
|
||||
app.add_crossref_type(
|
||||
directivename="templatetag",
|
||||
rolename="ttag",
|
||||
indextemplate="pair: %s; template tag"
|
||||
directivename='templatetag',
|
||||
rolename='ttag',
|
||||
indextemplate='pair: %s; template tag',
|
||||
)
|
||||
app.add_crossref_type(
|
||||
directivename="templatefilter",
|
||||
rolename="tfilter",
|
||||
indextemplate="pair: %s; template filter"
|
||||
directivename='templatefilter',
|
||||
rolename='tfilter',
|
||||
indextemplate='pair: %s; template filter',
|
||||
)
|
||||
app.add_crossref_type(
|
||||
directivename="fieldlookup",
|
||||
rolename="lookup",
|
||||
indextemplate="pair: %s; field lookup type",
|
||||
directivename='fieldlookup',
|
||||
rolename='lookup',
|
||||
indextemplate='pair: %s; field lookup type',
|
||||
)
|
||||
|
|
|
|||
22
docs/conf.py
22
docs/conf.py
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing
|
||||
# directory.
|
||||
|
|
@ -11,11 +10,10 @@ sys.path.append(os.path.dirname(os.path.abspath('.')))
|
|||
|
||||
import analytical # noqa
|
||||
|
||||
|
||||
# -- General configuration --------------------------------------------------
|
||||
|
||||
project = u'django-analytical'
|
||||
copyright = u'2011-2016, Joost Cassee <joost@cassee.net>'
|
||||
project = 'django-analytical'
|
||||
copyright = '2011, Joost Cassee <joost@cassee.net>'
|
||||
|
||||
release = analytical.__version__
|
||||
# The short X.Y version.
|
||||
|
|
@ -23,16 +21,15 @@ version = release.rsplit('.', 1)[0]
|
|||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'local']
|
||||
templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
source_suffix = {'.rst': 'restructuredtext'}
|
||||
master_doc = 'index'
|
||||
|
||||
add_function_parentheses = True
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
intersphinx_mapping = {
|
||||
'http://docs.python.org/2.7': None,
|
||||
'http://docs.djangoproject.com/en/1.9':
|
||||
'http://docs.djangoproject.com/en/1.9/_objects/',
|
||||
'python': ('https://docs.python.org/3.13', None),
|
||||
'django': ('https://docs.djangoproject.com/en/stable', None),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -45,6 +42,11 @@ htmlhelp_basename = 'analyticaldoc'
|
|||
# -- Options for LaTeX output -----------------------------------------------
|
||||
|
||||
latex_documents = [
|
||||
('index', 'django-analytical.tex', u'Documentation for django-analytical',
|
||||
u'Joost Cassee', 'manual'),
|
||||
(
|
||||
'index',
|
||||
'django-analytical.tex',
|
||||
'Documentation for django-analytical',
|
||||
'Joost Cassee',
|
||||
'manual',
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -68,3 +68,52 @@ and is enabled by default. To disable:
|
|||
Alternatively, add one of the variables to the context yourself
|
||||
when you render the template.
|
||||
|
||||
Changing the identity
|
||||
*********************
|
||||
|
||||
If you want to override the identity of the logged-in user that the various
|
||||
providers send you can do it by setting the ``analytical_identity`` context
|
||||
variable in your view code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
context = RequestContext({'analytical_identity': user.uuid})
|
||||
return some_template.render(context)
|
||||
|
||||
or in the template:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% with analytical_identity=request.user.uuid|default:None %}
|
||||
{% analytical_head_top %}
|
||||
{% endwith %}
|
||||
|
||||
or by implementing a context processor, e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# FILE: myproject/context_processors.py
|
||||
from django.conf import settings
|
||||
|
||||
def get_identity(request):
|
||||
return {
|
||||
'analytical_identity': 'some-value-here',
|
||||
}
|
||||
|
||||
# FILE: myproject/settings.py
|
||||
TEMPLATES = [
|
||||
{
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'myproject.context_processors.get_identity',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
That allows you as a developer to leave your view code untouched and
|
||||
make sure that the variable is injected for all templates.
|
||||
|
||||
If you want to change the identity only for specific provider use the
|
||||
``*_identity`` context variable, where the ``*`` prefix is the module name
|
||||
of the specific provider.
|
||||
|
|
|
|||
|
|
@ -23,8 +23,21 @@ considered to be a backward-incompatible change.
|
|||
Credits
|
||||
=======
|
||||
|
||||
.. include:: ../AUTHORS.rst
|
||||
The django-analytical package was originally written by `Joost Cassee`_
|
||||
and is now maintained by `Peter Bittner`_ and the `Jazzband community`_.
|
||||
All known contributors are listed as ``authors`` in the `project metadata`_.
|
||||
|
||||
Included JavaScript code snippets for integration of the analytics services
|
||||
were written by the respective service providers.
|
||||
|
||||
The application was inspired by and uses ideas from Analytical_, Joshua
|
||||
Krall's all-purpose analytics front-end for Rails.
|
||||
|
||||
.. _`Joost Cassee`: https://github.com/jcassee
|
||||
.. _`Peter Bittner`: https://github.com/bittner
|
||||
.. _`Jazzband community`: https://jazzband.co/
|
||||
.. _`project metadata`: https://github.com/jazzband/django-analytical/blob/main/pyproject.toml#L15-L60
|
||||
.. _`Analytical`: https://github.com/jkrall/analytical
|
||||
|
||||
.. _helping-out:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Django_ project.
|
|||
|
||||
.. _Django: https://www.djangoproject.com/
|
||||
|
||||
:Package: https://pypi.python.org/pypi/django-analytical/
|
||||
:Package: https://pypi.org/project/django-analytical/
|
||||
:Source: https://github.com/jazzband/django-analytical
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ get the development code:
|
|||
|
||||
$ git clone https://github.com/jazzband/django-analytical.git
|
||||
|
||||
.. _PyPI: http://pypi.python.org/pypi/django-analytical/
|
||||
.. _PyPI: https://pypi.org/project/django-analytical/
|
||||
.. _GitHub: http://github.com/jazzband/django-analytical
|
||||
|
||||
Then install the package by running the setup script:
|
||||
|
|
@ -68,7 +68,7 @@ file of your project:
|
|||
Adding the template tags to the base template
|
||||
=============================================
|
||||
|
||||
Because every analytics service uses own specific Javascript code that
|
||||
Because every analytics service uses own specific JavaScript code that
|
||||
should be added to the top or bottom of either the head or body of the
|
||||
HTML page, django-analytical provides four general-purpose template tags
|
||||
that will render the code needed for the services you are using. Your
|
||||
|
|
@ -133,10 +133,18 @@ settings required to enable each service are listed here:
|
|||
|
||||
GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef'
|
||||
|
||||
* :doc:`Google Analytics <services/google_analytics>`::
|
||||
* :doc:`Google Analytics (legacy) <services/google_analytics>`::
|
||||
|
||||
GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8'
|
||||
|
||||
* :doc:`Google Analytics (gtag.js) <services/google_analytics_gtag>`::
|
||||
|
||||
GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = 'UA-1234567-8'
|
||||
|
||||
* :doc:`Google Analytics (analytics.js) <services/google_analytics_js>`::
|
||||
|
||||
GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-12345678-9'
|
||||
|
||||
* :doc:`HubSpot <services/hubspot>`::
|
||||
|
||||
HUBSPOT_PORTAL_ID = '1234'
|
||||
|
|
@ -155,6 +163,10 @@ settings required to enable each service are listed here:
|
|||
|
||||
KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567'
|
||||
|
||||
* :doc:`Lucky Orange <services/luckyorange>`::
|
||||
|
||||
LUCKYORANGE_SITE_ID = '123456'
|
||||
|
||||
* :doc:`Matomo (formerly Piwik) <services/matomo>`::
|
||||
|
||||
MATOMO_DOMAIN_PATH = 'your.matomo.server/optional/path'
|
||||
|
|
@ -176,15 +188,14 @@ settings required to enable each service are listed here:
|
|||
|
||||
PERFORMABLE_API_KEY = '123abc'
|
||||
|
||||
* :doc:`Piwik (deprecated, see Matomo) <services/piwik>`::
|
||||
|
||||
PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path'
|
||||
PIWIK_SITE_ID = '123'
|
||||
|
||||
* :doc:`Rating\@Mail.ru <services/rating_mailru>`::
|
||||
|
||||
RATING_MAILRU_COUNTER_ID = '1234567'
|
||||
|
||||
* :doc:`SnapEngage <services/snapengage>`::
|
||||
|
||||
SNAPENGAGE_WIDGET_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
|
||||
|
||||
* :doc:`Woopra <services/woopra>`::
|
||||
|
||||
WOOPRA_DOMAIN = 'abcde.com'
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ important information about detecting the visitor IP address.
|
|||
Setting the domain
|
||||
------------------
|
||||
|
||||
The Javascript tracking code can send the website domain to Chartbeat.
|
||||
The JavaScript tracking code can send the website domain to Chartbeat.
|
||||
If you use multiple subdomains this enables you to treat them as one
|
||||
website in Chartbeat. If your project uses the sites framework, the
|
||||
domain name of the current :class:`~django.contrib.sites.models.Site`
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
Clickmap -- visual click tracking
|
||||
==================================
|
||||
|
||||
`Clickmap`_ is a real-time heatmap tool to track mouse clicks and scroll paths of your website visitors. Gain intelligence about what's hot and what's not, and optimize your conversion with Clickmap.
|
||||
`Clickmap`_ is a real-time heatmap tool to track mouse clicks and scroll paths of your website visitors.
|
||||
Gain intelligence about what's hot and what's not, and optimize your conversion with Clickmap.
|
||||
|
||||
.. _`Clickmap`: http://www.getclickmap.com/
|
||||
.. _`Clickmap`: http://www.clickmap.ch/
|
||||
|
||||
|
||||
.. clickmap-installation:
|
||||
|
|
@ -22,7 +23,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`clickmap-configuration`.
|
||||
|
||||
The Clickmap Javascript code is inserted into templates using a template
|
||||
The Clickmap JavaScript code is inserted into templates using a template
|
||||
tag. Load the :mod:`clickmap` template tag library and insert the
|
||||
:ttag:`clickmap` tag. Because every page that you want to track must
|
||||
have the tag, it is useful to add it to your base template. Insert
|
||||
|
|
@ -44,7 +45,7 @@ Before you can use the Clickmap integration, you must first set your
|
|||
Clickmap Tracker ID. If you don't have a Clickmap account yet,
|
||||
`sign up`_ to get your Tracker ID.
|
||||
|
||||
.. _`sign up`: http://www.getclickmap.com/
|
||||
.. _`sign up`: http://www.clickmap.ch/
|
||||
|
||||
|
||||
.. _clickmap-tracker-id:
|
||||
|
|
@ -53,7 +54,7 @@ Setting the Tracker ID
|
|||
----------------------
|
||||
|
||||
Clickmap gives you a unique Tracker ID, and the :ttag:`clickmap`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
tag will include it in the rendered JavaScript code. You can find your
|
||||
Tracker ID clicking the link named "Tracker" in the dashboard
|
||||
of your Clickmap account. Set :const:`CLICKMAP_TRACKER_ID` in the project
|
||||
:file:`settings.py` file::
|
||||
|
|
@ -72,6 +73,6 @@ 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:`ANALYTICAL_INTERNAL_IPS` setting
|
||||
(which is :const:`INTERNAL_IPS` by default,) the tracking code is
|
||||
(which is :const:`INTERNAL_IPS` by default,) the tracking code is
|
||||
commented out. See :ref:`identifying-visitors` for important information
|
||||
about detecting the visitor IP address.
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Setting the Site ID
|
|||
-------------------
|
||||
|
||||
Every website you track with Clicky gets its own Site ID, and the
|
||||
:ttag:`clicky` tag will include it in the rendered Javascript code.
|
||||
:ttag:`clicky` tag will include it in the rendered JavaScript code.
|
||||
You can find the Site ID in the *Info* tab of the website *Preferences*
|
||||
page, in your Clicky account. Set :const:`CLICKY_SITE_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
|
@ -84,7 +84,7 @@ Custom data
|
|||
|
||||
As described in the Clicky `customized tracking`_ documentation page,
|
||||
the data that is tracked by Clicky can be customized by setting the
|
||||
:data:`clicky_custom` Javascript variable before loading the tracking
|
||||
:data:`clicky_custom` JavaScript variable before loading the tracking
|
||||
code. Using template context variables, you can let the :ttag:`clicky`
|
||||
tag pass custom data to Clicky automatically. You can set the context
|
||||
variables in your view when you render a template containing the
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Setting the account number
|
|||
--------------------------
|
||||
|
||||
Crazy Egg gives you a unique account number, and the :ttag:`crazy egg`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
tag will include it in the rendered JavaScript code. You can find your
|
||||
account number by clicking the link named "What's my code?" in the
|
||||
dashboard of your Crazy Egg account. Set
|
||||
:const:`CRAZY_EGG_ACCOUNT_NUMBER` in the project :file:`settings.py`
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`gauges-configuration`.
|
||||
|
||||
The Gaug.es Javascript code is inserted into templates using a
|
||||
The Gaug.es JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`gauges` template tag library and
|
||||
insert the :ttag:`gauges` tag. Because every page that you want to
|
||||
track must have the tag, it is useful to add it to your base template.
|
||||
|
|
@ -51,12 +51,12 @@ Setting the site id
|
|||
--------------------------
|
||||
|
||||
Gaug.es gives you a unique site id, and the :ttag:`gauges`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
tag will include it in the rendered JavaScript code. You can find your
|
||||
site id by clicking the *Tracking Code* link when logged into
|
||||
the on the gaug.es website. A page will display containing
|
||||
HTML code looking like this::
|
||||
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
var _gauges = _gauges || [];
|
||||
(function() {
|
||||
var t = document.createElement('script');
|
||||
|
|
@ -76,7 +76,7 @@ file::
|
|||
|
||||
GAUGES_SITE_ID = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
||||
|
||||
If you do not set an site id, the Javascript code will not be
|
||||
If you do not set an site id, the JavaScript code will not be
|
||||
rendered.
|
||||
|
||||
|
||||
|
|
@ -88,6 +88,6 @@ 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:`ANALYTICAL_INTERNAL_IPS` setting
|
||||
(which is :const:`INTERNAL_IPS` by default,) the tracking code is
|
||||
(which is :const:`INTERNAL_IPS` by default,) the tracking code is
|
||||
commented out. See :ref:`identifying-visitors` for important information
|
||||
about detecting the visitor IP address.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
======================================
|
||||
==============================================
|
||||
Google Analytics (legacy) -- traffic analysis
|
||||
======================================
|
||||
==============================================
|
||||
|
||||
`Google Analytics`_ is the well-known web analytics service from
|
||||
Google. The product is aimed more at marketers than webmasters or
|
||||
|
|
@ -58,7 +58,7 @@ Setting the property ID
|
|||
|
||||
Every website you track with Google Analytics gets its own property ID,
|
||||
and the :ttag:`google_analytics` tag will include it in the rendered
|
||||
Javascript code. You can find the web property ID on the overview page
|
||||
JavaScript code. You can find the web property ID on the overview page
|
||||
of your account. Set :const:`GOOGLE_ANALYTICS_PROPERTY_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
||||
|
|
|
|||
168
docs/services/google_analytics_gtag.rst
Normal file
168
docs/services/google_analytics_gtag.rst
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
===============================================
|
||||
Google Analytics (gtag.js) -- 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. The global site tag (`gtag.js`_) is a JavaScript tagging
|
||||
framework and API that allows you to send event data to Google Analytics,
|
||||
Google Ads, and Google Marketing Platform.
|
||||
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
.. _`gtag.js`: https://developers.google.com/analytics/devguides/collection/gtagjs/
|
||||
|
||||
|
||||
.. 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-gtag`.
|
||||
|
||||
The Google Analytics tracking code is inserted into templates using a
|
||||
template tag. Load the :mod:`google_analytics_gtag` template tag library and
|
||||
insert the :ttag:`google_analytics_gtag` 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_gtag %}
|
||||
<html>
|
||||
<head>
|
||||
{% google_analytics_gtag %}
|
||||
...
|
||||
</head>
|
||||
...
|
||||
|
||||
|
||||
.. _google-analytics-configuration-gtag:
|
||||
|
||||
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-gtag-property-id:
|
||||
|
||||
Setting the property ID
|
||||
-----------------------
|
||||
|
||||
Every website you track with Google Analytics gets its own property ID,
|
||||
and the :ttag:`google_analytics_gtag` 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_GTAG_PROPERTY_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
||||
GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = 'UA-XXXXXX-X'
|
||||
|
||||
If you do not set a property ID, the tracking code will not be rendered.
|
||||
|
||||
Please note that the accepted Property IDs should be one of the following formats:
|
||||
|
||||
- 'UA-XXXXXX-Y'
|
||||
- 'AW-XXXXXXXXXX'
|
||||
- 'G-XXXXXXXX'
|
||||
- 'DC-XXXXXXXX'
|
||||
|
||||
|
||||
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-identify-user:
|
||||
|
||||
Identifying authenticated users
|
||||
-------------------------------
|
||||
|
||||
The username of an authenticated user is passed to Google Analytics
|
||||
automatically as the ``user_id``. See :ref:`identifying-visitors`.
|
||||
|
||||
According to `Google Analytics conditions`_ you should avoid
|
||||
sending Personally Identifiable Information.
|
||||
Using ``username`` as ``user_id`` might not be the best option.
|
||||
To avoid that, you can change the identity
|
||||
by setting ``google_analytics_gtag_identity`` (or ``analytical_identity`` to
|
||||
affect all providers) context variable:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
context = RequestContext({'google_analytics_gtag_identity': user.uuid})
|
||||
return some_template.render(context)
|
||||
|
||||
or in the template:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% with google_analytics_gtag_identity=request.user.uuid|default:None %}
|
||||
{% analytical_head_top %}
|
||||
{% endwith %}
|
||||
|
||||
.. _`Google Analytics conditions`: https://developers.google.com/analytics/solutions/crm-integration#user_id
|
||||
|
||||
.. _google-analytics-custom-dimensions:
|
||||
|
||||
Custom dimensions
|
||||
----------------
|
||||
|
||||
As described in the Google Analytics `custom dimensions`_ documentation
|
||||
page, you can define custom dimensions which are variables specific to your
|
||||
business needs. These variables can include both custom event parameters as
|
||||
well as customer user properties. Using the template context variable
|
||||
``google_analytics_custom_dimensions``, you can let the :ttag:`google_analytics_gtag`
|
||||
pass custom dimensions to Google Analytics automatically. The ``google_analytics_custom_dimensions``
|
||||
variable must be set to a dictionary where the keys are the dimension names
|
||||
and the values are the dimension values. You can set the context variable in your
|
||||
view when you render a template containing the tracking code::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
context = RequestContext({
|
||||
'google_analytics_custom_dimensions': {
|
||||
'gender': 'female',
|
||||
'country': 'US',
|
||||
'user_properties': {
|
||||
'age': 25
|
||||
}
|
||||
}
|
||||
})
|
||||
return some_template.render(context)
|
||||
|
||||
Note that the ``user_properties`` key is used to pass user properties to Google
|
||||
Analytics. It's not necessary to always use this key, but that'd be the way of
|
||||
sending user properties to Google Analytics automatically.
|
||||
|
||||
You may want to set custom dimensions in a context processor that you add
|
||||
to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def google_analytics_segment_language(request):
|
||||
try:
|
||||
return {'google_analytics_custom_dimensions': {'language': 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 dimensions`: https://support.google.com/analytics/answer/10075209
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
======================================
|
||||
Google Analytics -- traffic analysis
|
||||
======================================
|
||||
====================================================
|
||||
Google Analytics (analytics.js) -- 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.
|
||||
features. The `analytics.js`_ library (also known as "the Google
|
||||
Analytics tag") is a JavaScript library for measuring how users interact
|
||||
with your website.
|
||||
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
.. _`analytics.js`: https://developers.google.com/analytics/devguides/collection/analyticsjs/
|
||||
|
||||
|
||||
.. google-analytics-installation:
|
||||
|
|
@ -23,7 +26,7 @@ application to :const:`INSTALLED_APPS` in your project
|
|||
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`.
|
||||
:ref:`google-analytics-configuration-js`.
|
||||
|
||||
The Google Analytics tracking code is inserted into templates using a
|
||||
template tag. Load the :mod:`google_analytics_js` template tag library and
|
||||
|
|
@ -40,7 +43,7 @@ template. Insert the tag at the bottom of the HTML head::
|
|||
...
|
||||
|
||||
|
||||
.. _google-analytics-configuration:
|
||||
.. _google-analytics-configuration-js:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
|
@ -51,18 +54,18 @@ code, you also need to set-up the domain. Finally, you can add custom
|
|||
segments for Google Analytics to track.
|
||||
|
||||
|
||||
.. _google-analytics-property-id:
|
||||
.. _google-analytics-js-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
|
||||
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'
|
||||
GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-XXXXXXXX-X'
|
||||
|
||||
If you do not set a property ID, the tracking code will not be rendered.
|
||||
|
||||
|
|
@ -114,7 +117,7 @@ By default, display advertising features are disabled.
|
|||
.. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482
|
||||
|
||||
|
||||
.. _google-analytics-internal-ips:
|
||||
.. _google-analytics-js-internal-ips:
|
||||
|
||||
Internal IP addresses
|
||||
---------------------
|
||||
|
|
@ -128,7 +131,7 @@ setting, the tracking code is commented out. It takes the value of
|
|||
important information about detecting the visitor IP address.
|
||||
|
||||
|
||||
.. _google-analytics-custom-variables:
|
||||
.. _google-analytics-js-custom-variables:
|
||||
|
||||
Custom variables
|
||||
----------------
|
||||
|
|
@ -162,7 +165,7 @@ 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:
|
||||
.. _google-analytics-js-anonimyze-ips:
|
||||
|
||||
Anonymize IPs
|
||||
-------------
|
||||
|
|
@ -180,7 +183,7 @@ By default, IPs are not anonymized.
|
|||
.. _`IP anonymization`: https://support.google.com/analytics/bin/answer.py?hl=en&answer=2763052
|
||||
|
||||
|
||||
.. _google-analytics-sample-rate:
|
||||
.. _google-analytics-js-sample-rate:
|
||||
|
||||
Sample Rate
|
||||
-----------
|
||||
|
|
@ -196,7 +199,7 @@ integer value.
|
|||
.. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#sampleRate
|
||||
|
||||
|
||||
.. _google-analytics-site-speed-sample-rate:
|
||||
.. _google-analytics-js-site-speed-sample-rate:
|
||||
|
||||
Site Speed Sample Rate
|
||||
----------------------
|
||||
|
|
@ -215,7 +218,7 @@ integer value.
|
|||
.. _google-analytics-cookie-expiration:
|
||||
|
||||
Cookie Expiration
|
||||
----------------------
|
||||
-----------------
|
||||
|
||||
You can configure the `Cookie Expiration`_ feature by setting the
|
||||
:const:`GOOGLE_ANALYTICS_COOKIE_EXPIRATION` configuration setting::
|
||||
|
|
@ -225,3 +228,11 @@ You can configure the `Cookie Expiration`_ feature by setting the
|
|||
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
|
||||
|
||||
Custom JavaScript Source
|
||||
------------------------
|
||||
|
||||
You can configure a custom URL for the javascript file by setting the
|
||||
:const:`GOOGLE_ANALYTICS_JS_SOURCE` configuration setting::
|
||||
|
||||
GOOGLE_ANALYTICS_JS_SOURCE = 'https://www.example.com/analytics.js'
|
||||
|
|
|
|||
59
docs/services/heap.rst
Normal file
59
docs/services/heap.rst
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
=====================================
|
||||
Heap -- analytics and events tracking
|
||||
=====================================
|
||||
|
||||
`Heap`_ automatically captures all user interactions on your site, from the moment of installation forward.
|
||||
|
||||
.. _`Heap`: https://heap.io/
|
||||
|
||||
|
||||
.. heap-installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Heap 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.
|
||||
|
||||
.. _heap-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Heap integration, you must first get your
|
||||
Heap Tracker ID. If you don't have a Heap account yet,
|
||||
`sign up`_ to get your Tracker ID.
|
||||
|
||||
.. _`sign up`: https://heap.io/
|
||||
|
||||
|
||||
.. _heap-tracker-id:
|
||||
|
||||
Setting the Tracker ID
|
||||
----------------------
|
||||
|
||||
Heap gives you a unique ID. You can find this ID on the Projects page
|
||||
of your Heap account. Set :const:`HEAP_TRACKER_ID` in the project
|
||||
:file:`settings.py` file::
|
||||
|
||||
HEAP_TRACKER_ID = 'XXXXXXXX'
|
||||
|
||||
If you do not set an Tracker ID, the tracking code will not be
|
||||
rendered.
|
||||
|
||||
The tracking code will be added just before the closing head tag.
|
||||
|
||||
|
||||
.. _heap-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:`ANALYTICAL_INTERNAL_IPS` setting
|
||||
(which is :const:`INTERNAL_IPS` by default,) the tracking code is
|
||||
commented out. See :ref:`identifying-visitors` for important information
|
||||
about detecting the visitor IP address.
|
||||
|
|
@ -23,7 +23,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`intercom-configuration`.
|
||||
|
||||
The Intercom.io Javascript code is inserted into templates using a
|
||||
The Intercom.io JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`intercom` template tag library and
|
||||
insert the :ttag:`intercom` tag. Because every page that you want to
|
||||
track must have the tag, it is useful to add it to your base template.
|
||||
|
|
@ -55,7 +55,7 @@ Setting the app id
|
|||
--------------------------
|
||||
|
||||
Intercom.io gives you a unique app id, and the :ttag:`intercom`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
tag will include it in the rendered JavaScript code. You can find your
|
||||
app id by clicking the *Tracking Code* link when logged into
|
||||
the on the intercom.io website. A page will display containing
|
||||
HTML code looking like this::
|
||||
|
|
@ -71,7 +71,7 @@ file::
|
|||
|
||||
INTERCOM_APP_ID = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
||||
|
||||
If you do not set an app id, the Javascript code will not be
|
||||
If you do not set an app id, the JavaScript code will not be
|
||||
rendered.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@ Setting the account number and site code
|
|||
|
||||
In order to install the survey code, you need to set your KISSinsights
|
||||
account number and website code. The :ttag:`kiss_insights` tag will
|
||||
include it in the rendered Javascript code. You can find the account
|
||||
include it in the rendered JavaScript code. You can find the account
|
||||
number and website code by visiting the code installation page of the
|
||||
website you want to place the surveys on. You will see some HTML code
|
||||
with a Javascript tag with a ``src`` attribute containing
|
||||
with a JavaScript tag with a ``src`` attribute containing
|
||||
``//s3.amazonaws.com/ki.js/XXXXX/YYY.js``. Here ``XXXXX`` is the
|
||||
account number and ``YYY`` the website code. Set
|
||||
:const:`KISS_INSIGHTS_ACCOUNT_NUMBER` and
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`kiss-metrics-configuration`.
|
||||
|
||||
The KISSmetrics Javascript code is inserted into templates using a
|
||||
The KISSmetrics JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`kiss_metrics` template tag library and
|
||||
insert the :ttag:`kiss_metrics` tag. Because every page that you want
|
||||
to track must have the tag, it is useful to add it to your base
|
||||
|
|
@ -53,7 +53,7 @@ Setting the API key
|
|||
|
||||
Every website you track events for with KISSmetrics gets its own API
|
||||
key, and the :ttag:`kiss_metrics` tag will include it in the rendered
|
||||
Javascript code. You can find the website API key by visiting the
|
||||
JavaScript code. You can find the website API key by visiting the
|
||||
website *Product center* on your KISSmetrics dashboard. Set
|
||||
:const:`KISS_METRICS_API_KEY` in the project :file:`settings.py` file::
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ For example::
|
|||
})
|
||||
return some_template.render(context)
|
||||
|
||||
The output script tag will then include the corresponding Javascript event as
|
||||
The output script tag will then include the corresponding JavaScript event as
|
||||
documented in the `KISSmetrics record API`_ docs.
|
||||
|
||||
|
||||
|
|
|
|||
75
docs/services/luckyorange.rst
Normal file
75
docs/services/luckyorange.rst
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
==================================================
|
||||
Lucky Orange -- All-in-one conversion optimization
|
||||
==================================================
|
||||
|
||||
`Lucky Orange`_ is a website analytics and user feedback tool.
|
||||
|
||||
.. _`Lucky Orange`: https://www.luckyorange.com/
|
||||
|
||||
|
||||
.. luckyorange-installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Lucky Orange 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 Lucky Orange 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:`luckyorange-configuration`.
|
||||
|
||||
The Lucky Orange tracking 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:`luckyorange` template tag library. Then insert the
|
||||
:ttag:`luckyorange` tag at the bottom of the head section::
|
||||
|
||||
{% load luckyorange %}
|
||||
<html>
|
||||
<head>
|
||||
...
|
||||
{% luckyorange %}
|
||||
</head>
|
||||
...
|
||||
</html>
|
||||
|
||||
|
||||
.. _luckyorange-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Lucky Orange integration, you must first set your
|
||||
Site ID.
|
||||
|
||||
|
||||
.. _luckyorange-id:
|
||||
|
||||
Setting the Lucky Orange Site ID
|
||||
--------------------------------
|
||||
|
||||
You can find the Lucky Orange Site ID in the "Settings" of your Lucky
|
||||
Orange account, reachable via the gear icon on the top right corner.
|
||||
Set :const:`LUCKYORANGE_SITE_ID` in the project :file:`settings.py` file::
|
||||
|
||||
LUCKYORANGE_SITE_ID = 'XXXXXX'
|
||||
|
||||
If you do not set a Lucky Orange Site ID, the code will not be rendered.
|
||||
|
||||
|
||||
.. _luckyorange-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:`LUCKYORANGE_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,6 +1,6 @@
|
|||
==================================
|
||||
====================================================
|
||||
Matomo (formerly Piwik) -- open source web analytics
|
||||
==================================
|
||||
====================================================
|
||||
|
||||
Matomo_ is an open analytics platform currently used by individuals,
|
||||
companies and governments all over the world.
|
||||
|
|
@ -98,7 +98,7 @@ other parameters should be strings. ::
|
|||
return some_template.render(context)
|
||||
|
||||
Matomo default settings allow up to 5 custom variables for both scope. Variable
|
||||
mapping betweeen index and name must stay constant, or the latest name
|
||||
mapping between index and name must stay constant, or the latest name
|
||||
override the previous one.
|
||||
|
||||
If you use the same user variables in different views and its value can
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`mixpanel-configuration`.
|
||||
|
||||
The Mixpanel Javascript code is inserted into templates using a
|
||||
The Mixpanel JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`mixpanel` template tag library and
|
||||
insert the :ttag:`mixpanel` tag. Because every page that you want
|
||||
to track must have the tag, it is useful to add it to your base
|
||||
|
|
@ -53,7 +53,7 @@ Setting the token
|
|||
-----------------
|
||||
|
||||
Every website you track events for with Mixpanel gets its own token,
|
||||
and the :ttag:`mixpanel` tag will include it in the rendered Javascript
|
||||
and the :ttag:`mixpanel` tag will include it in the rendered JavaScript
|
||||
code. You can find the project token on the Mixpanel *projects* page.
|
||||
Set :const:`MIXPANEL_API_TOKEN` in the project :file:`settings.py`
|
||||
file::
|
||||
|
|
@ -137,16 +137,16 @@ For example::
|
|||
Tracking events
|
||||
===============
|
||||
|
||||
The django-analytical app integrates the Mixpanel Javascript API in
|
||||
The django-analytical app integrates the Mixpanel JavaScript API in
|
||||
templates. To tracking events in views or other parts of Django, you
|
||||
can use Wes Winham's `mixpanel-celery`_ package.
|
||||
|
||||
If you want to track an event in Javascript, use the asynchronous
|
||||
If you want to track an event in JavaScript, use the asynchronous
|
||||
notation, as described in the section titled
|
||||
`"Asynchronous Tracking with Javascript"`_ in the Mixpanel
|
||||
`"Asynchronous Tracking with JavaScript"`_ in the Mixpanel
|
||||
documentation. For example::
|
||||
|
||||
mixpanel.track("play-game", {"level": "12", "weapon": "sword", "character": "knight"});
|
||||
|
||||
.. _mixpanel-celery: http://github.com/winhamwr/mixpanel-celery
|
||||
.. _`"Asynchronous Tracking with Javascript"`: http://mixpanel.com/api/docs/guides/integration/js#async
|
||||
.. _`"Asynchronous Tracking with JavaScript"`: http://mixpanel.com/api/docs/guides/integration/js#async
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`olark-configuration`.
|
||||
|
||||
The Olark Javascript code is inserted into templates using a template
|
||||
The Olark JavaScript code is inserted into templates using a template
|
||||
tag. Load the :mod:`olark` template tag library and insert the
|
||||
:ttag:`olark` tag. Because every page that you want to track
|
||||
must have the tag, it is useful to add it to your base template. Insert
|
||||
|
|
@ -52,7 +52,7 @@ Setting the site ID
|
|||
-------------------
|
||||
|
||||
In order to install the chat code, you need to set your Olark site ID.
|
||||
The :ttag:`olark` tag will include it in the rendered Javascript code.
|
||||
The :ttag:`olark` tag will include it in the rendered JavaScript code.
|
||||
You can find the site ID on `installation page`_ of you Olark account.
|
||||
Set :const:`OLARK_SITE_ID` in the project :file:`settings.py` file::
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ 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.
|
||||
|
||||
See also `api.chat.updateVisitorNickname`_ in the Olark Javascript API
|
||||
See also `api.chat.updateVisitorNickname`_ in the Olark JavaScript API
|
||||
documentation.
|
||||
|
||||
.. _`api.chat.updateVisitorNickname`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorNickname
|
||||
|
|
@ -113,7 +113,7 @@ and the :ttag:`olark` tag will pass them to Olark as status messages::
|
|||
]})
|
||||
return some_template.render(context)
|
||||
|
||||
See also `api.chat.updateVisitorStatus`_ in the Olark Javascript API
|
||||
See also `api.chat.updateVisitorStatus`_ in the Olark JavaScript API
|
||||
documentation.
|
||||
|
||||
.. _`api.chat.updateVisitorStatus`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorStatus
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`optimizely-configuration`.
|
||||
|
||||
The Optimizely Javascript code is inserted into templates using a
|
||||
The Optimizely JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`optimizely` template tag library and
|
||||
insert the :ttag:`optimizely` tag. Because every page that you want to
|
||||
track must have the tag, it is useful to add it to your base template.
|
||||
|
|
@ -53,7 +53,7 @@ Setting the account number
|
|||
--------------------------
|
||||
|
||||
Optimizely gives you a unique account number, and the :ttag:`optimizely`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
tag will include it in the rendered JavaScript code. You can find your
|
||||
account number by clicking the *Implementation* link in the top
|
||||
right-hand corner of the Optimizely website. A pop-up window will
|
||||
appear containing HTML code looking like this::
|
||||
|
|
@ -66,7 +66,7 @@ file::
|
|||
|
||||
OPTIMIZELY_ACCOUNT_NUMBER = 'XXXXXXX'
|
||||
|
||||
If you do not set an account number, the Javascript code will not be
|
||||
If you do not set an account number, the JavaScript code will not be
|
||||
rendered.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`performable-configuration`.
|
||||
|
||||
The Performable Javascript code is inserted into templates using a
|
||||
The Performable JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`performable` template tag library and
|
||||
insert the :ttag:`performable` tag. Because every page that you want to
|
||||
track must have the tag, it is useful to add it to your base template.
|
||||
|
|
@ -53,14 +53,14 @@ Setting the API key
|
|||
-------------------
|
||||
|
||||
You Performable account has its own API key, which :ttag:`performable`
|
||||
tag will include it in the rendered Javascript code. You can find your
|
||||
tag will include it in the rendered JavaScript code. You can find your
|
||||
API key on the *Account Settings* page (click 'Account Settings' in the
|
||||
top right-hand corner of your Performable dashboard). Set
|
||||
:const:`PERFORMABLE_API_KEY` in the project :file:`settings.py` file::
|
||||
|
||||
PERFORMABLE_API_KEY = 'XXXXXX'
|
||||
|
||||
If you do not set an API key, the Javascript code will not be rendered.
|
||||
If you do not set an API key, the JavaScript code will not be rendered.
|
||||
|
||||
|
||||
.. _performable-identity-user:
|
||||
|
|
@ -116,7 +116,7 @@ Embedding a landing page
|
|||
========================
|
||||
|
||||
You can embed a Performable landing page in your Django website. The
|
||||
:ttag:`performable_embed` template tag adds the Javascript code to embed
|
||||
:ttag:`performable_embed` template tag adds the JavaScript code to embed
|
||||
the page. It takes two arguments: the hostname and the page ID::
|
||||
|
||||
{% performable_embed HOSTNAME PAGE_ID %}
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
==================================
|
||||
Piwik (deprecated) -- open source web analytics
|
||||
==================================
|
||||
|
||||
Piwik_ is an open analytics platform currently used by individuals,
|
||||
companies and governments all over the world. With Piwik, your data
|
||||
will always be yours, because you run your own analytics server.
|
||||
|
||||
.. _Piwik: http://www.piwik.org/
|
||||
|
||||
|
||||
Deprecated
|
||||
==========
|
||||
|
||||
Note that Piwik is now known as Matomo. New projects should use the
|
||||
Matomo integration. The Piwik integration in django-analytical is
|
||||
deprecated and eventually will be removed.
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To start using the Piwik 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 Piwik 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:`piwik-configuration`.
|
||||
|
||||
The Piwik tracking code is inserted into templates using a template
|
||||
tag. Load the :mod:`piwik` template tag library and insert the
|
||||
:ttag:`piwik` tag. Because every page that you want to track must
|
||||
have the tag, it is useful to add it to your base template. Insert
|
||||
the tag at the bottom of the HTML body as recommended by the
|
||||
`Piwik best practice for Integration Plugins`_::
|
||||
|
||||
{% load piwik %}
|
||||
...
|
||||
{% piwik %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
.. _`Piwik best practice for Integration Plugins`: http://piwik.org/integrate/how-to/
|
||||
|
||||
|
||||
|
||||
.. _piwik-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can use the Piwik integration, you must first define
|
||||
domain name and optional URI path to your Piwik server, as well as
|
||||
the Piwik ID of the website you're tracking with your Piwik server,
|
||||
in your project settings.
|
||||
|
||||
|
||||
Setting the domain
|
||||
------------------
|
||||
|
||||
Your Django project needs to know where your Piwik server is located.
|
||||
Typically, you'll have Piwik installed on a subdomain of its own
|
||||
(e.g. ``piwik.example.com``), otherwise it runs in a subdirectory of
|
||||
a website of yours (e.g. ``www.example.com/piwik``). Set
|
||||
:const:`PIWIK_DOMAIN_PATH` in the project :file:`settings.py` file
|
||||
accordingly::
|
||||
|
||||
PIWIK_DOMAIN_PATH = 'piwik.example.com'
|
||||
|
||||
If you do not set a domain the tracking code will not be rendered.
|
||||
|
||||
|
||||
Setting the site ID
|
||||
-------------------
|
||||
|
||||
Your Piwik server can track several websites. Each website has its
|
||||
site ID (this is the ``idSite`` parameter in the query string of your
|
||||
browser's address bar when you visit the Piwik Dashboard). Set
|
||||
:const:`PIWIK_SITE_ID` in the project :file:`settings.py` file to
|
||||
the value corresponding to the website you're tracking::
|
||||
|
||||
PIWIK_SITE_ID = '4'
|
||||
|
||||
If you do not set the site ID the tracking code will not be rendered.
|
||||
|
||||
|
||||
.. _piwik-uservars:
|
||||
|
||||
User variables
|
||||
--------------
|
||||
|
||||
Piwik supports sending `custom variables`_ along with default statistics. If
|
||||
you want to set a custom variable, use the context variable ``piwik_vars`` when
|
||||
you render your template. It should be an iterable of custom variables
|
||||
represented by tuples like: ``(index, name, value[, scope])``, where scope may
|
||||
be ``'page'`` (default) or ``'visit'``. ``index`` should be an integer and the
|
||||
other parameters should be strings. ::
|
||||
|
||||
context = Context({
|
||||
'piwik_vars': [(1, 'foo', 'Sir Lancelot of Camelot'),
|
||||
(2, 'bar', 'To seek the Holy Grail', 'page'),
|
||||
(3, 'spam', 'Blue', 'visit')]
|
||||
})
|
||||
return some_template.render(context)
|
||||
|
||||
Piwik default settings allow up to 5 custom variables for both scope. Variable
|
||||
mapping betweeen index and name must stay constant, or the latest name
|
||||
override the previous one.
|
||||
|
||||
If you use the same user variables in different views and its value can
|
||||
be computed from the HTTP request, you can also set them in a context
|
||||
processor that you add to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list
|
||||
in :file:`settings.py`.
|
||||
|
||||
.. _`custom variables`: http://developer.piwik.org/guides/tracking-javascript-guide#custom-variables
|
||||
|
||||
|
||||
.. _piwik-user-tracking:
|
||||
|
||||
User tracking
|
||||
-------------
|
||||
|
||||
If you use the standard Django authentication system, you can allow Piwik to
|
||||
`track individual users`_ by setting the :data:`ANALYTICAL_AUTO_IDENTIFY`
|
||||
setting to :const:`True`. This is enabled by default. Piwik will identify
|
||||
users based on their ``username``.
|
||||
|
||||
If you disable this settings, or want to customize what user id to use, you can
|
||||
set the context variable ``analytical_identity`` (for global configuration) or
|
||||
``piwik_identity`` (for Piwik specific configuration). Setting one to
|
||||
:const:`None` will disable the user tracking feature::
|
||||
|
||||
# Piwik will identify this user as 'BDFL' if ANALYTICAL_AUTO_IDENTIFY is True or unset
|
||||
request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||
|
||||
# Piwik will identify this user as 'Guido van Rossum'
|
||||
request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||
context = Context({
|
||||
'piwik_identity': request.user.get_full_name()
|
||||
})
|
||||
|
||||
# Piwik will not identify this user (but will still collect statistics)
|
||||
request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||
context = Context({
|
||||
'piwik_identity': None
|
||||
})
|
||||
|
||||
.. _`track individual users`: http://developer.piwik.org/guides/tracking-javascript-guide#user-id
|
||||
|
||||
Disabling cookies
|
||||
-----------------
|
||||
|
||||
If you want to `disable cookies`_, set :data:`PIWIK_DISABLE_COOKIES` to
|
||||
:const:`True`. By default, cookies are enabled (i.e. :const:`False`).
|
||||
|
||||
.. _`disable cookies`: https://matomo.org/faq/general/faq_157/
|
||||
|
||||
Ask for consent
|
||||
-----------------
|
||||
|
||||
If you do not want to track visitors without permission, you can `ask for consent`_ first.
|
||||
To enable this, set :data:`PIWIK_ASK_FOR_CONSENT` to :const:`True`. By default, no consent for tracking is needed (i.e. :const:`False`).
|
||||
|
||||
To give and remove consent in your page, create DOM elements with the following classes:
|
||||
|
||||
`piwik_give_consent` - class name for element to click when visitors want to **give** consent
|
||||
`piwik_remove_consent` - class name for element to click when visitors want to **remove** consent
|
||||
|
||||
Examples:
|
||||
# button to allow tracking
|
||||
<button class="piwik_give_consent">Track me!</button>
|
||||
|
||||
# button to remove tracking consent
|
||||
<button class="piwik_remove_consent">Don't track me anymore!</button>
|
||||
|
||||
.. _`asking for consent`: https://developer.matomo.org/guides/tracking-javascript-guide#asking-for-consent
|
||||
|
||||
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:`ANALYTICAL_INTERNAL_IPS` (which
|
||||
takes the value of :const:`INTERNAL_IPS` by default) the tracking code
|
||||
is commented out. See :ref:`identifying-visitors` for important
|
||||
information about detecting the visitor IP address.
|
||||
|
||||
|
||||
----
|
||||
|
||||
Thanks go to Piwik for providing an excellent web analytics platform
|
||||
entirely for free! Consider donating_ to ensure that they continue
|
||||
their development efforts in the spirit of open source and freedom
|
||||
for our personal data.
|
||||
|
||||
.. _donating: http://piwik.org/donate/
|
||||
|
|
@ -53,7 +53,7 @@ Setting the counter ID
|
|||
|
||||
Every website you track with Rating\@Mail.ru gets its own counter ID,
|
||||
and the :ttag:`rating_mailru` tag will include it in the rendered
|
||||
Javascript code. You can find the web counter ID on the overview page
|
||||
JavaScript code. You can find the web counter ID on the overview page
|
||||
of your account. Set :const:`RATING_MAILRU_COUNTER_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`snapengage-configuration`.
|
||||
|
||||
The SnapEngage Javascript code is inserted into templates using a
|
||||
The SnapEngage JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`SnapEngage` template tag library and
|
||||
insert the :ttag:`SnapEngage` tag. Because every page that you want to
|
||||
track must have the tag, it is useful to add it to your base template.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
Spring Metrics -- conversion tracking
|
||||
=====================================
|
||||
|
||||
`Spring Metrics`_ is a convesions analysis tool. It shows you the top
|
||||
`Spring Metrics`_ is a conversions analysis tool. It shows you the top
|
||||
converting sources, search keywords and landing pages. The real-time
|
||||
dashboard shows you how customers interact with your website and how
|
||||
to increase conversion.
|
||||
|
|
@ -53,7 +53,7 @@ Setting the Tracking ID
|
|||
|
||||
Every website you track with Spring Metrics gets its own Tracking ID,
|
||||
and the :ttag:`spring_metrics` tag will include it in the rendered
|
||||
Javascript code. You can find the Tracking ID in the `Site Settings`_
|
||||
JavaScript code. You can find the Tracking ID in the `Site Settings`_
|
||||
of your Spring Metrics account. Set :const:`SPRING_METRICS_TRACKING_ID`
|
||||
in the project :file:`settings.py` file::
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ This step is only needed if you are not using the generic
|
|||
:ttag:`analytical.*` tags. If you are, skip to
|
||||
:ref:`uservoice-configuration`.
|
||||
|
||||
The UserVoice Javascript code is inserted into templates using a
|
||||
The UserVoice JavaScript code is inserted into templates using a
|
||||
template tag. Load the :mod:`uservoice` template tag library and insert
|
||||
the :ttag:`uservoice` tag. Because every page that you want to have
|
||||
the feedback tab to appear on must have the tag, it is useful to add
|
||||
|
|
@ -57,12 +57,12 @@ Setting the widget key
|
|||
|
||||
In order to use the feedback widget, you need to configure which widget
|
||||
you want to show. You can find the widget keys in the *Channels* tab on
|
||||
your UserVoice *Settings* page. Under the *Javascript Widget* heading,
|
||||
find the Javascript embed code of the widget. The widget key is the
|
||||
your UserVoice *Settings* page. Under the *JavaScript Widget* heading,
|
||||
find the JavaScript embed code of the widget. The widget key is the
|
||||
alphanumerical string contained in the URL of the script imported by the
|
||||
embed code::
|
||||
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
|
||||
UserVoice=window.UserVoice||[];(function(){
|
||||
var uv=document.createElement('script');uv.type='text/javascript';
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ the tag at the bottom of the HTML head::
|
|||
</head>
|
||||
...
|
||||
|
||||
Because Javascript code is asynchronous, putting the tag in the head
|
||||
Because JavaScript code is asynchronous, putting the tag in the head
|
||||
section increases the chances that a page view is going to be tracked
|
||||
before the visitor leaves the page. See for details the `Asynchronous
|
||||
JavaScript Developer’s Guide`_ on the Woopra website.
|
||||
|
|
@ -94,6 +94,38 @@ a page reporting. So it’s important to keep the number reasonable in
|
|||
order to accurately make predictions.
|
||||
|
||||
|
||||
Cookie control
|
||||
--------------
|
||||
|
||||
Optional settings that influence the cookie behavior::
|
||||
|
||||
WOOPRA_COOKIE_NAME = "wooTracker"
|
||||
WOOPRA_COOKIE_DOMAIN = ".example.com"
|
||||
WOOPRA_COOKIE_PATH = "/"
|
||||
WOOPRA_COOKIE_EXPIRE = "Fri Jan 01 2027 15:00:00 GMT+0000"
|
||||
|
||||
See the `Woopra documentation`_ for more specific details.
|
||||
|
||||
|
||||
Tracking control
|
||||
----------------
|
||||
|
||||
Optional settings that influence the tracking as such::
|
||||
|
||||
WOOPRA_CLICK_TRACKING = False
|
||||
WOOPRA_DOWNLOAD_TRACKING = False
|
||||
WOOPRA_OUTGOING_TRACKING = False
|
||||
WOOPRA_OUTGOING_IGNORE_SUBDOMAIN = True
|
||||
WOOPRA_IGNORE_QUERY_URL = True
|
||||
WOOPRA_HIDE_CAMPAIGN = False
|
||||
|
||||
See the `Woopra documentation`_ for more specific details.
|
||||
|
||||
|
||||
.. _`Woopra documentation`:
|
||||
https://docs.woopra.com/reference/woopraconfig#configuring-your-tracker
|
||||
|
||||
|
||||
Internal IP addresses
|
||||
---------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Setting the counter ID
|
|||
|
||||
Every website you track with Yandex.Metrica gets its own counter ID,
|
||||
and the :ttag:`yandex_metrica` tag will include it in the rendered
|
||||
Javascript code. You can find the web counter ID on the overview page
|
||||
JavaScript code. You can find the web counter ID on the overview page
|
||||
of your account. Set :const:`YANDEX_METRICA_COUNTER_ID` in the
|
||||
project :file:`settings.py` file::
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ source = ["analytical"]
|
|||
[tool.pytest.ini_options]
|
||||
addopts = "--junitxml=tests/unittests-report.xml --color=yes --verbose"
|
||||
DJANGO_SETTINGS_MODULE = "tests.testproject.settings"
|
||||
norecursedirs = ["playground"]
|
||||
# norecursedirs = ["playground"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "single"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Django>=1.7.0
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
[build_sphinx]
|
||||
source-dir = docs
|
||||
build-dir = build/docs
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = build/docs/html
|
||||
99
setup.py
99
setup.py
|
|
@ -1,99 +0,0 @@
|
|||
import os
|
||||
|
||||
try:
|
||||
from setuptools import setup, Command
|
||||
except ImportError:
|
||||
from distutils.core import setup, Command
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'analytical.tests.settings'
|
||||
|
||||
cmdclass = {}
|
||||
|
||||
try:
|
||||
from sphinx.setup_command import BuildDoc
|
||||
|
||||
cmdclass['build_sphinx'] = BuildDoc
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class TestCommand(Command):
|
||||
description = "run package tests"
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
pass
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
from analytical.tests.utils import run_tests
|
||||
|
||||
run_tests()
|
||||
|
||||
|
||||
cmdclass['test'] = TestCommand
|
||||
|
||||
|
||||
def read_file(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
|
||||
try:
|
||||
import django
|
||||
os.environ.setdefault(
|
||||
"DJANGO_SETTINGS_MODULE",
|
||||
"analytical.tests.settings"
|
||||
)
|
||||
django.setup()
|
||||
except ImportError:
|
||||
print(
|
||||
"Could not import django. "
|
||||
"This is fine, unless you intend to run unit tests."
|
||||
)
|
||||
|
||||
import analytical as package # noqa
|
||||
|
||||
setup(
|
||||
name='django-analytical',
|
||||
version=package.__version__,
|
||||
license=package.__license__,
|
||||
description=package.__doc__.strip(),
|
||||
long_description=read_file('README.rst'),
|
||||
long_description_content_type='text/x-rst',
|
||||
author=package.__author__,
|
||||
author_email=package.__email__,
|
||||
packages=[
|
||||
'analytical',
|
||||
'analytical.templatetags',
|
||||
'analytical.tests',
|
||||
'analytical.tests.templatetags',
|
||||
],
|
||||
keywords=['django', 'analytics'],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Framework :: Django :: 2.1',
|
||||
'Framework :: Django :: 2.2',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 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/jazzband/django-analytical',
|
||||
download_url='https://github.com/jazzband/django-analytical/archive/master.zip',
|
||||
cmdclass=cmdclass,
|
||||
)
|
||||
|
|
@ -29,3 +29,5 @@ TEMPLATES = [
|
|||
'APP_DIRS': True,
|
||||
},
|
||||
]
|
||||
|
||||
USE_TZ = False
|
||||
|
|
@ -2,24 +2,22 @@
|
|||
Dummy testing template tags and filters.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.templatetags.analytical import TAG_LOCATIONS
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
def _location_node(location):
|
||||
class DummyNode(Node):
|
||||
def render(self, context):
|
||||
return "<!-- dummy_%s -->" % location
|
||||
return '<!-- dummy_%s -->' % location
|
||||
|
||||
return DummyNode
|
||||
|
||||
|
||||
_location_nodes = dict((l, _location_node(l)) for l in TAG_LOCATIONS)
|
||||
_location_nodes = {loc: _location_node(loc) for loc in TAG_LOCATIONS}
|
||||
|
||||
|
||||
def _location_tag(location):
|
||||
|
|
@ -28,6 +26,7 @@ def _location_tag(location):
|
|||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0])
|
||||
return _location_nodes[location]
|
||||
|
||||
return dummy_tag
|
||||
|
||||
|
||||
|
|
@ -3,9 +3,9 @@ Tests for the generic template tags and filters.
|
|||
"""
|
||||
|
||||
from django.template import Context, Template
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags import analytical
|
||||
from analytical.tests.utils import TagTestCase
|
||||
|
||||
|
||||
class AnalyticsTagTestCase(TagTestCase):
|
||||
|
|
@ -14,23 +14,23 @@ class AnalyticsTagTestCase(TagTestCase):
|
|||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(AnalyticsTagTestCase, self).setUp()
|
||||
super().setUp()
|
||||
self._tag_modules = analytical.TAG_MODULES
|
||||
analytical.TAG_MODULES = ['analytical.tests.dummy']
|
||||
analytical.TAG_MODULES = ['tests.testproject.dummy']
|
||||
analytical.template_nodes = analytical._load_template_nodes()
|
||||
|
||||
def tearDown(self):
|
||||
analytical.TAG_MODULES = self._tag_modules
|
||||
analytical.template_nodes = analytical._load_template_nodes()
|
||||
super(AnalyticsTagTestCase, self).tearDown()
|
||||
super().tearDown()
|
||||
|
||||
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):
|
||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(l)
|
||||
self.assertTrue('dummy_%s' % l in r, r)
|
||||
for loc in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||
r = self.render_location_tag(loc)
|
||||
assert f'dummy_{loc}' in r
|
||||
|
|
@ -4,14 +4,14 @@ Tests for the Chartbeat template tags and filters.
|
|||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.chartbeat import ChartbeatTopNode, \
|
||||
ChartbeatBottomNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.templatetags.chartbeat import ChartbeatBottomNode, ChartbeatTopNode
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
|
|
@ -22,25 +22,29 @@ class ChartbeatTagTestCaseNoSites(TestCase):
|
|||
self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r)
|
||||
|
||||
|
||||
@override_settings(INSTALLED_APPS=(
|
||||
'analytical',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
))
|
||||
@override_settings(
|
||||
INSTALLED_APPS=(
|
||||
'analytical',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
)
|
||||
)
|
||||
@override_settings(CHARTBEAT_USER_ID='12345')
|
||||
class ChartbeatTagTestCaseWithSites(TestCase):
|
||||
def setUp(self):
|
||||
from django.core.management import call_command
|
||||
call_command("migrate", verbosity=0)
|
||||
|
||||
call_command('migrate', verbosity=0)
|
||||
|
||||
def test_rendering_setup_site(self):
|
||||
from django.contrib.sites.models import Site
|
||||
site = Site.objects.create(domain="test.com", name="test")
|
||||
|
||||
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)
|
||||
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
|
||||
assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r)
|
||||
|
||||
@override_settings(CHARTBEAT_AUTO_DOMAIN=False)
|
||||
def test_auto_domain_false(self):
|
||||
|
|
@ -50,7 +54,7 @@ class ChartbeatTagTestCaseWithSites(TestCase):
|
|||
in _sf_async_config.
|
||||
"""
|
||||
r = ChartbeatBottomNode().render(Context())
|
||||
self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r)
|
||||
assert 'var _sf_async_config={"uid": "12345"};' in r
|
||||
|
||||
|
||||
@override_settings(CHARTBEAT_USER_ID='12345')
|
||||
|
|
@ -60,34 +64,48 @@ class ChartbeatTagTestCase(TagTestCase):
|
|||
"""
|
||||
|
||||
def test_top_tag(self):
|
||||
r = self.render_tag('chartbeat', 'chartbeat_top', {'chartbeat_domain': "test.com"})
|
||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
||||
r = self.render_tag(
|
||||
'chartbeat', 'chartbeat_top', {'chartbeat_domain': 'test.com'}
|
||||
)
|
||||
assert 'var _sf_startpt=(new Date()).getTime()' in 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'}
|
||||
)
|
||||
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
|
||||
assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r)
|
||||
|
||||
def test_top_node(self):
|
||||
r = ChartbeatTopNode().render(Context({
|
||||
'chartbeat_domain': "test.com",
|
||||
}))
|
||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
||||
r = ChartbeatTopNode().render(
|
||||
Context(
|
||||
{
|
||||
'chartbeat_domain': 'test.com',
|
||||
}
|
||||
)
|
||||
)
|
||||
assert 'var _sf_startpt=(new Date()).getTime()' in 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',
|
||||
}
|
||||
)
|
||||
)
|
||||
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
|
||||
assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r)
|
||||
|
||||
@override_settings(CHARTBEAT_USER_ID=None)
|
||||
def test_no_user_id(self):
|
||||
self.assertRaises(AnalyticalException, ChartbeatBottomNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
ChartbeatBottomNode()
|
||||
|
||||
@override_settings(CHARTBEAT_USER_ID='123abc')
|
||||
def test_wrong_user_id(self):
|
||||
self.assertRaises(AnalyticalException, ChartbeatBottomNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
ChartbeatBottomNode()
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
@ -95,6 +113,5 @@ class ChartbeatTagTestCase(TagTestCase):
|
|||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = ChartbeatBottomNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Chartbeat disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
assert r.startswith('<!-- Chartbeat disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
|
@ -2,12 +2,13 @@
|
|||
Tests for the Clickmap template tags and filters.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.clickmap import ClickmapNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
|
|
@ -19,19 +20,21 @@ class ClickmapTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('clickmap', 'clickmap')
|
||||
self.assertTrue("tracker: '12345ABC', version:'2'};" in r, r)
|
||||
assert "tracker: '12345ABC', version:'2'};" in r
|
||||
|
||||
def test_node(self):
|
||||
r = ClickmapNode().render(Context({}))
|
||||
self.assertTrue("tracker: '12345ABC', version:'2'};" in r, r)
|
||||
assert "tracker: '12345ABC', version:'2'};" in r
|
||||
|
||||
@override_settings(CLICKMAP_TRACKER_ID=None)
|
||||
def test_no_site_id(self):
|
||||
self.assertRaises(AnalyticalException, ClickmapNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
ClickmapNode()
|
||||
|
||||
@override_settings(CLICKMAP_TRACKER_ID='ab#c')
|
||||
def test_wrong_site_id(self):
|
||||
self.assertRaises(AnalyticalException, ClickmapNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
ClickmapNode()
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
@ -39,6 +42,5 @@ class ClickmapTagTestCase(TagTestCase):
|
|||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = ClickmapNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Clickmap disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
assert r.startswith('<!-- Clickmap disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
|
@ -4,13 +4,14 @@ Tests for the Clicky template tags and filters.
|
|||
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
import pytest
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.clicky import ClickyNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
|
|
@ -22,40 +23,46 @@ 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)
|
||||
assert 'clicky_site_ids.push(12345678);' in r
|
||||
assert 'src="//in.getclicky.com/12345678ns.gif"' in 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)
|
||||
assert 'clicky_site_ids.push(12345678);' in r
|
||||
assert 'src="//in.getclicky.com/12345678ns.gif"' in r
|
||||
|
||||
@override_settings(CLICKY_SITE_ID=None)
|
||||
def test_no_site_id(self):
|
||||
self.assertRaises(AnalyticalException, ClickyNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
ClickyNode()
|
||||
|
||||
@override_settings(CLICKY_SITE_ID='123abc')
|
||||
def test_wrong_site_id(self):
|
||||
self.assertRaises(AnalyticalException, ClickyNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
ClickyNode()
|
||||
|
||||
@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)
|
||||
assert 'var clicky_custom = {"session": {"username": "test"}};' in r
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify_anonymous_user(self):
|
||||
r = ClickyNode().render(Context({'user': AnonymousUser()}))
|
||||
self.assertFalse('var clicky_custom = {"session": {"username":' in r, r)
|
||||
assert 'var clicky_custom = {"session": {"username":' not in r
|
||||
|
||||
def test_custom(self):
|
||||
r = ClickyNode().render(Context({
|
||||
'clicky_var1': 'val1',
|
||||
'clicky_var2': 'val2',
|
||||
}))
|
||||
self.assertTrue(
|
||||
re.search(r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r),
|
||||
r)
|
||||
r = ClickyNode().render(
|
||||
Context(
|
||||
{
|
||||
'clicky_var1': 'val1',
|
||||
'clicky_var2': 'val2',
|
||||
}
|
||||
)
|
||||
)
|
||||
assert re.search(
|
||||
r'var clicky_custom = {.*"var1": "val1", "var2": "val2".*};', r
|
||||
)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
@ -63,6 +70,5 @@ class ClickyTagTestCase(TagTestCase):
|
|||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = ClickyNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Clicky disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
assert r.startswith('<!-- Clicky disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
|
@ -2,12 +2,13 @@
|
|||
Tests for the Crazy Egg template tags and filters.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.crazy_egg import CrazyEggNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
|
|
@ -19,25 +20,27 @@ class CrazyEggTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('crazy_egg', 'crazy_egg')
|
||||
self.assertTrue('/1234/5678.js' in r, r)
|
||||
assert '/1234/5678.js' in r
|
||||
|
||||
def test_node(self):
|
||||
r = CrazyEggNode().render(Context())
|
||||
self.assertTrue('/1234/5678.js' in r, r)
|
||||
assert '/1234/5678.js' in r
|
||||
|
||||
@override_settings(CRAZY_EGG_ACCOUNT_NUMBER=None)
|
||||
def test_no_account_number(self):
|
||||
self.assertRaises(AnalyticalException, CrazyEggNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
CrazyEggNode()
|
||||
|
||||
@override_settings(CRAZY_EGG_ACCOUNT_NUMBER='123abc')
|
||||
def test_wrong_account_number(self):
|
||||
self.assertRaises(AnalyticalException, CrazyEggNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
CrazyEggNode()
|
||||
|
||||
def test_uservars(self):
|
||||
context = Context({'crazy_egg_var1': 'foo', 'crazy_egg_var2': 'bar'})
|
||||
r = CrazyEggNode().render(context)
|
||||
self.assertTrue("CE2.set(1, 'foo');" in r, r)
|
||||
self.assertTrue("CE2.set(2, 'bar');" in r, r)
|
||||
assert "CE2.set(1, 'foo');" in r
|
||||
assert "CE2.set(2, 'bar');" in r
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
@ -45,6 +48,5 @@ class CrazyEggTagTestCase(TagTestCase):
|
|||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = CrazyEggNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Crazy Egg disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
assert r.startswith('<!-- Crazy Egg disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
"""
|
||||
Tests for the Facebook Pixel template tags.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context, Template, TemplateSyntaxError
|
||||
from django.test import override_settings
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.analytical import _load_template_nodes
|
||||
from analytical.templatetags.facebook_pixel import FacebookPixelHeadNode, FacebookPixelBodyNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.templatetags.facebook_pixel import (
|
||||
FacebookPixelBodyNode,
|
||||
FacebookPixelHeadNode,
|
||||
)
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
expected_head_html = """\
|
||||
<script>
|
||||
!function(f,b,e,v,n,t,s)
|
||||
|
|
@ -36,51 +40,51 @@ expected_body_html = """\
|
|||
|
||||
@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)
|
||||
assert expected_head_html == html
|
||||
|
||||
def test_head_node(self):
|
||||
html = FacebookPixelHeadNode().render(Context({}))
|
||||
self.assertEqual(expected_head_html, html)
|
||||
assert expected_head_html == html
|
||||
|
||||
def test_body_tag(self):
|
||||
html = self.render_tag('facebook_pixel', 'facebook_pixel_body')
|
||||
self.assertEqual(expected_body_html, html)
|
||||
assert expected_body_html == html
|
||||
|
||||
def test_body_node(self):
|
||||
html = FacebookPixelBodyNode().render(Context({}))
|
||||
self.assertEqual(expected_body_html, html)
|
||||
assert 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({}))),
|
||||
)
|
||||
template = '{%% load facebook_pixel %%}{%% facebook_pixel_%s "arg" %%}'
|
||||
with pytest.raises(
|
||||
TemplateSyntaxError, match="'facebook_pixel_head' takes no arguments"
|
||||
):
|
||||
Template(template % 'head').render(Context({}))
|
||||
|
||||
with pytest.raises(
|
||||
TemplateSyntaxError, match="'facebook_pixel_body' takes no arguments"
|
||||
):
|
||||
Template(template % 'body').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)
|
||||
expected_pattern = 'FACEBOOK_PIXEL_ID setting is not set'
|
||||
with pytest.raises(AnalyticalException, match=expected_pattern):
|
||||
FacebookPixelHeadNode()
|
||||
with pytest.raises(AnalyticalException, match=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)
|
||||
expected_pattern = r"FACEBOOK_PIXEL_ID setting: must be \(a string containing\) a number: 'invalid'$"
|
||||
with pytest.raises(AnalyticalException, match=expected_pattern):
|
||||
FacebookPixelHeadNode()
|
||||
with pytest.raises(AnalyticalException, match=expected_pattern):
|
||||
FacebookPixelBodyNode()
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
@ -89,26 +93,28 @@ class FacebookPixelTagTestCase(TagTestCase):
|
|||
context = Context({'request': request})
|
||||
|
||||
def _disabled(html):
|
||||
return '\n'.join([
|
||||
'<!-- Facebook Pixel disabled on internal IP address',
|
||||
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)
|
||||
assert _disabled(expected_head_html) == head_html
|
||||
|
||||
body_html = FacebookPixelBodyNode().render(context)
|
||||
self.assertEqual(_disabled(expected_body_html), body_html)
|
||||
assert _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({
|
||||
assert template_nodes == {
|
||||
'head_top': [],
|
||||
'head_bottom': [FacebookPixelHeadNode],
|
||||
'body_top': [],
|
||||
'body_bottom': [FacebookPixelBodyNode],
|
||||
}, template_nodes)
|
||||
}
|
||||
|
|
@ -2,12 +2,13 @@
|
|||
Tests for the Gauges template tags and filters.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
from utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.gauges import GaugesNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
|
|
@ -18,8 +19,10 @@ class GaugesTagTestCase(TagTestCase):
|
|||
"""
|
||||
|
||||
def test_tag(self):
|
||||
self.assertEqual("""
|
||||
<script type="text/javascript">
|
||||
assert (
|
||||
self.render_tag('gauges', 'gauges')
|
||||
== """
|
||||
<script>
|
||||
var _gauges = _gauges || [];
|
||||
(function() {
|
||||
var t = document.createElement('script');
|
||||
|
|
@ -32,12 +35,14 @@ class GaugesTagTestCase(TagTestCase):
|
|||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
""", self.render_tag('gauges', 'gauges'))
|
||||
"""
|
||||
)
|
||||
|
||||
def test_node(self):
|
||||
self.assertEqual(
|
||||
"""
|
||||
<script type="text/javascript">
|
||||
assert (
|
||||
GaugesNode().render(Context())
|
||||
== """
|
||||
<script>
|
||||
var _gauges = _gauges || [];
|
||||
(function() {
|
||||
var t = document.createElement('script');
|
||||
|
|
@ -50,11 +55,13 @@ class GaugesTagTestCase(TagTestCase):
|
|||
s.parentNode.insertBefore(t, s);
|
||||
})();
|
||||
</script>
|
||||
""", GaugesNode().render(Context()))
|
||||
"""
|
||||
)
|
||||
|
||||
@override_settings(GAUGES_SITE_ID=None)
|
||||
def test_no_account_number(self):
|
||||
self.assertRaises(AnalyticalException, GaugesNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
GaugesNode()
|
||||
|
||||
@override_settings(GAUGES_SITE_ID='123abQ')
|
||||
def test_wrong_account_number(self):
|
||||
|
|
@ -66,6 +73,5 @@ class GaugesTagTestCase(TagTestCase):
|
|||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = GaugesNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Gauges disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
assert r.startswith('<!-- Gauges disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
|
@ -2,19 +2,28 @@
|
|||
Tests for the Google Analytics template tags and filters.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context
|
||||
from django.test.utils import override_settings
|
||||
from utils import TagTestCase, TestCase
|
||||
|
||||
from analytical.templatetags.google_analytics import GoogleAnalyticsNode, \
|
||||
TRACK_SINGLE_DOMAIN, TRACK_MULTIPLE_DOMAINS, TRACK_MULTIPLE_SUBDOMAINS,\
|
||||
SCOPE_VISITOR, SCOPE_SESSION, SCOPE_PAGE
|
||||
from analytical.tests.utils import TestCase, TagTestCase
|
||||
from analytical.templatetags.google_analytics import (
|
||||
SCOPE_PAGE,
|
||||
SCOPE_SESSION,
|
||||
SCOPE_VISITOR,
|
||||
TRACK_MULTIPLE_DOMAINS,
|
||||
TRACK_MULTIPLE_SUBDOMAINS,
|
||||
TRACK_SINGLE_DOMAIN,
|
||||
GoogleAnalyticsNode,
|
||||
)
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7',
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN)
|
||||
@override_settings(
|
||||
GOOGLE_ANALYTICS_PROPERTY_ID='UA-123456-7',
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_SINGLE_DOMAIN,
|
||||
)
|
||||
class GoogleAnalyticsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``google_analytics`` template tag.
|
||||
|
|
@ -22,62 +31,70 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
|
|||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('google_analytics', 'google_analytics')
|
||||
self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_trackPageview']);" in r, r)
|
||||
assert "_gaq.push(['_setAccount', 'UA-123456-7']);" in r
|
||||
assert "_gaq.push(['_trackPageview']);" in r
|
||||
|
||||
def test_node(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push(['_setAccount', 'UA-123456-7']);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_trackPageview']);" in r, r)
|
||||
assert "_gaq.push(['_setAccount', 'UA-123456-7']);" in r
|
||||
assert "_gaq.push(['_trackPageview']);" in r
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID=None)
|
||||
def test_no_property_id(self):
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode)
|
||||
with pytest.raises(AnalyticalException):
|
||||
GoogleAnalyticsNode()
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_PROPERTY_ID='wrong')
|
||||
def test_wrong_property_id(self):
|
||||
self.assertRaises(AnalyticalException, GoogleAnalyticsNode)
|
||||
with pytest.raises(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)
|
||||
assert "_gaq.push(['_setDomainName', 'example.com']);" in r
|
||||
assert "_gaq.push(['_setAllowHash', false]);" in r
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
GOOGLE_ANALYTICS_DOMAIN='example.com')
|
||||
@override_settings(
|
||||
GOOGLE_ANALYTICS_TRACKING_STYLE=TRACK_MULTIPLE_DOMAINS,
|
||||
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)
|
||||
self.assertTrue("_gaq.push(['_setAllowHash', false]);" in r, r)
|
||||
self.assertTrue("_gaq.push(['_setAllowLinker', true]);" in r, r)
|
||||
assert "_gaq.push(['_setDomainName', 'example.com']);" in r
|
||||
assert "_gaq.push(['_setAllowHash', false]);" in r
|
||||
assert "_gaq.push(['_setAllowLinker', true]);" in r
|
||||
|
||||
def test_custom_vars(self):
|
||||
context = Context({
|
||||
'google_analytics_var1': ('test1', 'foo'),
|
||||
'google_analytics_var2': ('test2', 'bar', SCOPE_VISITOR),
|
||||
'google_analytics_var4': ('test4', 'baz', SCOPE_SESSION),
|
||||
'google_analytics_var5': ('test5', 'qux', SCOPE_PAGE),
|
||||
})
|
||||
context = Context(
|
||||
{
|
||||
'google_analytics_var1': ('test1', 'foo'),
|
||||
'google_analytics_var2': ('test2', 'bar', SCOPE_VISITOR),
|
||||
'google_analytics_var4': ('test4', 'baz', SCOPE_SESSION),
|
||||
'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)
|
||||
assert "_gaq.push(['_setCustomVar', 1, 'test1', 'foo', 3]);" in r
|
||||
assert "_gaq.push(['_setCustomVar', 2, 'test2', 'bar', 1]);" in r
|
||||
assert "_gaq.push(['_setCustomVar', 4, 'test4', 'baz', 2]);" in r
|
||||
assert "_gaq.push(['_setCustomVar', 5, 'test5', 'qux', 3]);" in r
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_SITE_SPEED=True)
|
||||
def test_track_page_load_time(self):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("_gaq.push(['_trackPageLoadTime']);" in r, r)
|
||||
assert "_gaq.push(['_trackPageLoadTime']);" in r
|
||||
|
||||
def test_display_advertising(self):
|
||||
with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=False):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("google-analytics.com/ga.js" in r, r)
|
||||
assert 'google-analytics.com/ga.js' in r
|
||||
with override_settings(GOOGLE_ANALYTICS_DISPLAY_ADVERTISING=True):
|
||||
r = GoogleAnalyticsNode().render(Context())
|
||||
self.assertTrue("stats.g.doubleclick.net/dc.js" in r, r)
|
||||
assert 'stats.g.doubleclick.net/dc.js' in r
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
|
|
@ -85,97 +102,105 @@ class GoogleAnalyticsTagTestCase(TagTestCase):
|
|||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': req})
|
||||
r = GoogleAnalyticsNode().render(context)
|
||||
self.assertTrue(r.startswith(
|
||||
'<!-- Google Analytics disabled on internal IP address'), r)
|
||||
self.assertTrue(r.endswith('-->'), r)
|
||||
assert r.startswith('<!-- Google Analytics disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
||||
@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(r.index('_gat._anonymizeIp') < r.index('_trackPageview'), r)
|
||||
assert "_gaq.push(['_gat._anonymizeIp']);" in r
|
||||
assert r.index('_gat._anonymizeIp') < r.index('_trackPageview')
|
||||
|
||||
@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)
|
||||
assert "_gaq.push(['_gat._anonymizeIp']);" not in 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)
|
||||
assert "_gaq.push(['_setSampleRate', '0.00']);" in 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)
|
||||
assert "_gaq.push(['_setSampleRate', '100.00']);" in 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)
|
||||
with pytest.raises(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)
|
||||
with pytest.raises(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)
|
||||
assert "_gaq.push(['_setSiteSpeedSampleRate', '0.00']);" in 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)
|
||||
assert "_gaq.push(['_setSiteSpeedSampleRate', '100.00']);" in 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)
|
||||
with pytest.raises(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)
|
||||
with pytest.raises(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)
|
||||
assert "_gaq.push(['_setSessionCookieTimeout', '0']);" in 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)
|
||||
assert "_gaq.push(['_setSessionCookieTimeout', '10000']);" in 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)
|
||||
with pytest.raises(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)
|
||||
assert "_gaq.push(['_setVisitorCookieTimeout', '0']);" in 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)
|
||||
assert "_gaq.push(['_setVisitorCookieTimeout', '10000']);" in 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)
|
||||
with pytest.raises(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)
|
||||
@override_settings(
|
||||
GOOGLE_ANALYTICS_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, GoogleAnalyticsNode().render, context)
|
||||
with pytest.raises(AnalyticalException):
|
||||
GoogleAnalyticsNode().render(context)
|
||||
164
tests/unit/test_tag_google_analytics_gtag.py
Normal file
164
tests/unit/test_tag_google_analytics_gtag.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
"""
|
||||
Tests for the Google Analytics template tags and filters, using the new gtag.js library.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
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 utils import TagTestCase
|
||||
|
||||
from analytical.templatetags.google_analytics_gtag import GoogleAnalyticsGTagNode
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='UA-123456-7')
|
||||
class GoogleAnalyticsTagTestCase(TagTestCase):
|
||||
"""
|
||||
Tests for the ``google_analytics_gtag`` template tag.
|
||||
"""
|
||||
|
||||
def test_tag(self):
|
||||
r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag')
|
||||
assert (
|
||||
'<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script>'
|
||||
) in r
|
||||
assert "gtag('js', new Date());" in r
|
||||
assert "gtag('config', 'UA-123456-7', {});" in r
|
||||
|
||||
def test_node(self):
|
||||
r = GoogleAnalyticsGTagNode().render(Context())
|
||||
assert (
|
||||
'<script async src="https://www.googletagmanager.com/gtag/js?id=UA-123456-7"></script>'
|
||||
) in r
|
||||
assert "gtag('js', new Date());" in r
|
||||
assert "gtag('config', 'UA-123456-7', {});" in r
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID=None)
|
||||
def test_no_property_id(self):
|
||||
with pytest.raises(AnalyticalException):
|
||||
GoogleAnalyticsGTagNode()
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='wrong')
|
||||
def test_wrong_property_id(self):
|
||||
with pytest.raises(AnalyticalException):
|
||||
GoogleAnalyticsGTagNode()
|
||||
|
||||
@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 = GoogleAnalyticsGTagNode().render(context)
|
||||
assert r.startswith('<!-- Google Analytics disabled on internal IP address')
|
||||
assert r.endswith('-->')
|
||||
|
||||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True)
|
||||
def test_identify(self):
|
||||
r = GoogleAnalyticsGTagNode().render(Context({'user': User(username='test')}))
|
||||
assert 'gtag(\'config\', \'UA-123456-7\', {"user_id": "test"});' in r
|
||||
|
||||
def test_identity_context_specific_provider(self):
|
||||
"""
|
||||
The user_id variable must be set according to
|
||||
google_analytics_gtag_identity variable in the context.
|
||||
"""
|
||||
r = GoogleAnalyticsGTagNode().render(
|
||||
Context(
|
||||
{
|
||||
'google_analytics_gtag_identity': 'foo_gtag_identity',
|
||||
'user': User(username='test'),
|
||||
}
|
||||
)
|
||||
)
|
||||
assert (
|
||||
'gtag(\'config\', \'UA-123456-7\', {"user_id": "foo_gtag_identity"});' in r
|
||||
)
|
||||
|
||||
def test_identity_context_general(self):
|
||||
"""
|
||||
The user_id variable must be set according to analytical_identity variable in the context.
|
||||
"""
|
||||
r = GoogleAnalyticsGTagNode().render(
|
||||
Context(
|
||||
{
|
||||
'analytical_identity': 'bar_analytical_identity',
|
||||
'user': User(username='test'),
|
||||
}
|
||||
)
|
||||
)
|
||||
assert (
|
||||
'gtag(\'config\', \'UA-123456-7\', {"user_id": "bar_analytical_identity"});'
|
||||
in r
|
||||
)
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='G-12345678')
|
||||
def test_tag_with_measurement_id(self):
|
||||
r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag')
|
||||
assert (
|
||||
'<script async src="https://www.googletagmanager.com/gtag/js?id=G-12345678"></script>'
|
||||
) in r
|
||||
assert "gtag('js', new Date());" in r
|
||||
assert "gtag('config', 'G-12345678', {});" in r
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='AW-1234567890')
|
||||
def test_tag_with_conversion_id(self):
|
||||
r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag')
|
||||
assert (
|
||||
'<script async src="https://www.googletagmanager.com/gtag/js?id=AW-1234567890"></script'
|
||||
) in r
|
||||
assert "gtag('js', new Date());" in r
|
||||
assert "gtag('config', 'AW-1234567890', {});" in r
|
||||
|
||||
@override_settings(GOOGLE_ANALYTICS_GTAG_PROPERTY_ID='DC-12345678')
|
||||
def test_tag_with_advertiser_id(self):
|
||||
r = self.render_tag('google_analytics_gtag', 'google_analytics_gtag')
|
||||
assert (
|
||||
'<script async src="https://www.googletagmanager.com/gtag/js?id=DC-12345678"></script>'
|
||||
) in r
|
||||
assert "gtag('js', new Date());" in r
|
||||
assert "gtag('config', 'DC-12345678', {});" in r
|
||||
|
||||
def test_tag_with_custom_dimensions(self):
|
||||
r = GoogleAnalyticsGTagNode().render(
|
||||
Context(
|
||||
{
|
||||
'google_analytics_custom_dimensions': {
|
||||
'dimension_1': 'foo',
|
||||
'dimension_2': 'bar',
|
||||
'user_properties': {
|
||||
'user_property_1': True,
|
||||
'user_property_2': 'xyz',
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
assert (
|
||||
"gtag('config', 'UA-123456-7', {"
|
||||
'"dimension_1": "foo", '
|
||||
'"dimension_2": "bar", '
|
||||
'"user_properties": {'
|
||||
'"user_property_1": true, '
|
||||
'"user_property_2": "xyz"}});' in r
|
||||
)
|
||||
|
||||
def test_tag_with_identity_and_custom_dimensions(self):
|
||||
r = GoogleAnalyticsGTagNode().render(
|
||||
Context(
|
||||
{
|
||||
'google_analytics_gtag_identity': 'foo_gtag_identity',
|
||||
'google_analytics_custom_dimensions': {
|
||||
'dimension_1': 'foo',
|
||||
'dimension_2': 'bar',
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
assert (
|
||||
"gtag('config', 'UA-123456-7', {"
|
||||
'"dimension_1": "foo", '
|
||||
'"dimension_2": "bar", '
|
||||
'"user_id": "foo_gtag_identity"});' in r
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue