mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Compare commits
223 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6f12719cc | ||
|
|
694fc9097a | ||
|
|
10102d1017 | ||
|
|
033d2dc02f | ||
|
|
4ea4605791 | ||
|
|
7253a3a048 | ||
|
|
a751ffa5e3 | ||
|
|
8b12e33e69 | ||
|
|
e0f95bc86b | ||
|
|
110d5bf361 | ||
|
|
0f099b8ab4 | ||
|
|
18b5a73bd3 | ||
|
|
df5b04b932 | ||
|
|
7704f8c56b | ||
|
|
25d08a8633 | ||
|
|
a246d62ae0 | ||
|
|
0d87a76da0 | ||
|
|
6850710413 | ||
|
|
4540999dc1 | ||
|
|
d3ddf1ac6a | ||
|
|
1dd5b2ac62 | ||
|
|
a112ec445f | ||
|
|
82fbbeb6b2 | ||
|
|
8d6419eec8 | ||
|
|
6e367cdd2d | ||
|
|
2163c77684 | ||
|
|
858a05307b | ||
|
|
19c43bef4a | ||
|
|
5ad082df48 | ||
|
|
47a5a4d6a9 | ||
|
|
f56a779b7a | ||
|
|
a82bde708a | ||
|
|
83e4361d2a | ||
|
|
25979fd0c2 | ||
|
|
96f1ba98b1 | ||
|
|
7e28ee31f1 | ||
|
|
e596e20183 | ||
|
|
248e04aef5 | ||
|
|
f487cb895c | ||
|
|
688524305f | ||
|
|
ba59f396df | ||
|
|
844a178c04 | ||
|
|
ce90933ab1 | ||
|
|
a8be4ea814 | ||
|
|
d82ca0aed5 | ||
|
|
52aa88f08a | ||
|
|
023702fa75 | ||
|
|
1d9a3b5626 | ||
|
|
2fd2279ecb | ||
|
|
a021e07e15 | ||
|
|
051d46ceda | ||
|
|
8d449868da | ||
|
|
44e642c34b | ||
|
|
41b8c243d4 | ||
|
|
5daa0c5329 | ||
|
|
e4399d63a1 | ||
|
|
0b162449c5 | ||
|
|
58aa95df34 | ||
|
|
aa29a051bd | ||
|
|
dbd7414b33 | ||
|
|
1923108e93 | ||
|
|
a615b954ae | ||
|
|
392cbba489 | ||
|
|
37009f2e08 | ||
|
|
f0e7ebe731 | ||
|
|
baaa1c4a8b | ||
|
|
896a19196f | ||
|
|
8cd75d9e60 | ||
|
|
a3fe981f76 | ||
|
|
514780aeb2 | ||
|
|
7f1358dcb6 | ||
|
|
5ea7d15868 | ||
|
|
47fa937910 | ||
|
|
bf471d7dfc | ||
|
|
a9d7d17ce6 | ||
|
|
ba8982be0a | ||
|
|
00bc123b88 | ||
|
|
4515fcd1be | ||
|
|
9163294603 | ||
|
|
3eb17007ad | ||
|
|
df7ed2d132 | ||
|
|
10d109eb6c | ||
|
|
146a96fca0 | ||
|
|
98e7e24e3e | ||
|
|
161983fad5 | ||
|
|
197cdbcfad | ||
|
|
7580bc1619 | ||
|
|
de2ed4bafc | ||
|
|
2322f7a0bf | ||
|
|
1ebc2a23ea | ||
|
|
b9320b41d9 | ||
|
|
e517bce3a6 | ||
|
|
ef40ba6ff5 | ||
|
|
ee956230ea | ||
|
|
d98e815493 | ||
|
|
1cdfb0ed0d | ||
|
|
3292b664ec | ||
|
|
ac2ebf375c | ||
|
|
7e68563849 | ||
|
|
788cab447e | ||
|
|
88197ca17e | ||
|
|
438b1408fa | ||
|
|
962af837af | ||
|
|
c10759d378 | ||
|
|
d9b21e96c6 | ||
|
|
77fdb51432 | ||
|
|
1ce265d2d9 | ||
|
|
d14d4727ee | ||
|
|
63adb0fbba | ||
|
|
0ea5d39155 | ||
|
|
756ac787e8 | ||
|
|
637805e003 | ||
|
|
5487fd677b | ||
|
|
724e4ddac4 | ||
|
|
c7f8dc21ad | ||
|
|
84e7dab3d2 | ||
|
|
b96bf0050a | ||
|
|
d5de14ebdc | ||
|
|
02b0768c97 | ||
|
|
ef0b19ccb2 | ||
|
|
3b1ab2bbed | ||
|
|
e5f8c199dc | ||
|
|
bfe92c716c | ||
|
|
367606c12c | ||
|
|
dc975aa2d3 | ||
|
|
29a95f0369 | ||
|
|
3680872619 | ||
|
|
4e874d88ae | ||
|
|
4a07cb35e4 | ||
|
|
c4934b4c96 | ||
|
|
81502c3d68 | ||
|
|
456ab03a7d | ||
|
|
005c00c272 | ||
|
|
cb02a33b9f | ||
|
|
b6ed31e034 | ||
|
|
fb4f1c1b40 | ||
|
|
1164f75969 | ||
|
|
ba89b80695 | ||
|
|
1ac44107df | ||
|
|
abde59822d | ||
|
|
a5ca5c7ae1 | ||
|
|
4f4de63759 | ||
|
|
deba4e0021 | ||
|
|
d2c782c1d5 | ||
|
|
32c45f2f0c | ||
|
|
e91615b754 | ||
|
|
d08da39fb1 | ||
|
|
3007bca61e | ||
|
|
de3c2d73d1 | ||
|
|
c2a11ce794 | ||
|
|
2d8cbd25bd | ||
|
|
736472587d | ||
|
|
11e0372017 | ||
|
|
bf3a1aebde | ||
|
|
d7fd927b2c | ||
|
|
d33d380dd4 | ||
|
|
771848b428 | ||
|
|
30e9ddc7cf | ||
|
|
b837a20ed4 | ||
|
|
d76c72e2a5 | ||
|
|
f8c4f6bf47 | ||
|
|
1b7429c3e1 | ||
|
|
4b4f26f54e | ||
|
|
47cf9aac3e | ||
|
|
128c535550 | ||
|
|
c34d429f57 | ||
|
|
0a549c8ee6 | ||
|
|
926b46dc03 | ||
|
|
8f4a62ac6d | ||
|
|
7b3c107758 | ||
|
|
ac66302ddc | ||
|
|
3653b388fd | ||
|
|
dfc8baf2ee | ||
|
|
a9d0befa46 | ||
|
|
4c72289ac8 | ||
|
|
7bc1467374 | ||
|
|
2e9329dbb8 | ||
|
|
be7b530f7e | ||
|
|
ef44d0ccf1 | ||
|
|
9738f31688 | ||
|
|
4e8644fbdc | ||
|
|
347df54502 | ||
|
|
77e1d6f95a | ||
|
|
6f71ddf04f | ||
|
|
5f0b1128f2 | ||
|
|
2ea2f824e8 | ||
|
|
be564dd2b8 | ||
|
|
621d207944 | ||
|
|
969377b39a | ||
|
|
a7246fbe44 | ||
|
|
70b2ff9081 | ||
|
|
d19e1bfdb7 | ||
|
|
cd9394467e | ||
|
|
2ee99a656b | ||
|
|
4157c55e26 | ||
|
|
4ada8ff81a | ||
|
|
c7b14bdf5c | ||
|
|
bec45403ee | ||
|
|
9df1182877 | ||
|
|
22403f9a03 | ||
|
|
add3baff74 | ||
|
|
e1d060398c | ||
|
|
eaa9610cca | ||
|
|
8785a55a5a | ||
|
|
0b38497524 | ||
|
|
67ee8bdb50 | ||
|
|
7da975c08c | ||
|
|
7d96f8e618 | ||
|
|
e8e2d84158 | ||
|
|
6f1db73026 | ||
|
|
7ef1c9fd31 | ||
|
|
6eeb809ea2 | ||
|
|
212394a30d | ||
|
|
d9b482b8c7 | ||
|
|
5a82e9b446 | ||
|
|
4221a1a3ed | ||
|
|
1cd95cd77b | ||
|
|
9cf5c4995e | ||
|
|
d579ec2be0 | ||
|
|
2f22762010 | ||
|
|
f869323aeb | ||
|
|
71e8538451 | ||
|
|
1cb458fe7c |
129 changed files with 5266 additions and 2157 deletions
|
|
@ -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 }}
|
||||||
21
.gitignore
vendored
21
.gitignore
vendored
|
|
@ -2,15 +2,22 @@
|
||||||
/*.geany
|
/*.geany
|
||||||
/.idea
|
/.idea
|
||||||
/.tox
|
/.tox
|
||||||
/.coverage
|
/.vscode
|
||||||
|
|
||||||
/build
|
|
||||||
/dist
|
|
||||||
/MANIFEST
|
|
||||||
|
|
||||||
/docs/_templates/layout.html
|
|
||||||
|
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
|
|
||||||
|
/.coverage
|
||||||
|
/coverage.xml
|
||||||
|
/tests/*-report.json
|
||||||
|
/tests/*-report.xml
|
||||||
|
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
/docs/_build
|
||||||
|
/docs/_templates/layout.html
|
||||||
|
/MANIFEST
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
||||||
|
/requirements.txt
|
||||||
|
/uv.lock
|
||||||
|
|
|
||||||
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
|
||||||
23
.travis.yml
23
.travis.yml
|
|
@ -1,23 +0,0 @@
|
||||||
language: python
|
|
||||||
python: "3.5"
|
|
||||||
install:
|
|
||||||
# continue to support Python 3.2 (see issue #84)
|
|
||||||
- pip install "virtualenv<14.0.0"
|
|
||||||
- pip install coveralls tox
|
|
||||||
script:
|
|
||||||
- tox
|
|
||||||
env:
|
|
||||||
# NOTE: To generate (update) the env list run
|
|
||||||
# $ tox -l | sort | xargs -I ITEM echo " - TOXENV="ITEM
|
|
||||||
- TOXENV=py27-django17
|
|
||||||
- TOXENV=py27-django18
|
|
||||||
- TOXENV=py27-django19
|
|
||||||
- TOXENV=py32-django17
|
|
||||||
- TOXENV=py32-django18
|
|
||||||
- TOXENV=py33-django17
|
|
||||||
- TOXENV=py33-django18
|
|
||||||
- TOXENV=py34-django17
|
|
||||||
- TOXENV=py34-django18
|
|
||||||
- TOXENV=py34-django19
|
|
||||||
- TOXENV=py35-django18
|
|
||||||
- TOXENV=py35-django19
|
|
||||||
42
AUTHORS.rst
42
AUTHORS.rst
|
|
@ -1,42 +0,0 @@
|
||||||
The django-analytical package was written by `Joost Cassee`_, with
|
|
||||||
contributions from `Eric Davis`_, `Paul Oswald`_, `Uros Trebec`_,
|
|
||||||
`Steven Skoczen`_, `Piet Delport`_, `Sandra Mau`_, `Simon Ye`_,
|
|
||||||
`Tinnet Coronam`_, `Philippe O. Wagner`_, `Max Arnold`_ , `Martín
|
|
||||||
Gaitán`_, `Craig Bruce`_, `Peter Bittner`_, `Scott Adams`_, `Eric Amador`_,
|
|
||||||
`Alexandre Pocquet`_, `Brad Pitcher`_, `Hugo Osvaldo Barrera`_,
|
|
||||||
`Nikolay Korotkiy`_, `Steve Schwarz`_, `Aleck Landgraf`_ and others.
|
|
||||||
|
|
||||||
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`: mailto:joost@cassee.net
|
|
||||||
.. _`Eric Davis`: https://github.com/edavis
|
|
||||||
.. _`Paul Oswald`: https://github.com/poswald
|
|
||||||
.. _`Uros Trebec`: https://github.com/failedguidedog
|
|
||||||
.. _`Steven Skoczen`: https://github.com/skoczen
|
|
||||||
.. _`Piet Delport`: https://github.com/pjdelport
|
|
||||||
.. _`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
|
|
||||||
.. _`Analytical`: https://github.com/jkrall/analytical
|
|
||||||
.. _`Bateau Knowledge`: http://www.bateauknowledge.nl/
|
|
||||||
.. _`GreenKahuna`: http://www.greenkahuna.com/
|
|
||||||
|
|
@ -1,10 +1,83 @@
|
||||||
|
(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)
|
||||||
|
* Ask site visitors for consent when using Matomo (Julian Haluska & Ronard Luna)
|
||||||
|
|
||||||
|
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)
|
||||||
|
* Make sure _trackPageview happens before other settings in Google Analytics
|
||||||
|
(Diederik van der Boor)
|
||||||
|
|
||||||
|
Version 2.4.0
|
||||||
|
-------------
|
||||||
|
* Support Django 2.0 (Matthäus G. Chajdas)
|
||||||
|
|
||||||
|
Version 2.3.0
|
||||||
|
-------------
|
||||||
|
* Add Facebook Pixel support (Pi Delport)
|
||||||
|
* Add Python 3.6 and Django 1.10 & 1.11 tests (Pi Delport)
|
||||||
|
* Drop Python 3.2 support
|
||||||
|
|
||||||
|
Version 2.2.2
|
||||||
|
-------------
|
||||||
|
* Allow port in Piwik domain path. (Alex Ramsay)
|
||||||
|
|
||||||
|
Version 2.2.1
|
||||||
|
-------------
|
||||||
|
* Fix a bug with the extra Google Analytics variables also pushing the `_gat.`
|
||||||
|
flag onto the configuration array.
|
||||||
|
|
||||||
Version 2.2.0
|
Version 2.2.0
|
||||||
-------------
|
-------------
|
||||||
* Update Woopra JavaScript snippet (Aleck Landgraf)
|
* Update Woopra JavaScript snippet (Aleck Landgraf)
|
||||||
|
|
||||||
Version 2.1.0
|
Version 2.1.0
|
||||||
-------------
|
-------------
|
||||||
* Support Rating@Mmail.ru (Nikolay Korotkiy)
|
* Support Rating\@mail.ru (Nikolay Korotkiy)
|
||||||
* Support Yandex.Metrica (Nikolay Korotkiy)
|
* Support Yandex.Metrica (Nikolay Korotkiy)
|
||||||
* Add support for extra Google Analytics variables (Steve Schwarz)
|
* Add support for extra Google Analytics variables (Steve Schwarz)
|
||||||
* Remove support for Reinvigorate (service shut down)
|
* Remove support for Reinvigorate (service shut down)
|
||||||
|
|
@ -69,7 +142,7 @@ Version 0.14.0
|
||||||
Version 0.13.0
|
Version 0.13.0
|
||||||
--------------
|
--------------
|
||||||
* Add support for the KISSmetrics alias feature (Sandra Mau)
|
* Add support for the KISSmetrics alias feature (Sandra Mau)
|
||||||
* Update testing code for Django 1.4 (Piet Delport)
|
* Update testing code for Django 1.4 (Pi Delport)
|
||||||
|
|
||||||
Version 0.12.0
|
Version 0.12.0
|
||||||
--------------
|
--------------
|
||||||
|
|
@ -92,7 +165,7 @@ Version 0.11.0
|
||||||
--------------
|
--------------
|
||||||
* Added support for the Spring Metrics service.
|
* Added support for the Spring Metrics service.
|
||||||
* Allow sending events and properties to KISSmetrics (Paul Oswald).
|
* Allow sending events and properties to KISSmetrics (Paul Oswald).
|
||||||
* Add support for the Site Speed report in Google Analytics (Uros
|
* Add support for the Site Speed report in Google Analytics (Uros
|
||||||
Trebec).
|
Trebec).
|
||||||
|
|
||||||
Version 0.10.0
|
Version 0.10.0
|
||||||
|
|
@ -112,7 +185,7 @@ Version 0.9.1
|
||||||
Version 0.9.0
|
Version 0.9.0
|
||||||
-------------
|
-------------
|
||||||
* Updated Clicky tracking code to support multiple site ids.
|
* Updated Clicky tracking code to support multiple site ids.
|
||||||
* Fixed Chartbeat auto-domain bug when the Sites framework is not used
|
* Fixed Chartbeat auto-domain bug when the Sites framework is not used
|
||||||
(Eric Davis).
|
(Eric Davis).
|
||||||
* Improved testing code (Eric Davis).
|
* Improved testing code (Eric Davis).
|
||||||
|
|
||||||
|
|
@ -145,7 +218,7 @@ Version 0.5.0
|
||||||
-------------
|
-------------
|
||||||
* Split off Geckoboard support into django-geckoboard_.
|
* 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
|
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/
|
||||||
5
CONTRIBUTING.rst
Normal file
5
CONTRIBUTING.rst
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.. image:: https://jazzband.co/static/img/jazzband.svg
|
||||||
|
:target: https://jazzband.co/
|
||||||
|
:alt: Jazzband
|
||||||
|
|
||||||
|
This is a `Jazzband <https://jazzband.co>`_ project. By contributing you agree to abide by the `Contributor Code of Conduct <https://jazzband.co/about/conduct>`_ and follow the `guidelines <https://jazzband.co/about/guidelines>`_.
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (C) 2011-2016 Joost Cassee and others
|
Copyright (C) 2011-2019 Joost Cassee and contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
include LICENSE.txt *.rst
|
include LICENSE.txt *.rst
|
||||||
recursive-include docs *.rst *.py
|
recursive-include docs *.rst *.py
|
||||||
|
recursive-include tests *.py
|
||||||
|
|
|
||||||
78
README.rst
78
README.rst
|
|
@ -1,14 +1,14 @@
|
||||||
django-analytical |latest-version|
|
django-analytical |latest-version|
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
|travis-ci| |coveralls| |health| |python-support| |downloads| |license| |gitter|
|
|build-status| |coverage| |python-support| |license| |jazzband|
|
||||||
|
|
||||||
The django-analytical application integrates analytics services into a
|
The django-analytical application integrates analytics services into a
|
||||||
Django_ project.
|
Django_ project.
|
||||||
|
|
||||||
.. start docs include
|
.. 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
|
tracking code to the project templates. Of course, every service has
|
||||||
its own specific installation instructions. Furthermore, you need to
|
its own specific installation instructions. Furthermore, you need to
|
||||||
include your unique identifiers, which then end up in the templates.
|
include your unique identifiers, which then end up in the templates.
|
||||||
|
|
@ -19,34 +19,28 @@ behind a generic interface, and keeps personal information and
|
||||||
configuration out of the templates. Its goal is to make the basic
|
configuration out of the templates. Its goal is to make the basic
|
||||||
set-up very simple, while allowing advanced users to customize tracking.
|
set-up very simple, while allowing advanced users to customize tracking.
|
||||||
Each service is set up as recommended by the services themselves, using
|
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
|
.. end docs include
|
||||||
|
|
||||||
.. |latest-version| image:: https://img.shields.io/pypi/v/django-analytical.svg
|
.. |latest-version| image:: https://img.shields.io/pypi/v/django-analytical.svg
|
||||||
:alt: Latest version on PyPI
|
:alt: Latest version on PyPI
|
||||||
:target: https://pypi.python.org/pypi/django-analytical
|
:target: https://pypi.org/project/django-analytical/
|
||||||
.. |travis-ci| image:: https://travis-ci.org/jcassee/django-analytical.svg
|
.. |build-status| image:: https://github.com/jazzband/django-analytical/workflows/Test/badge.svg
|
||||||
:alt: Build status
|
:target: https://github.com/jazzband/django-analytical/actions
|
||||||
:target: https://travis-ci.org/jcassee/django-analytical
|
:alt: GitHub Actions
|
||||||
.. |coveralls| image:: https://coveralls.io/repos/jcassee/django-analytical/badge.svg
|
.. |coverage| image:: https://codecov.io/gh/jazzband/django-analytical/branch/main/graph/badge.svg
|
||||||
:alt: Test coverage
|
:alt: Test coverage
|
||||||
:target: https://coveralls.io/r/jcassee/django-analytical
|
:target: https://codecov.io/gh/jazzband/django-analytical
|
||||||
.. |health| image:: https://landscape.io/github/jcassee/django-analytical/master/landscape.svg?style=flat
|
|
||||||
:target: https://landscape.io/github/jcassee/django-analytical/master
|
|
||||||
:alt: Code health
|
|
||||||
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg
|
.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-analytical.svg
|
||||||
:target: https://pypi.python.org/pypi/django-analytical
|
:target: https://pypi.org/project/django-analytical/
|
||||||
:alt: Python versions
|
:alt: Python versions
|
||||||
.. |downloads| image:: https://img.shields.io/pypi/dm/django-analytical.svg
|
|
||||||
:alt: Monthly downloads from PyPI
|
|
||||||
:target: https://pypi.python.org/pypi/django-analytical
|
|
||||||
.. |license| image:: https://img.shields.io/pypi/l/django-analytical.svg
|
.. |license| image:: https://img.shields.io/pypi/l/django-analytical.svg
|
||||||
:alt: Software license
|
:alt: Software license
|
||||||
:target: https://github.com/jcassee/django-analytical/blob/master/LICENSE.txt
|
:target: https://github.com/jazzband/django-analytical/blob/main/LICENSE.txt
|
||||||
.. |gitter| image:: https://badges.gitter.im/Join%20Chat.svg
|
.. |jazzband| image:: https://jazzband.co/static/img/badge.svg
|
||||||
:alt: Gitter chat room
|
:alt: Jazzband
|
||||||
:target: https://gitter.im/jcassee/django-analytical
|
:target: https://jazzband.co/projects/django-analytical
|
||||||
.. _`Django`: http://www.djangoproject.com/
|
.. _`Django`: http://www.djangoproject.com/
|
||||||
|
|
||||||
Currently Supported Services
|
Currently Supported Services
|
||||||
|
|
@ -56,19 +50,23 @@ Currently Supported Services
|
||||||
* `Clickmap`_ visual click tracking
|
* `Clickmap`_ visual click tracking
|
||||||
* `Clicky`_ traffic analysis
|
* `Clicky`_ traffic analysis
|
||||||
* `Crazy Egg`_ visual click tracking
|
* `Crazy Egg`_ visual click tracking
|
||||||
|
* `Facebook Pixel`_ advertising analytics
|
||||||
* `Gaug.es`_ real time web analytics
|
* `Gaug.es`_ real time web analytics
|
||||||
* `Google Analytics`_ traffic analysis
|
* `Google Analytics`_ traffic analysis
|
||||||
* `GoSquared`_ traffic monitoring
|
* `GoSquared`_ traffic monitoring
|
||||||
|
* `Heap`_ analytics and events tracking
|
||||||
|
* `Hotjar`_ analytics and user feedback
|
||||||
* `HubSpot`_ inbound marketing
|
* `HubSpot`_ inbound marketing
|
||||||
* `Intercom`_ live chat and support
|
* `Intercom`_ live chat and support
|
||||||
* `KISSinsights`_ feedback surveys
|
* `KISSinsights`_ feedback surveys
|
||||||
* `KISSmetrics`_ funnel analysis
|
* `KISSmetrics`_ funnel analysis
|
||||||
|
* `Lucky Orange`_ analytics and user feedback
|
||||||
* `Mixpanel`_ event tracking
|
* `Mixpanel`_ event tracking
|
||||||
* `Olark`_ visitor chat
|
* `Olark`_ visitor chat
|
||||||
* `Optimizely`_ A/B testing
|
* `Optimizely`_ A/B testing
|
||||||
* `Performable`_ web analytics and landing pages
|
* `Performable`_ web analytics and landing pages
|
||||||
* `Piwik`_ open source web analytics
|
* `Matomo (formerly Piwik)`_ open source web analytics
|
||||||
* `Rating@Mail.ru`_ web analytics
|
* `Rating\@Mail.ru`_ web analytics
|
||||||
* `SnapEngage`_ live chat
|
* `SnapEngage`_ live chat
|
||||||
* `Spring Metrics`_ conversion tracking
|
* `Spring Metrics`_ conversion tracking
|
||||||
* `UserVoice`_ user feedback and helpdesk
|
* `UserVoice`_ user feedback and helpdesk
|
||||||
|
|
@ -76,22 +74,26 @@ Currently Supported Services
|
||||||
* `Yandex.Metrica`_ web analytics
|
* `Yandex.Metrica`_ web analytics
|
||||||
|
|
||||||
.. _`Chartbeat`: http://www.chartbeat.com/
|
.. _`Chartbeat`: http://www.chartbeat.com/
|
||||||
.. _`Clickmap`: http://getclickmap.com/
|
.. _`Clickmap`: http://clickmap.ch/
|
||||||
.. _`Clicky`: http://getclicky.com/
|
.. _`Clicky`: http://getclicky.com/
|
||||||
.. _`Crazy Egg`: http://www.crazyegg.com/
|
.. _`Crazy Egg`: http://www.crazyegg.com/
|
||||||
|
.. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/
|
||||||
.. _`Gaug.es`: http://get.gaug.es/
|
.. _`Gaug.es`: http://get.gaug.es/
|
||||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||||
.. _`GoSquared`: http://www.gosquared.com/
|
.. _`GoSquared`: http://www.gosquared.com/
|
||||||
|
.. _`Heap`: https://heapanalytics.com/
|
||||||
|
.. _`Hotjar`: https://www.hotjar.com/
|
||||||
.. _`HubSpot`: http://www.hubspot.com/
|
.. _`HubSpot`: http://www.hubspot.com/
|
||||||
.. _`Intercom`: http://www.intercom.io/
|
.. _`Intercom`: http://www.intercom.io/
|
||||||
.. _`KISSinsights`: http://www.kissinsights.com/
|
.. _`KISSinsights`: http://www.kissinsights.com/
|
||||||
.. _`KISSmetrics`: http://www.kissmetrics.com/
|
.. _`KISSmetrics`: http://www.kissmetrics.com/
|
||||||
|
.. _`Lucky Orange`: http://www.luckyorange.com/
|
||||||
.. _`Mixpanel`: http://www.mixpanel.com/
|
.. _`Mixpanel`: http://www.mixpanel.com/
|
||||||
.. _`Olark`: http://www.olark.com/
|
.. _`Olark`: http://www.olark.com/
|
||||||
.. _`Optimizely`: http://www.optimizely.com/
|
.. _`Optimizely`: http://www.optimizely.com/
|
||||||
.. _`Performable`: http://www.performable.com/
|
.. _`Performable`: http://www.performable.com/
|
||||||
.. _`Piwik`: http://www.piwik.org/
|
.. _`Matomo (formerly Piwik)`: https://matomo.org
|
||||||
.. _`Rating@Mail.ru`: http://top.mail.ru/
|
.. _`Rating\@Mail.ru`: http://top.mail.ru/
|
||||||
.. _`SnapEngage`: http://www.snapengage.com/
|
.. _`SnapEngage`: http://www.snapengage.com/
|
||||||
.. _`Spring Metrics`: http://www.springmetrics.com/
|
.. _`Spring Metrics`: http://www.springmetrics.com/
|
||||||
.. _`UserVoice`: http://www.uservoice.com/
|
.. _`UserVoice`: http://www.uservoice.com/
|
||||||
|
|
@ -103,13 +105,11 @@ Documentation and Support
|
||||||
|
|
||||||
The documentation can be found in the ``docs`` directory or `read
|
The documentation can be found in the ``docs`` directory or `read
|
||||||
online`_. The source code and issue tracker are generously `hosted by
|
online`_. The source code and issue tracker are generously `hosted by
|
||||||
GitHub`_. Bugs should be reported there, whereas for lengthy chats
|
GitHub`_.
|
||||||
and coding support when implementing new service integrations you're
|
|
||||||
welcome to use our `Gitter chat room`_.
|
|
||||||
|
|
||||||
.. _`read online`: https://packages.python.org/django-analytical/
|
.. _`read online`: https://django-analytical.readthedocs.io/
|
||||||
.. _`hosted by GitHub`: https://github.com/jcassee/django-analytical
|
.. _`hosted by GitHub`: https://github.com/jazzband/django-analytical
|
||||||
.. _`Gitter chat room`: https://gitter.im/jcassee/django-analytical
|
.. _`Gitter chat room`: https://gitter.im/jazzband/django-analytical
|
||||||
|
|
||||||
How To Contribute
|
How To Contribute
|
||||||
-----------------
|
-----------------
|
||||||
|
|
@ -123,7 +123,19 @@ services to support, or suggesting documentation improvements, use the
|
||||||
the repository, make changes and place a `pull request`_. Creating an
|
the repository, make changes and place a `pull request`_. Creating an
|
||||||
issue to discuss your plans is useful.
|
issue to discuss your plans is useful.
|
||||||
|
|
||||||
.. _`issue tracker`: https://github.com/jcassee/django-analytical/issues
|
At the end, don't forget to add yourself to the `list of authors`_ and
|
||||||
.. _`pull request`: https://github.com/jcassee/django-analytical/pulls
|
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
|
||||||
|
|
||||||
.. end contribute include
|
.. end contribute include
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Analytics service integration for Django
|
Analytics service integration for Django projects.
|
||||||
========================================
|
|
||||||
|
|
||||||
The django-analytical application integrates analytics services into a
|
|
||||||
Django_ project. See the ``docs`` directory for more information.
|
|
||||||
|
|
||||||
.. _Django: http://www.djangoproject.com/
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = "Joost Cassee"
|
__version__ = '3.2.0'
|
||||||
__email__ = "joost@cassee.net"
|
|
||||||
__version__ = "2.2.0"
|
|
||||||
__copyright__ = "Copyright (C) 2011-2016 Joost Cassee and others"
|
|
||||||
__license__ = "MIT License"
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,14 @@
|
||||||
Analytical template tags and filters.
|
Analytical template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.template import Node, TemplateSyntaxError
|
from django.template import Node, TemplateSyntaxError
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from analytical.utils import AnalyticalException
|
from analytical.utils import AnalyticalException
|
||||||
|
|
||||||
|
|
||||||
TAG_LOCATIONS = ['head_top', 'head_bottom', 'body_top', 'body_bottom']
|
TAG_LOCATIONS = ['head_top', 'head_bottom', 'body_top', 'body_bottom']
|
||||||
TAG_POSITIONS = ['first', None, 'last']
|
TAG_POSITIONS = ['first', None, 'last']
|
||||||
TAG_MODULES = [
|
TAG_MODULES = [
|
||||||
|
|
@ -20,18 +17,24 @@ TAG_MODULES = [
|
||||||
'analytical.clickmap',
|
'analytical.clickmap',
|
||||||
'analytical.clicky',
|
'analytical.clicky',
|
||||||
'analytical.crazy_egg',
|
'analytical.crazy_egg',
|
||||||
|
'analytical.facebook_pixel',
|
||||||
'analytical.gauges',
|
'analytical.gauges',
|
||||||
'analytical.google_analytics',
|
'analytical.google_analytics',
|
||||||
|
'analytical.google_analytics_js',
|
||||||
|
'analytical.google_analytics_gtag',
|
||||||
'analytical.gosquared',
|
'analytical.gosquared',
|
||||||
|
'analytical.heap',
|
||||||
|
'analytical.hotjar',
|
||||||
'analytical.hubspot',
|
'analytical.hubspot',
|
||||||
'analytical.intercom',
|
'analytical.intercom',
|
||||||
'analytical.kiss_insights',
|
'analytical.kiss_insights',
|
||||||
'analytical.kiss_metrics',
|
'analytical.kiss_metrics',
|
||||||
|
'analytical.luckyorange',
|
||||||
|
'analytical.matomo',
|
||||||
'analytical.mixpanel',
|
'analytical.mixpanel',
|
||||||
'analytical.olark',
|
'analytical.olark',
|
||||||
'analytical.optimizely',
|
'analytical.optimizely',
|
||||||
'analytical.performable',
|
'analytical.performable',
|
||||||
'analytical.piwik',
|
|
||||||
'analytical.rating_mailru',
|
'analytical.rating_mailru',
|
||||||
'analytical.snapengage',
|
'analytical.snapengage',
|
||||||
'analytical.spring_metrics',
|
'analytical.spring_metrics',
|
||||||
|
|
@ -63,12 +66,11 @@ class AnalyticalNode(Node):
|
||||||
self.nodes = [node_cls() for node_cls in template_nodes[location]]
|
self.nodes = [node_cls() for node_cls in template_nodes[location]]
|
||||||
|
|
||||||
def render(self, context):
|
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():
|
def _load_template_nodes():
|
||||||
template_nodes = dict((l, dict((p, []) for p in TAG_POSITIONS))
|
template_nodes = {loc: {pos: [] for pos in TAG_POSITIONS} for loc in TAG_LOCATIONS}
|
||||||
for l in TAG_LOCATIONS)
|
|
||||||
|
|
||||||
def add_node_cls(location, node, position=None):
|
def add_node_cls(location, node, position=None):
|
||||||
template_nodes[location][position].append(node)
|
template_nodes[location][position].append(node)
|
||||||
|
|
@ -80,14 +82,15 @@ def _load_template_nodes():
|
||||||
except AnalyticalException as e:
|
except AnalyticalException as e:
|
||||||
logger.debug("not loading tags from '%s': %s", path, e)
|
logger.debug("not loading tags from '%s': %s", path, e)
|
||||||
for location in TAG_LOCATIONS:
|
for location in TAG_LOCATIONS:
|
||||||
template_nodes[location] = sum((template_nodes[location][p]
|
template_nodes[location] = sum(
|
||||||
for p in TAG_POSITIONS), [])
|
(template_nodes[location][p] for p in TAG_POSITIONS), []
|
||||||
|
)
|
||||||
return template_nodes
|
return template_nodes
|
||||||
|
|
||||||
|
|
||||||
def _import_tag_module(path):
|
def _import_tag_module(path):
|
||||||
app_name, lib_name = path.rsplit('.', 1)
|
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()
|
template_nodes = _load_template_nodes()
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
Chartbeat template tags and filters.
|
Chartbeat template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
@ -11,13 +9,12 @@ from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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+$')
|
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 = """
|
SETUP_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var _sf_async_config=%(config)s;
|
var _sf_async_config=%(config)s;
|
||||||
(function(){
|
(function(){
|
||||||
function loadChartbeat() {
|
function loadChartbeat() {
|
||||||
|
|
@ -35,7 +32,7 @@ SETUP_CODE = """
|
||||||
loadChartbeat : function() { oldonload(); loadChartbeat(); };
|
loadChartbeat : function() { oldonload(); loadChartbeat(); };
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
DOMAIN_CONTEXT_KEY = 'chartbeat_domain'
|
DOMAIN_CONTEXT_KEY = 'chartbeat_domain'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,7 +44,7 @@ def chartbeat_top(parser, token):
|
||||||
"""
|
"""
|
||||||
Top Chartbeat template tag.
|
Top Chartbeat template tag.
|
||||||
|
|
||||||
Render the top Javascript code for Chartbeat.
|
Render the top JavaScript code for Chartbeat.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
if len(bits) > 1:
|
if len(bits) > 1:
|
||||||
|
|
@ -58,7 +55,7 @@ def chartbeat_top(parser, token):
|
||||||
class ChartbeatTopNode(Node):
|
class ChartbeatTopNode(Node):
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
if is_internal_ip(context):
|
if is_internal_ip(context):
|
||||||
return disable_html(INIT_CODE, "Chartbeat")
|
return disable_html(INIT_CODE, 'Chartbeat')
|
||||||
return INIT_CODE
|
return INIT_CODE
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,7 +64,7 @@ def chartbeat_bottom(parser, token):
|
||||||
"""
|
"""
|
||||||
Bottom Chartbeat template tag.
|
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``
|
your Chartbeat User ID (as a string) in the ``CHARTBEAT_USER_ID``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -79,8 +76,9 @@ def chartbeat_bottom(parser, token):
|
||||||
|
|
||||||
class ChartbeatBottomNode(Node):
|
class ChartbeatBottomNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.user_id = get_required_setting('CHARTBEAT_USER_ID', USER_ID_RE,
|
self.user_id = get_required_setting(
|
||||||
"must be (a string containing) a number")
|
'CHARTBEAT_USER_ID', USER_ID_RE, 'must be (a string containing) a number'
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
config = {'uid': self.user_id}
|
config = {'uid': self.user_id}
|
||||||
|
|
@ -109,6 +107,7 @@ def _get_domain(context):
|
||||||
return
|
return
|
||||||
elif getattr(settings, 'CHARTBEAT_AUTO_DOMAIN', True):
|
elif getattr(settings, 'CHARTBEAT_AUTO_DOMAIN', True):
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return Site.objects.get_current().domain
|
return Site.objects.get_current().domain
|
||||||
except (ImproperlyConfigured, Site.DoesNotExist): # pylint: disable=E1101
|
except (ImproperlyConfigured, Site.DoesNotExist): # pylint: disable=E1101
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,15 @@
|
||||||
Clickmap template tags and filters.
|
Clickmap template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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+$')
|
CLICKMAP_TRACKER_ID_RE = re.compile(r'^\w+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var clickmapConfig = {tracker: '%(tracker_id)s', version:'2'};
|
var clickmapConfig = {tracker: '%(tracker_id)s', version:'2'};
|
||||||
window.clickmapAsyncInit = function(){ __clickmap.init(clickmapConfig); };
|
window.clickmapAsyncInit = function(){ __clickmap.init(clickmapConfig); };
|
||||||
(function() { var _cmf = document.createElement('script'); _cmf.async = true;
|
(function() { var _cmf = document.createElement('script'); _cmf.async = true;
|
||||||
|
|
@ -33,7 +30,7 @@ def clickmap(parser, token):
|
||||||
"""
|
"""
|
||||||
Clickmap tracker template tag.
|
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``
|
your clickmap tracker ID (as a string) in the ``CLICKMAP_TRACKER_ID``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -45,9 +42,11 @@ def clickmap(parser, token):
|
||||||
|
|
||||||
class ClickmapNode(Node):
|
class ClickmapNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tracker_id = get_required_setting('CLICKMAP_TRACKER_ID',
|
self.tracker_id = get_required_setting(
|
||||||
CLICKMAP_TRACKER_ID_RE,
|
'CLICKMAP_TRACKER_ID',
|
||||||
"must be an alphanumeric string")
|
CLICKMAP_TRACKER_ID_RE,
|
||||||
|
'must be an alphanumeric string',
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
html = TRACKING_CODE % {'tracker_id': self.tracker_id}
|
html = TRACKING_CODE % {'tracker_id': self.tracker_id}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,21 @@
|
||||||
Clicky template tags and filters.
|
Clicky template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import get_identity, is_internal_ip, disable_html, \
|
from analytical.utils import (
|
||||||
get_required_setting
|
disable_html,
|
||||||
|
get_identity,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
SITE_ID_RE = re.compile(r'^\d+$')
|
SITE_ID_RE = re.compile(r'^\d+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var clicky = { log: function(){ return; }, goal: function(){ return; }};
|
var clicky = { log: function(){ return; }, goal: function(){ return; }};
|
||||||
var clicky_site_ids = clicky_site_ids || [];
|
var clicky_site_ids = clicky_site_ids || [];
|
||||||
clicky_site_ids.push(%(site_id)s);
|
clicky_site_ids.push(%(site_id)s);
|
||||||
|
|
@ -29,8 +30,7 @@ TRACKING_CODE = """
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<noscript><p><img alt="Clicky" width="1" height="1" src="//in.getclicky.com/%(site_id)sns.gif" /></p></noscript>
|
<noscript><p><img alt="Clicky" width="1" height="1" src="//in.getclicky.com/%(site_id)sns.gif" /></p></noscript>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ def clicky(parser, token):
|
||||||
"""
|
"""
|
||||||
Clicky tracking template tag.
|
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``
|
your Clicky Site ID (as a string) in the ``CLICKY_SITE_ID``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -52,8 +52,9 @@ def clicky(parser, token):
|
||||||
|
|
||||||
class ClickyNode(Node):
|
class ClickyNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.site_id = get_required_setting('CLICKY_SITE_ID', SITE_ID_RE,
|
self.site_id = get_required_setting(
|
||||||
"must be a (string containing) a number")
|
'CLICKY_SITE_ID', SITE_ID_RE, 'must be a (string containing) a number'
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
custom = {}
|
custom = {}
|
||||||
|
|
@ -66,8 +67,10 @@ class ClickyNode(Node):
|
||||||
if identity is not None:
|
if identity is not None:
|
||||||
custom.setdefault('session', {})['username'] = identity
|
custom.setdefault('session', {})['username'] = identity
|
||||||
|
|
||||||
html = TRACKING_CODE % {'site_id': self.site_id,
|
html = TRACKING_CODE % {
|
||||||
'custom': json.dumps(custom, sort_keys=True)}
|
'site_id': self.site_id,
|
||||||
|
'custom': json.dumps(custom, sort_keys=True),
|
||||||
|
}
|
||||||
if is_internal_ip(context, 'CLICKY'):
|
if is_internal_ip(context, 'CLICKY'):
|
||||||
html = disable_html(html, 'Clicky')
|
html = disable_html(html, 'Clicky')
|
||||||
return html
|
return html
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,17 @@
|
||||||
Crazy Egg template tags and filters.
|
Crazy Egg template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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+$')
|
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
||||||
SETUP_CODE = '<script type="text/javascript" src="{placeholder_url}">' \
|
SETUP_CODE = '<script src="{placeholder_url}"></script>'.format(
|
||||||
'</script>'.\
|
placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/'
|
||||||
format(placeholder_url='//dnn506yrbagrg.cloudfront.net/pages/scripts/'
|
'%(account_nr_1)s/%(account_nr_2)s.js'
|
||||||
'%(account_nr_1)s/%(account_nr_2)s.js')
|
)
|
||||||
USERVAR_CODE = "CE2.set(%(varnr)d, '%(value)s');"
|
USERVAR_CODE = "CE2.set(%(varnr)d, '%(value)s');"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,7 +24,7 @@ def crazy_egg(parser, token):
|
||||||
"""
|
"""
|
||||||
Crazy Egg tracking template tag.
|
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
|
your Crazy Egg account number (as a string) in the
|
||||||
``CRAZY_EGG_ACCOUNT_NUMBER`` setting.
|
``CRAZY_EGG_ACCOUNT_NUMBER`` setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -41,7 +38,8 @@ class CrazyEggNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.account_nr = get_required_setting(
|
self.account_nr = get_required_setting(
|
||||||
'CRAZY_EGG_ACCOUNT_NUMBER',
|
'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):
|
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))
|
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]
|
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
|
||||||
if params:
|
if params:
|
||||||
js = " ".join(USERVAR_CODE % {
|
js = ' '.join(
|
||||||
'varnr': varnr,
|
USERVAR_CODE
|
||||||
'value': value,
|
% {
|
||||||
} for (varnr, value) in params)
|
'varnr': varnr,
|
||||||
html = '%s\n' \
|
'value': value,
|
||||||
'<script type="text/javascript">%s</script>' % (html, js)
|
}
|
||||||
|
for (varnr, value) in params
|
||||||
|
)
|
||||||
|
html = '%s\n<script>%s</script>' % (html, js)
|
||||||
if is_internal_ip(context, 'CRAZY_EGG'):
|
if is_internal_ip(context, 'CRAZY_EGG'):
|
||||||
html = disable_html(html, 'Crazy Egg')
|
html = disable_html(html, 'Crazy Egg')
|
||||||
return html
|
return html
|
||||||
|
|
|
||||||
96
analytical/templatetags/facebook_pixel.py
Normal file
96
analytical/templatetags/facebook_pixel.py
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
"""
|
||||||
|
Facebook Pixel 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
|
||||||
|
|
||||||
|
FACEBOOK_PIXEL_HEAD_CODE = """\
|
||||||
|
<script>
|
||||||
|
!function(f,b,e,v,n,t,s)
|
||||||
|
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||||
|
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||||
|
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||||
|
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||||
|
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||||
|
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||||
|
'https://connect.facebook.net/en_US/fbevents.js');
|
||||||
|
fbq('init', '%(FACEBOOK_PIXEL_ID)s');
|
||||||
|
fbq('track', 'PageView');
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
|
||||||
|
FACEBOOK_PIXEL_BODY_CODE = """\
|
||||||
|
<noscript><img height="1" width="1" style="display:none"
|
||||||
|
src="https://www.facebook.com/tr?id=%(FACEBOOK_PIXEL_ID)s&ev=PageView&noscript=1"
|
||||||
|
/></noscript>
|
||||||
|
"""
|
||||||
|
|
||||||
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_no_args(token):
|
||||||
|
bits = token.split_contents()
|
||||||
|
if len(bits) > 1:
|
||||||
|
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def facebook_pixel_head(parser, token):
|
||||||
|
"""
|
||||||
|
Facebook Pixel head template tag.
|
||||||
|
"""
|
||||||
|
_validate_no_args(token)
|
||||||
|
return FacebookPixelHeadNode()
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def facebook_pixel_body(parser, token):
|
||||||
|
"""
|
||||||
|
Facebook Pixel body template tag.
|
||||||
|
"""
|
||||||
|
_validate_no_args(token)
|
||||||
|
return FacebookPixelBodyNode()
|
||||||
|
|
||||||
|
|
||||||
|
class _FacebookPixelNode(Node):
|
||||||
|
"""
|
||||||
|
Base class: override and provide code_template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pixel_id = get_required_setting(
|
||||||
|
'FACEBOOK_PIXEL_ID',
|
||||||
|
re.compile(r'^\d+$'),
|
||||||
|
'must be (a string containing) a number',
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
html = self.code_template % {'FACEBOOK_PIXEL_ID': self.pixel_id}
|
||||||
|
if is_internal_ip(context, 'FACEBOOK_PIXEL'):
|
||||||
|
return disable_html(html, 'Facebook Pixel')
|
||||||
|
else:
|
||||||
|
return html
|
||||||
|
|
||||||
|
@property
|
||||||
|
def code_template(self):
|
||||||
|
raise NotImplementedError # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
class FacebookPixelHeadNode(_FacebookPixelNode):
|
||||||
|
code_template = FACEBOOK_PIXEL_HEAD_CODE
|
||||||
|
|
||||||
|
|
||||||
|
class FacebookPixelBodyNode(_FacebookPixelNode):
|
||||||
|
code_template = FACEBOOK_PIXEL_BODY_CODE
|
||||||
|
|
||||||
|
|
||||||
|
def contribute_to_analytical(add_node):
|
||||||
|
# ensure properly configured
|
||||||
|
FacebookPixelHeadNode()
|
||||||
|
FacebookPixelBodyNode()
|
||||||
|
add_node('head_bottom', FacebookPixelHeadNode)
|
||||||
|
add_node('body_bottom', FacebookPixelBodyNode)
|
||||||
|
|
@ -2,17 +2,15 @@
|
||||||
Gaug.es template tags and filters.
|
Gaug.es template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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]+$')
|
SITE_ID_RE = re.compile(r'[\da-f]+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var _gauges = _gauges || [];
|
var _gauges = _gauges || [];
|
||||||
(function() {
|
(function() {
|
||||||
var t = document.createElement('script');
|
var t = document.createElement('script');
|
||||||
|
|
@ -35,7 +33,7 @@ def gauges(parser, token):
|
||||||
"""
|
"""
|
||||||
Gaug.es template tag.
|
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``
|
your Site ID account number in the ``GAUGES_SITE_ID``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -48,8 +46,8 @@ def gauges(parser, token):
|
||||||
class GaugesNode(Node):
|
class GaugesNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.site_id = get_required_setting(
|
self.site_id = get_required_setting(
|
||||||
'GAUGES_SITE_ID', SITE_ID_RE,
|
'GAUGES_SITE_ID', SITE_ID_RE, "must be a string looking like 'XXXXXXX'"
|
||||||
"must be a string looking like 'XXXXXXX'")
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
html = TRACKING_CODE % {'site_id': self.site_id}
|
html = TRACKING_CODE % {'site_id': self.site_id}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Google Analytics template tags and filters.
|
Google Analytics template tags and filters.
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
DEPRECATED
|
||||||
|
"""
|
||||||
|
|
||||||
import decimal
|
import decimal
|
||||||
import re
|
import re
|
||||||
|
|
@ -28,11 +28,10 @@ SCOPE_PAGE = 3
|
||||||
|
|
||||||
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
||||||
SETUP_CODE = """
|
SETUP_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
|
|
||||||
var _gaq = _gaq || [];
|
var _gaq = _gaq || [];
|
||||||
_gaq.push(['_setAccount', '%(property_id)s']);
|
_gaq.push(['_setAccount', '%(property_id)s']);
|
||||||
_gaq.push(['_trackPageview']);
|
|
||||||
%(commands)s
|
%(commands)s
|
||||||
(function() {
|
(function() {
|
||||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||||
|
|
@ -44,17 +43,22 @@ SETUP_CODE = """
|
||||||
"""
|
"""
|
||||||
DOMAIN_CODE = "_gaq.push(['_setDomainName', '%s']);"
|
DOMAIN_CODE = "_gaq.push(['_setDomainName', '%s']);"
|
||||||
NO_ALLOW_HASH_CODE = "_gaq.push(['_setAllowHash', false]);"
|
NO_ALLOW_HASH_CODE = "_gaq.push(['_setAllowHash', false]);"
|
||||||
|
TRACK_PAGE_VIEW = "_gaq.push(['_trackPageview']);"
|
||||||
ALLOW_LINKER_CODE = "_gaq.push(['_setAllowLinker', true]);"
|
ALLOW_LINKER_CODE = "_gaq.push(['_setAllowLinker', true]);"
|
||||||
CUSTOM_VAR_CODE = "_gaq.push(['_setCustomVar', %(index)s, '%(name)s', " \
|
CUSTOM_VAR_CODE = (
|
||||||
"'%(value)s', %(scope)s]);"
|
"_gaq.push(['_setCustomVar', %(index)s, '%(name)s', '%(value)s', %(scope)s]);"
|
||||||
|
)
|
||||||
SITE_SPEED_CODE = "_gaq.push(['_trackPageLoadTime']);"
|
SITE_SPEED_CODE = "_gaq.push(['_trackPageLoadTime']);"
|
||||||
ANONYMIZE_IP_CODE = "_gaq.push (['_gat._anonymizeIp']);"
|
ANONYMIZE_IP_CODE = "_gaq.push(['_gat._anonymizeIp']);"
|
||||||
SAMPLE_RATE_CODE = "_gaq.push (['_gat._setSampleRate', '%s']);"
|
SAMPLE_RATE_CODE = "_gaq.push(['_setSampleRate', '%s']);"
|
||||||
SITE_SPEED_SAMPLE_RATE_CODE = "_gaq.push (['_gat._setSiteSpeedSampleRate', '%s']);"
|
SITE_SPEED_SAMPLE_RATE_CODE = "_gaq.push(['_setSiteSpeedSampleRate', '%s']);"
|
||||||
SESSION_COOKIE_TIMEOUT_CODE = "_gaq.push (['_gat._setSessionCookieTimeout', '%s']);"
|
SESSION_COOKIE_TIMEOUT_CODE = "_gaq.push(['_setSessionCookieTimeout', '%s']);"
|
||||||
VISITOR_COOKIE_TIMEOUT_CODE = "_gaq.push (['_gat._setVisitorCookieTimeout', '%s']);"
|
VISITOR_COOKIE_TIMEOUT_CODE = "_gaq.push(['_setVisitorCookieTimeout', '%s']);"
|
||||||
DEFAULT_SOURCE = ("'https://ssl' : 'http://www'", "'.google-analytics.com/ga.js'")
|
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')
|
ZEROPLACES = decimal.Decimal('0')
|
||||||
TWOPLACES = decimal.Decimal('0.01')
|
TWOPLACES = decimal.Decimal('0.01')
|
||||||
|
|
@ -67,7 +71,7 @@ def google_analytics(parser, token):
|
||||||
"""
|
"""
|
||||||
Google Analytics tracking template tag.
|
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
|
your website property ID (as a string) in the
|
||||||
``GOOGLE_ANALYTICS_PROPERTY_ID`` setting.
|
``GOOGLE_ANALYTICS_PROPERTY_ID`` setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -80,37 +84,43 @@ def google_analytics(parser, token):
|
||||||
class GoogleAnalyticsNode(Node):
|
class GoogleAnalyticsNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.property_id = get_required_setting(
|
self.property_id = get_required_setting(
|
||||||
'GOOGLE_ANALYTICS_PROPERTY_ID', PROPERTY_ID_RE,
|
'GOOGLE_ANALYTICS_PROPERTY_ID',
|
||||||
"must be a string looking like 'UA-XXXXXX-Y'")
|
PROPERTY_ID_RE,
|
||||||
|
"must be a string looking like 'UA-XXXXXX-Y'",
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
commands = self._get_domain_commands(context)
|
commands = self._get_domain_commands(context)
|
||||||
commands.extend(self._get_custom_var_commands(context))
|
commands.extend(self._get_custom_var_commands(context))
|
||||||
commands.extend(self._get_other_commands(context))
|
commands.extend(self._get_other_commands(context))
|
||||||
|
commands.append(TRACK_PAGE_VIEW)
|
||||||
if getattr(settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False):
|
if getattr(settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False):
|
||||||
source = DISPLAY_ADVERTISING_SOURCE
|
source = DISPLAY_ADVERTISING_SOURCE
|
||||||
else:
|
else:
|
||||||
source = DEFAULT_SOURCE
|
source = DEFAULT_SOURCE
|
||||||
html = SETUP_CODE % {'property_id': self.property_id,
|
html = SETUP_CODE % {
|
||||||
'commands': " ".join(commands),
|
'property_id': self.property_id,
|
||||||
'source_scheme': source[0],
|
'commands': ' '.join(commands),
|
||||||
'source_url': source[1]}
|
'source_scheme': source[0],
|
||||||
|
'source_url': source[1],
|
||||||
|
}
|
||||||
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
||||||
html = disable_html(html, 'Google Analytics')
|
html = disable_html(html, 'Google Analytics')
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def _get_domain_commands(self, context):
|
def _get_domain_commands(self, context):
|
||||||
commands = []
|
commands = []
|
||||||
tracking_type = getattr(settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE',
|
tracking_type = getattr(
|
||||||
TRACK_SINGLE_DOMAIN)
|
settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', TRACK_SINGLE_DOMAIN
|
||||||
|
)
|
||||||
if tracking_type == TRACK_SINGLE_DOMAIN:
|
if tracking_type == TRACK_SINGLE_DOMAIN:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
domain = get_domain(context, 'google_analytics')
|
domain = get_domain(context, 'google_analytics')
|
||||||
if domain is None:
|
if domain is None:
|
||||||
raise AnalyticalException(
|
raise AnalyticalException(
|
||||||
"tracking multiple domains with Google Analytics"
|
'tracking multiple domains with Google Analytics requires a domain name'
|
||||||
" requires a domain name")
|
)
|
||||||
commands.append(DOMAIN_CODE % domain)
|
commands.append(DOMAIN_CODE % domain)
|
||||||
commands.append(NO_ALLOW_HASH_CODE)
|
commands.append(NO_ALLOW_HASH_CODE)
|
||||||
if tracking_type == TRACK_MULTIPLE_DOMAINS:
|
if tracking_type == TRACK_MULTIPLE_DOMAINS:
|
||||||
|
|
@ -118,9 +128,7 @@ class GoogleAnalyticsNode(Node):
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
def _get_custom_var_commands(self, context):
|
def _get_custom_var_commands(self, context):
|
||||||
values = (
|
values = (context.get('google_analytics_var%s' % i) for i in range(1, 6))
|
||||||
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]
|
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
|
||||||
commands = []
|
commands = []
|
||||||
for index, var in params:
|
for index, var in params:
|
||||||
|
|
@ -130,12 +138,15 @@ class GoogleAnalyticsNode(Node):
|
||||||
scope = var[2]
|
scope = var[2]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
scope = SCOPE_PAGE
|
scope = SCOPE_PAGE
|
||||||
commands.append(CUSTOM_VAR_CODE % {
|
commands.append(
|
||||||
'index': index,
|
CUSTOM_VAR_CODE
|
||||||
'name': name,
|
% {
|
||||||
'value': value,
|
'index': index,
|
||||||
'scope': scope,
|
'name': name,
|
||||||
})
|
'value': value,
|
||||||
|
'scope': scope,
|
||||||
|
}
|
||||||
|
)
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
def _get_other_commands(self, context):
|
def _get_other_commands(self, context):
|
||||||
|
|
@ -150,28 +161,42 @@ class GoogleAnalyticsNode(Node):
|
||||||
if sampleRate is not False:
|
if sampleRate is not False:
|
||||||
value = decimal.Decimal(sampleRate)
|
value = decimal.Decimal(sampleRate)
|
||||||
if not 0 <= value <= 100:
|
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))
|
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:
|
if siteSpeedSampleRate is not False:
|
||||||
value = decimal.Decimal(siteSpeedSampleRate)
|
value = decimal.Decimal(siteSpeedSampleRate)
|
||||||
if not 0 <= value <= 100:
|
if not 0 <= value <= 100:
|
||||||
raise AnalyticalException("'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100")
|
raise AnalyticalException(
|
||||||
|
"'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100"
|
||||||
|
)
|
||||||
commands.append(SITE_SPEED_SAMPLE_RATE_CODE % value.quantize(TWOPLACES))
|
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:
|
if sessionCookieTimeout is not False:
|
||||||
value = decimal.Decimal(sessionCookieTimeout)
|
value = decimal.Decimal(sessionCookieTimeout)
|
||||||
if value < 0:
|
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))
|
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:
|
if visitorCookieTimeout is not False:
|
||||||
value = decimal.Decimal(visitorCookieTimeout)
|
value = decimal.Decimal(visitorCookieTimeout)
|
||||||
if value < 0:
|
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))
|
commands.append(VISITOR_COOKIE_TIMEOUT_CODE % value.quantize(ZEROPLACES))
|
||||||
return commands
|
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)
|
||||||
176
analytical/templatetags/google_analytics_js.py
Normal file
176
analytical/templatetags/google_analytics_js.py
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
"""
|
||||||
|
Google Analytics template tags and filters, using the new analytics.js library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import decimal
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
|
from analytical.utils import (
|
||||||
|
AnalyticalException,
|
||||||
|
disable_html,
|
||||||
|
get_domain,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
|
TRACK_SINGLE_DOMAIN = 1
|
||||||
|
TRACK_MULTIPLE_SUBDOMAINS = 2
|
||||||
|
TRACK_MULTIPLE_DOMAINS = 3
|
||||||
|
|
||||||
|
PROPERTY_ID_RE = re.compile(r'^UA-\d+-\d+$')
|
||||||
|
SETUP_CODE = """
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){{i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){{
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)}},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
}})(window,document,'script','{js_source}','ga');
|
||||||
|
|
||||||
|
ga('create', '{property_id}', 'auto', {create_fields});
|
||||||
|
{commands}ga('send', 'pageview');
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
REQUIRE_DISPLAY_FEATURES = "ga('require', 'displayfeatures');\n"
|
||||||
|
CUSTOM_VAR_CODE = "ga('set', '{name}', {value});\n"
|
||||||
|
ANONYMIZE_IP_CODE = "ga('set', 'anonymizeIp', true);\n"
|
||||||
|
|
||||||
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def google_analytics_js(parser, token):
|
||||||
|
"""
|
||||||
|
Google Analytics tracking template tag.
|
||||||
|
|
||||||
|
Renders JavaScript code to track page visits. You must supply
|
||||||
|
your website property ID (as a string) in the
|
||||||
|
``GOOGLE_ANALYTICS_JS_PROPERTY_ID`` setting.
|
||||||
|
"""
|
||||||
|
bits = token.split_contents()
|
||||||
|
if len(bits) > 1:
|
||||||
|
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||||
|
return GoogleAnalyticsJsNode()
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleAnalyticsJsNode(Node):
|
||||||
|
def __init__(self):
|
||||||
|
self.property_id = get_required_setting(
|
||||||
|
'GOOGLE_ANALYTICS_JS_PROPERTY_ID',
|
||||||
|
PROPERTY_ID_RE,
|
||||||
|
"must be a string looking like 'UA-XXXXXX-Y'",
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
import json
|
||||||
|
|
||||||
|
create_fields = self._get_domain_fields(context)
|
||||||
|
create_fields.update(self._get_other_create_fields(context))
|
||||||
|
commands = self._get_custom_var_commands(context)
|
||||||
|
commands.extend(self._get_other_commands(context))
|
||||||
|
display_features = getattr(
|
||||||
|
settings, 'GOOGLE_ANALYTICS_DISPLAY_ADVERTISING', False
|
||||||
|
)
|
||||||
|
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),
|
||||||
|
js_source=js_source,
|
||||||
|
)
|
||||||
|
if is_internal_ip(context, 'GOOGLE_ANALYTICS'):
|
||||||
|
html = disable_html(html, 'Google Analytics')
|
||||||
|
return html
|
||||||
|
|
||||||
|
def _get_domain_fields(self, context):
|
||||||
|
domain_fields = {}
|
||||||
|
tracking_type = getattr(
|
||||||
|
settings, 'GOOGLE_ANALYTICS_TRACKING_STYLE', TRACK_SINGLE_DOMAIN
|
||||||
|
)
|
||||||
|
if tracking_type == TRACK_SINGLE_DOMAIN:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
domain = get_domain(context, 'google_analytics')
|
||||||
|
if domain is None:
|
||||||
|
raise AnalyticalException(
|
||||||
|
'tracking multiple domains with Google Analytics requires a domain name'
|
||||||
|
)
|
||||||
|
domain_fields['legacyCookieDomain'] = domain
|
||||||
|
if tracking_type == TRACK_MULTIPLE_DOMAINS:
|
||||||
|
domain_fields['allowLinker'] = True
|
||||||
|
return domain_fields
|
||||||
|
|
||||||
|
def _get_other_create_fields(self, context):
|
||||||
|
other_fields = {}
|
||||||
|
|
||||||
|
site_speed_sample_rate = getattr(
|
||||||
|
settings, 'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE', False
|
||||||
|
)
|
||||||
|
if site_speed_sample_rate is not False:
|
||||||
|
value = int(decimal.Decimal(site_speed_sample_rate))
|
||||||
|
if not 0 <= value <= 100:
|
||||||
|
raise AnalyticalException(
|
||||||
|
"'GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE' must be >= 0 and <= 100"
|
||||||
|
)
|
||||||
|
other_fields['siteSpeedSampleRate'] = value
|
||||||
|
|
||||||
|
sample_rate = getattr(settings, 'GOOGLE_ANALYTICS_SAMPLE_RATE', False)
|
||||||
|
if sample_rate is not False:
|
||||||
|
value = int(decimal.Decimal(sample_rate))
|
||||||
|
if not 0 <= value <= 100:
|
||||||
|
raise AnalyticalException(
|
||||||
|
"'GOOGLE_ANALYTICS_SAMPLE_RATE' must be >= 0 and <= 100"
|
||||||
|
)
|
||||||
|
other_fields['sampleRate'] = value
|
||||||
|
|
||||||
|
cookie_expires = getattr(settings, 'GOOGLE_ANALYTICS_COOKIE_EXPIRATION', False)
|
||||||
|
if cookie_expires is not False:
|
||||||
|
value = int(decimal.Decimal(cookie_expires))
|
||||||
|
if value < 0:
|
||||||
|
raise AnalyticalException(
|
||||||
|
"'GOOGLE_ANALYTICS_COOKIE_EXPIRATION' must be >= 0"
|
||||||
|
)
|
||||||
|
other_fields['cookieExpires'] = value
|
||||||
|
|
||||||
|
return other_fields
|
||||||
|
|
||||||
|
def _get_custom_var_commands(self, context):
|
||||||
|
values = (context.get('google_analytics_var%s' % i) for i in range(1, 6))
|
||||||
|
params = [(i, v) for i, v in enumerate(values, 1) if v is not None]
|
||||||
|
commands = []
|
||||||
|
for _, var in params:
|
||||||
|
name = var[0]
|
||||||
|
value = var[1]
|
||||||
|
try:
|
||||||
|
float(value)
|
||||||
|
except ValueError:
|
||||||
|
value = f"'{value}'"
|
||||||
|
commands.append(
|
||||||
|
CUSTOM_VAR_CODE.format(
|
||||||
|
name=name,
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return commands
|
||||||
|
|
||||||
|
def _get_other_commands(self, context):
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
if getattr(settings, 'GOOGLE_ANALYTICS_ANONYMIZE_IP', False):
|
||||||
|
commands.append(ANONYMIZE_IP_CODE)
|
||||||
|
|
||||||
|
return commands
|
||||||
|
|
||||||
|
|
||||||
|
def contribute_to_analytical(add_node):
|
||||||
|
GoogleAnalyticsJsNode() # ensure properly configured
|
||||||
|
add_node('head_bottom', GoogleAnalyticsJsNode)
|
||||||
|
|
@ -2,19 +2,20 @@
|
||||||
GoSquared template tags and filters.
|
GoSquared template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import get_identity, \
|
from analytical.utils import (
|
||||||
is_internal_ip, disable_html, get_required_setting
|
disable_html,
|
||||||
|
get_identity,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
TOKEN_RE = re.compile(r'^\S+-\S+-\S+$')
|
TOKEN_RE = re.compile(r'^\S+-\S+-\S+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var GoSquared={};
|
var GoSquared={};
|
||||||
%(config)s
|
%(config)s
|
||||||
(function(w){
|
(function(w){
|
||||||
|
|
@ -26,7 +27,7 @@ TRACKING_CODE = """
|
||||||
w.addEventListener?w.addEventListener("load",gs,false):w.attachEvent("onload",gs);
|
w.addEventListener?w.addEventListener("load",gs,false):w.attachEvent("onload",gs);
|
||||||
})(window);
|
})(window);
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
TOKEN_CODE = 'GoSquared.acct = "%s";'
|
TOKEN_CODE = 'GoSquared.acct = "%s";'
|
||||||
IDENTIFY_CODE = 'GoSquared.UserName = "%s";'
|
IDENTIFY_CODE = 'GoSquared.UserName = "%s";'
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ def gosquared(parser, token):
|
||||||
"""
|
"""
|
||||||
GoSquared tracking template tag.
|
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.
|
your GoSquared site token in the ``GOSQUARED_SITE_TOKEN`` setting.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
|
|
@ -51,8 +52,10 @@ def gosquared(parser, token):
|
||||||
class GoSquaredNode(Node):
|
class GoSquaredNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.site_token = get_required_setting(
|
self.site_token = get_required_setting(
|
||||||
'GOSQUARED_SITE_TOKEN', TOKEN_RE,
|
'GOSQUARED_SITE_TOKEN',
|
||||||
"must be a string looking like XXX-XXXXXX-X")
|
TOKEN_RE,
|
||||||
|
'must be a string looking like XXX-XXXXXX-X',
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
configs = [TOKEN_CODE % self.site_token]
|
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)
|
||||||
62
analytical/templatetags/hotjar.py
Normal file
62
analytical/templatetags/hotjar.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
"""
|
||||||
|
Hotjar 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
|
||||||
|
|
||||||
|
HOTJAR_TRACKING_CODE = """\
|
||||||
|
<script>
|
||||||
|
(function(h,o,t,j,a,r){
|
||||||
|
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
||||||
|
h._hjSettings={hjid:%(HOTJAR_SITE_ID)s,hjsv:6};
|
||||||
|
a=o.getElementsByTagName('head')[0];
|
||||||
|
r=o.createElement('script');r.async=1;
|
||||||
|
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
||||||
|
a.appendChild(r);
|
||||||
|
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_no_args(token):
|
||||||
|
bits = token.split_contents()
|
||||||
|
if len(bits) > 1:
|
||||||
|
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def hotjar(parser, token):
|
||||||
|
"""
|
||||||
|
Hotjar template tag.
|
||||||
|
"""
|
||||||
|
_validate_no_args(token)
|
||||||
|
return HotjarNode()
|
||||||
|
|
||||||
|
|
||||||
|
class HotjarNode(Node):
|
||||||
|
def __init__(self):
|
||||||
|
self.site_id = get_required_setting(
|
||||||
|
'HOTJAR_SITE_ID',
|
||||||
|
re.compile(r'^\d+$'),
|
||||||
|
'must be (a string containing) a number',
|
||||||
|
)
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
html = HOTJAR_TRACKING_CODE % {'HOTJAR_SITE_ID': self.site_id}
|
||||||
|
if is_internal_ip(context, 'HOTJAR'):
|
||||||
|
return disable_html(html, 'Hotjar')
|
||||||
|
else:
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def contribute_to_analytical(add_node):
|
||||||
|
# ensure properly configured
|
||||||
|
HotjarNode()
|
||||||
|
add_node('head_bottom', HotjarNode)
|
||||||
|
|
@ -2,19 +2,16 @@
|
||||||
HubSpot template tags and filters.
|
HubSpot template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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+$')
|
PORTAL_ID_RE = re.compile(r'^\d+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<!-- Start of Async HubSpot Analytics Code -->
|
<!-- Start of Async HubSpot Analytics Code -->
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
(function(d,s,i,r) {
|
(function(d,s,i,r) {
|
||||||
if (d.getElementById(i)){return;}
|
if (d.getElementById(i)){return;}
|
||||||
var n=d.createElement(s),e=d.getElementsByTagName(s)[0];
|
var n=d.createElement(s),e=d.getElementsByTagName(s)[0];
|
||||||
|
|
@ -23,7 +20,7 @@ TRACKING_CODE = """
|
||||||
})(document,"script","hs-analytics",300000);
|
})(document,"script","hs-analytics",300000);
|
||||||
</script>
|
</script>
|
||||||
<!-- End of Async HubSpot Analytics Code -->
|
<!-- End of Async HubSpot Analytics Code -->
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
@ -33,7 +30,7 @@ def hubspot(parser, token):
|
||||||
"""
|
"""
|
||||||
HubSpot tracking template tag.
|
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.
|
your portal ID (as a string) in the ``HUBSPOT_PORTAL_ID`` setting.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
|
|
@ -44,8 +41,9 @@ def hubspot(parser, token):
|
||||||
|
|
||||||
class HubSpotNode(Node):
|
class HubSpotNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.portal_id = get_required_setting('HUBSPOT_PORTAL_ID',
|
self.portal_id = get_required_setting(
|
||||||
PORTAL_ID_RE, "must be a (string containing a) number")
|
'HUBSPOT_PORTAL_ID', PORTAL_ID_RE, 'must be a (string containing a) number'
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
html = TRACKING_CODE % {'portal_id': self.portal_id}
|
html = TRACKING_CODE % {'portal_id': self.portal_id}
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,68 @@
|
||||||
intercom.io template tags and filters.
|
intercom.io template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
import hashlib
|
||||||
|
import hmac
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import disable_html, get_required_setting, \
|
from analytical.utils import (
|
||||||
is_internal_ip, get_user_from_context, get_identity
|
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-f]+$')
|
APP_ID_RE = re.compile(r'[\da-z]+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script id="IntercomSettingsScriptTag">
|
<script id="IntercomSettingsScriptTag">
|
||||||
window.intercomSettings = %(settings_json)s;
|
window.intercomSettings = %(settings_json)s;
|
||||||
</script>
|
</script>
|
||||||
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
|
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
def _hashable_bytes(data):
|
||||||
|
"""
|
||||||
|
Coerce strings to hashable bytes.
|
||||||
|
"""
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
return data
|
||||||
|
elif isinstance(data, str):
|
||||||
|
return data.encode('ascii') # Fail on anything non-ASCII.
|
||||||
|
else:
|
||||||
|
raise TypeError(data)
|
||||||
|
|
||||||
|
|
||||||
|
def intercom_user_hash(data):
|
||||||
|
"""
|
||||||
|
Return a SHA-256 HMAC `user_hash` as expected by Intercom, if configured.
|
||||||
|
|
||||||
|
Return None if the `INTERCOM_HMAC_SECRET_KEY` setting is not configured.
|
||||||
|
"""
|
||||||
|
if getattr(settings, 'INTERCOM_HMAC_SECRET_KEY', None):
|
||||||
|
return hmac.new(
|
||||||
|
key=_hashable_bytes(settings.INTERCOM_HMAC_SECRET_KEY),
|
||||||
|
msg=_hashable_bytes(data),
|
||||||
|
digestmod=hashlib.sha256,
|
||||||
|
).hexdigest()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@register.tag
|
@register.tag
|
||||||
def intercom(parser, token):
|
def intercom(parser, token):
|
||||||
"""
|
"""
|
||||||
Intercom.io template tag.
|
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``
|
your APP ID account number in the ``INTERCOM_APP_ID``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -41,8 +76,8 @@ def intercom(parser, token):
|
||||||
class IntercomNode(Node):
|
class IntercomNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.app_id = get_required_setting(
|
self.app_id = get_required_setting(
|
||||||
'INTERCOM_APP_ID', APP_ID_RE,
|
'INTERCOM_APP_ID', APP_ID_RE, "must be a string looking like 'XXXXXXX'"
|
||||||
"must be a string looking like 'XXXXXXX'")
|
)
|
||||||
|
|
||||||
def _identify(self, user):
|
def _identify(self, user):
|
||||||
name = user.get_full_name()
|
name = user.get_full_name()
|
||||||
|
|
@ -58,31 +93,36 @@ class IntercomNode(Node):
|
||||||
params[var[9:]] = val
|
params[var[9:]] = val
|
||||||
|
|
||||||
user = get_user_from_context(context)
|
user = get_user_from_context(context)
|
||||||
if user is not None and user.is_authenticated():
|
if user is not None and get_user_is_authenticated(user):
|
||||||
if 'name' not in params:
|
if 'name' not in params:
|
||||||
params['name'] = get_identity(
|
params['name'] = get_identity(context, 'intercom', self._identify, user)
|
||||||
context, 'intercom', self._identify, user)
|
|
||||||
if 'email' not in params and user.email:
|
if 'email' not in params and user.email:
|
||||||
params['email'] = user.email
|
params['email'] = user.email
|
||||||
|
|
||||||
params['created_at'] = int(time.mktime(
|
params.setdefault('user_id', user.pk)
|
||||||
user.date_joined.timetuple()))
|
|
||||||
|
params['created_at'] = int(user.date_joined.timestamp())
|
||||||
else:
|
else:
|
||||||
params['created_at'] = None
|
params['created_at'] = None
|
||||||
|
|
||||||
|
# Generate a user_hash HMAC to verify the user's identity, if configured.
|
||||||
|
# (If both user_id and email are present, the user_id field takes precedence.)
|
||||||
|
# See:
|
||||||
|
# https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
|
||||||
|
user_hash_data = params.get('user_id', params.get('email'))
|
||||||
|
if user_hash_data:
|
||||||
|
user_hash = intercom_user_hash(str(user_hash_data))
|
||||||
|
if user_hash is not None:
|
||||||
|
params.setdefault('user_hash', user_hash)
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
user = get_user_from_context(context)
|
|
||||||
params = self._get_custom_attrs(context)
|
params = self._get_custom_attrs(context)
|
||||||
params["app_id"] = self.app_id
|
params['app_id'] = self.app_id
|
||||||
html = TRACKING_CODE % {
|
html = TRACKING_CODE % {'settings_json': json.dumps(params, sort_keys=True)}
|
||||||
"settings_json": json.dumps(params, sort_keys=True)
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_internal_ip(context, 'INTERCOM') \
|
if is_internal_ip(context, 'INTERCOM'):
|
||||||
or not user or not user.is_authenticated():
|
|
||||||
# Intercom is disabled for non-logged in users.
|
|
||||||
html = disable_html(html, 'Intercom')
|
html = disable_html(html, 'Intercom')
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,18 @@
|
||||||
KISSinsights template tags.
|
KISSinsights template tags.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import get_identity, get_required_setting
|
from analytical.utils import get_identity, get_required_setting
|
||||||
|
|
||||||
|
|
||||||
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
||||||
SITE_CODE_RE = re.compile(r'^[\w]+$')
|
SITE_CODE_RE = re.compile(r'^[\w]+$')
|
||||||
SETUP_CODE = """
|
SETUP_CODE = """
|
||||||
<script type="text/javascript">var _kiq = _kiq || []; %(commands)s</script>
|
<script>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 src="//s3.amazonaws.com/ki.js/%(account_number)s/%(site_code)s.js" async="true"></script>
|
||||||
"""
|
""" # noqa
|
||||||
IDENTIFY_CODE = "_kiq.push(['identify', '%s']);"
|
IDENTIFY_CODE = "_kiq.push(['identify', '%s']);"
|
||||||
SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);"
|
SHOW_SURVEY_CODE = "_kiq.push(['showSurvey', %s]);"
|
||||||
SHOW_SURVEY_CONTEXT_KEY = 'kiss_insights_show_survey'
|
SHOW_SURVEY_CONTEXT_KEY = 'kiss_insights_show_survey'
|
||||||
|
|
@ -30,7 +27,7 @@ def kiss_insights(parser, token):
|
||||||
"""
|
"""
|
||||||
KISSinsights set-up template tag.
|
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
|
your account number and site code in the
|
||||||
``KISS_INSIGHTS_ACCOUNT_NUMBER`` and ``KISS_INSIGHTS_SITE_CODE``
|
``KISS_INSIGHTS_ACCOUNT_NUMBER`` and ``KISS_INSIGHTS_SITE_CODE``
|
||||||
settings.
|
settings.
|
||||||
|
|
@ -44,10 +41,15 @@ def kiss_insights(parser, token):
|
||||||
class KissInsightsNode(Node):
|
class KissInsightsNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.account_number = get_required_setting(
|
self.account_number = get_required_setting(
|
||||||
'KISS_INSIGHTS_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
'KISS_INSIGHTS_ACCOUNT_NUMBER',
|
||||||
"must be (a string containing) a number")
|
ACCOUNT_NUMBER_RE,
|
||||||
self.site_code = get_required_setting('KISS_INSIGHTS_SITE_CODE',
|
'must be (a string containing) a number',
|
||||||
SITE_CODE_RE, "must be a string containing three characters")
|
)
|
||||||
|
self.site_code = get_required_setting(
|
||||||
|
'KISS_INSIGHTS_SITE_CODE',
|
||||||
|
SITE_CODE_RE,
|
||||||
|
'must be a string containing three characters',
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
commands = []
|
commands = []
|
||||||
|
|
@ -55,12 +57,14 @@ class KissInsightsNode(Node):
|
||||||
if identity is not None:
|
if identity is not None:
|
||||||
commands.append(IDENTIFY_CODE % identity)
|
commands.append(IDENTIFY_CODE % identity)
|
||||||
try:
|
try:
|
||||||
commands.append(SHOW_SURVEY_CODE
|
commands.append(SHOW_SURVEY_CODE % context[SHOW_SURVEY_CONTEXT_KEY])
|
||||||
% context[SHOW_SURVEY_CONTEXT_KEY])
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
html = SETUP_CODE % {'account_number': self.account_number,
|
html = SETUP_CODE % {
|
||||||
'site_code': self.site_code, 'commands': " ".join(commands)}
|
'account_number': self.account_number,
|
||||||
|
'site_code': self.site_code,
|
||||||
|
'commands': ' '.join(commands),
|
||||||
|
}
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,21 @@
|
||||||
KISSmetrics template tags.
|
KISSmetrics template tags.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
from analytical.utils import (
|
||||||
get_required_setting
|
disable_html,
|
||||||
|
get_identity,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
|
API_KEY_RE = re.compile(r'^[0-9a-f]{40}$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var _kmq = _kmq || [];
|
var _kmq = _kmq || [];
|
||||||
%(commands)s
|
%(commands)s
|
||||||
function _kms(u){
|
function _kms(u){
|
||||||
|
|
@ -49,7 +50,7 @@ def kiss_metrics(parser, token):
|
||||||
"""
|
"""
|
||||||
KISSinsights tracking template tag.
|
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``
|
your KISSmetrics API key in the ``KISS_METRICS_API_KEY``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -61,9 +62,11 @@ def kiss_metrics(parser, token):
|
||||||
|
|
||||||
class KissMetricsNode(Node):
|
class KissMetricsNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_key = get_required_setting('KISS_METRICS_API_KEY',
|
self.api_key = get_required_setting(
|
||||||
API_KEY_RE,
|
'KISS_METRICS_API_KEY',
|
||||||
"must be a string containing a 40-digit hexadecimal number")
|
API_KEY_RE,
|
||||||
|
'must be a string containing a 40-digit hexadecimal number',
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
commands = []
|
commands = []
|
||||||
|
|
@ -78,18 +81,29 @@ class KissMetricsNode(Node):
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
name, properties = context[EVENT_CONTEXT_KEY]
|
name, properties = context[EVENT_CONTEXT_KEY]
|
||||||
commands.append(EVENT_CODE % {'name': name,
|
commands.append(
|
||||||
'properties': json.dumps(properties, sort_keys=True)})
|
EVENT_CODE
|
||||||
|
% {
|
||||||
|
'name': name,
|
||||||
|
'properties': json.dumps(properties, sort_keys=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
properties = context[PROPERTY_CONTEXT_KEY]
|
properties = context[PROPERTY_CONTEXT_KEY]
|
||||||
commands.append(PROPERTY_CODE % {
|
commands.append(
|
||||||
'properties': json.dumps(properties, sort_keys=True)})
|
PROPERTY_CODE
|
||||||
|
% {
|
||||||
|
'properties': json.dumps(properties, sort_keys=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
html = TRACKING_CODE % {'api_key': self.api_key,
|
html = TRACKING_CODE % {
|
||||||
'commands': " ".join(commands)}
|
'api_key': self.api_key,
|
||||||
|
'commands': ' '.join(commands),
|
||||||
|
}
|
||||||
if is_internal_ip(context, 'KISS_METRICS'):
|
if is_internal_ip(context, 'KISS_METRICS'):
|
||||||
html = disable_html(html, 'KISSmetrics')
|
html = disable_html(html, 'KISSmetrics')
|
||||||
return html
|
return html
|
||||||
|
|
|
||||||
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)
|
||||||
152
analytical/templatetags/matomo.py
Normal file
152
analytical/templatetags/matomo.py
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
"""
|
||||||
|
Matomo template tags and filters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from collections import namedtuple
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
|
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]+)?(/[^/?#@:]+)*$')
|
||||||
|
|
||||||
|
# numeric ID
|
||||||
|
SITEID_RE = re.compile(r'^\d+$')
|
||||||
|
|
||||||
|
TRACKING_CODE = """
|
||||||
|
<script>
|
||||||
|
var _paq = window._paq || [];
|
||||||
|
%(variables)s
|
||||||
|
%(commands)s
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="//%(url)s/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'matomo.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+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<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
|
||||||
|
)
|
||||||
|
IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);'
|
||||||
|
DISABLE_COOKIES_CODE = "_paq.push(['disableCookies']);"
|
||||||
|
|
||||||
|
GIVE_CONSENT_CLASS = 'matomo_give_consent'
|
||||||
|
REMOVE_CONSENT_CLASS = 'matomo_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'
|
||||||
|
|
||||||
|
MatomoVar = namedtuple('MatomoVar', ('index', 'name', 'value', 'scope'))
|
||||||
|
|
||||||
|
|
||||||
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def matomo(parser, token):
|
||||||
|
"""
|
||||||
|
Matomo tracking template tag.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Custom variables can be passed in the ``matomo_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 MatomoNode()
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
variables_code = (
|
||||||
|
VARIABLE_CODE % MatomoVar(*var)._asdict() for var in complete_variables
|
||||||
|
)
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
if getattr(settings, 'MATOMO_DISABLE_COOKIES', False):
|
||||||
|
commands.append(DISABLE_COOKIES_CODE)
|
||||||
|
|
||||||
|
if getattr(settings, 'MATOMO_ASK_FOR_CONSENT', False):
|
||||||
|
commands.append(ASK_FOR_CONSENT_CODE)
|
||||||
|
|
||||||
|
userid = get_identity(context, 'matomo')
|
||||||
|
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, 'MATOMO'):
|
||||||
|
html = disable_html(html, 'Matomo')
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def contribute_to_analytical(add_node):
|
||||||
|
MatomoNode() # ensure properly configured
|
||||||
|
add_node('body_bottom', MatomoNode)
|
||||||
|
|
@ -2,29 +2,30 @@
|
||||||
Mixpanel template tags and filters.
|
Mixpanel template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
from analytical.utils import (
|
||||||
get_required_setting
|
disable_html,
|
||||||
|
get_identity,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
MIXPANEL_API_TOKEN_RE = re.compile(r'^[0-9a-f]{32}$')
|
MIXPANEL_API_TOKEN_RE = re.compile(r'^[0-9a-f]{32}$')
|
||||||
TRACKING_CODE = """
|
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,
|
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||[]);
|
e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);
|
||||||
mixpanel.init('%(token)s');
|
mixpanel.init('%(token)s');
|
||||||
%(commands)s
|
%(commands)s
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
IDENTIFY_CODE = "mixpanel.identify('%s');"
|
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_CODE = "mixpanel.track('%(name)s', %(properties)s);"
|
||||||
EVENT_CONTEXT_KEY = 'mixpanel_event'
|
EVENT_CONTEXT_KEY = 'mixpanel_event'
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ def mixpanel(parser, token):
|
||||||
"""
|
"""
|
||||||
Mixpanel tracking template tag.
|
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.
|
your Mixpanel token in the ``MIXPANEL_API_TOKEN`` setting.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
|
|
@ -48,26 +49,39 @@ def mixpanel(parser, token):
|
||||||
class MixpanelNode(Node):
|
class MixpanelNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._token = get_required_setting(
|
self._token = get_required_setting(
|
||||||
'MIXPANEL_API_TOKEN', MIXPANEL_API_TOKEN_RE,
|
'MIXPANEL_API_TOKEN',
|
||||||
"must be a string containing a 32-digit hexadecimal number")
|
MIXPANEL_API_TOKEN_RE,
|
||||||
|
'must be a string containing a 32-digit hexadecimal number',
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
commands = []
|
commands = []
|
||||||
identity = get_identity(context, 'mixpanel')
|
identity = get_identity(context, 'mixpanel')
|
||||||
if identity is not None:
|
if identity is not None:
|
||||||
if isinstance(identity, dict):
|
if isinstance(identity, dict):
|
||||||
commands.append(IDENTIFY_CODE % identity.get('id', identity.get('username')))
|
commands.append(
|
||||||
commands.append(IDENTIFY_PROPERTIES % json.dumps(identity, sort_keys=True))
|
IDENTIFY_CODE % identity.get('id', identity.get('username'))
|
||||||
|
)
|
||||||
|
commands.append(
|
||||||
|
IDENTIFY_PROPERTIES % json.dumps(identity, sort_keys=True)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
commands.append(IDENTIFY_CODE % identity)
|
commands.append(IDENTIFY_CODE % identity)
|
||||||
try:
|
try:
|
||||||
name, properties = context[EVENT_CONTEXT_KEY]
|
name, properties = context[EVENT_CONTEXT_KEY]
|
||||||
commands.append(EVENT_CODE % {'name': name,
|
commands.append(
|
||||||
'properties': json.dumps(properties, sort_keys=True)})
|
EVENT_CODE
|
||||||
|
% {
|
||||||
|
'name': name,
|
||||||
|
'properties': json.dumps(properties, sort_keys=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
html = TRACKING_CODE % {'token': self._token,
|
html = TRACKING_CODE % {
|
||||||
'commands': " ".join(commands)}
|
'token': self._token,
|
||||||
|
'commands': ' '.join(commands),
|
||||||
|
}
|
||||||
if is_internal_ip(context, 'MIXPANEL'):
|
if is_internal_ip(context, 'MIXPANEL'):
|
||||||
html = disable_html(html, 'Mixpanel')
|
html = disable_html(html, 'Mixpanel')
|
||||||
return mark_safe(html)
|
return mark_safe(html)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
Olark template tags.
|
Olark template tags.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
@ -11,14 +9,13 @@ from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import get_identity, get_required_setting
|
from analytical.utils import get_identity, get_required_setting
|
||||||
|
|
||||||
|
|
||||||
SITE_ID_RE = re.compile(r'^\d+-\d+-\d+-\d+$')
|
SITE_ID_RE = re.compile(r'^\d+-\d+-\d+-\d+$')
|
||||||
SETUP_CODE = """
|
SETUP_CODE = """
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
/*{literal}<![CDATA[*/ window.olark||(function(k){var g=window,j=document,a=g.location.protocol=="https:"?"https:":"http:",i=k.name,b="load",h="addEventListener";(function(){g[i]=function(){(c.s=c.s||[]).push(arguments)};var c=g[i]._={},f=k.methods.length;while(f--){(function(l){g[i][l]=function(){g[i]("call",l,arguments)}})(k.methods[f])}c.l=k.loader;c.i=arguments.callee;c.p={0:+new Date};c.P=function(l){c.p[l]=new Date-c.p[0]};function e(){c.P(b);g[i](b)}g[h]?g[h](b,e,false):g.attachEvent("on"+b,e);c.P(1);var d=j.createElement("script"),m=document.getElementsByTagName("script")[0];d.type="text/javascript";d.async=true;d.src=a+"//"+c.l;m.parentNode.insertBefore(d,m);c.P(2)})()})({loader:(function(a){return "static.olark.com/jsclient/loader1.js?ts="+(a?a[1]:(+new Date))})(document.cookie.match(/olarkld=([0-9]+)/)),name:"olark",methods:["configure","extend","declare","identify"]}); olark.identify('%(site_id)s');/*]]>{/literal}*/
|
/*{literal}<![CDATA[*/ window.olark||(function(k){var g=window,j=document,a=g.location.protocol=="https:"?"https:":"http:",i=k.name,b="load",h="addEventListener";(function(){g[i]=function(){(c.s=c.s||[]).push(arguments)};var c=g[i]._={},f=k.methods.length;while(f--){(function(l){g[i][l]=function(){g[i]("call",l,arguments)}})(k.methods[f])}c.l=k.loader;c.i=arguments.callee;c.p={0:+new Date};c.P=function(l){c.p[l]=new Date-c.p[0]};function e(){c.P(b);g[i](b)}g[h]?g[h](b,e,false):g.attachEvent("on"+b,e);c.P(1);var d=j.createElement("script"),m=document.getElementsByTagName("script")[0];d.type="text/javascript";d.async=true;d.src=a+"//"+c.l;m.parentNode.insertBefore(d,m);c.P(2)})()})({loader:(function(a){return "static.olark.com/jsclient/loader1.js?ts="+(a?a[1]:(+new Date))})(document.cookie.match(/olarkld=([0-9]+)/)),name:"olark",methods:["configure","extend","declare","identify"]}); olark.identify('%(site_id)s');/*]]>{/literal}*/
|
||||||
%(extra_code)s
|
%(extra_code)s
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
NICKNAME_CODE = "olark('api.chat.updateVisitorNickname', {snippet: '%s'});"
|
NICKNAME_CODE = "olark('api.chat.updateVisitorNickname', {snippet: '%s'});"
|
||||||
NICKNAME_CONTEXT_KEY = 'olark_nickname'
|
NICKNAME_CONTEXT_KEY = 'olark_nickname'
|
||||||
FULLNAME_CODE = "olark('api.visitor.updateFullName', {{fullName: '{0}'}});"
|
FULLNAME_CODE = "olark('api.visitor.updateFullName', {{fullName: '{0}'}});"
|
||||||
|
|
@ -27,15 +24,30 @@ EMAIL_CODE = "olark('api.visitor.updateEmailAddress', {{emailAddress: '{0}'}});"
|
||||||
EMAIL_CONTEXT_KEY = 'olark_email'
|
EMAIL_CONTEXT_KEY = 'olark_email'
|
||||||
STATUS_CODE = "olark('api.chat.updateVisitorStatus', {snippet: %s});"
|
STATUS_CODE = "olark('api.chat.updateVisitorStatus', {snippet: %s});"
|
||||||
STATUS_CONTEXT_KEY = 'olark_status'
|
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 = set(["welcome_title", "chatting_title", "unavailable_title",
|
MESSAGE_KEYS = {
|
||||||
"busy_title", "away_message", "loading_title", "welcome_message",
|
'welcome_title',
|
||||||
"busy_message", "chat_input_text", "name_input_text",
|
'chatting_title',
|
||||||
"email_input_text", "offline_note_message", "send_button_text",
|
'unavailable_title',
|
||||||
"offline_note_thankyou_text", "offline_note_error_text",
|
'busy_title',
|
||||||
"offline_note_sending_text", "operator_is_typing_text",
|
'away_message',
|
||||||
"operator_has_stopped_typing_text", "introduction_error_text",
|
'loading_title',
|
||||||
"introduction_messages", "introduction_submit_button_text"])
|
'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()
|
register = Library()
|
||||||
|
|
||||||
|
|
@ -45,7 +57,7 @@ def olark(parser, token):
|
||||||
"""
|
"""
|
||||||
Olark set-up template tag.
|
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.
|
your site ID in the ``OLARK_SITE_ID`` setting.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
|
|
@ -56,8 +68,11 @@ def olark(parser, token):
|
||||||
|
|
||||||
class OlarkNode(Node):
|
class OlarkNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.site_id = get_required_setting('OLARK_SITE_ID', SITE_ID_RE,
|
self.site_id = get_required_setting(
|
||||||
"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):
|
def render(self, context):
|
||||||
extra_code = []
|
extra_code = []
|
||||||
|
|
@ -76,19 +91,22 @@ class OlarkNode(Node):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
extra_code.append(STATUS_CODE %
|
extra_code.append(
|
||||||
json.dumps(context[STATUS_CONTEXT_KEY], sort_keys=True))
|
STATUS_CODE % json.dumps(context[STATUS_CONTEXT_KEY], sort_keys=True)
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
extra_code.extend(self._get_configuration(context))
|
extra_code.extend(self._get_configuration(context))
|
||||||
html = SETUP_CODE % {'site_id': self.site_id,
|
html = SETUP_CODE % {
|
||||||
'extra_code': " ".join(extra_code)}
|
'site_id': self.site_id,
|
||||||
|
'extra_code': ' '.join(extra_code),
|
||||||
|
}
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def _get_nickname(self, user):
|
def _get_nickname(self, user):
|
||||||
name = user.get_full_name()
|
name = user.get_full_name()
|
||||||
if name:
|
if name:
|
||||||
return "%s (%s)" % (name, user.username)
|
return '%s (%s)' % (name, user.username)
|
||||||
else:
|
else:
|
||||||
return user.username
|
return user.username
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@
|
||||||
Optimizely template tags and filters.
|
Optimizely template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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+$')
|
ACCOUNT_NUMBER_RE = re.compile(r'^\d+$')
|
||||||
SETUP_CODE = """<script src="//cdn.optimizely.com/js/%(account_number)s.js"></script>"""
|
SETUP_CODE = """<script src="//cdn.optimizely.com/js/%(account_number)s.js"></script>"""
|
||||||
|
|
@ -23,7 +20,7 @@ def optimizely(parser, token):
|
||||||
"""
|
"""
|
||||||
Optimizely template tag.
|
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``
|
your Optimizely account number in the ``OPTIMIZELY_ACCOUNT_NUMBER``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -36,8 +33,10 @@ def optimizely(parser, token):
|
||||||
class OptimizelyNode(Node):
|
class OptimizelyNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.account_number = get_required_setting(
|
self.account_number = get_required_setting(
|
||||||
'OPTIMIZELY_ACCOUNT_NUMBER', ACCOUNT_NUMBER_RE,
|
'OPTIMIZELY_ACCOUNT_NUMBER',
|
||||||
"must be a string looking like 'XXXXXXX'")
|
ACCOUNT_NUMBER_RE,
|
||||||
|
"must be a string looking like 'XXXXXXX'",
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
html = SETUP_CODE % {'account_number': self.account_number}
|
html = SETUP_CODE % {'account_number': self.account_number}
|
||||||
|
|
|
||||||
|
|
@ -2,37 +2,38 @@
|
||||||
Performable template tags and filters.
|
Performable template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from analytical.utils import is_internal_ip, disable_html, get_identity, \
|
from analytical.utils import (
|
||||||
get_required_setting
|
disable_html,
|
||||||
|
get_identity,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
API_KEY_RE = re.compile(r'^\w+$')
|
API_KEY_RE = re.compile(r'^\w+$')
|
||||||
SETUP_CODE = """
|
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 = """
|
IDENTIFY_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var _paq = _paq || [];
|
var _paq = _paq || [];
|
||||||
_paq.push(["identify", {identity: "%s"}]);
|
_paq.push(["identify", {identity: "%s"}]);
|
||||||
</script>
|
</script>
|
||||||
"""
|
"""
|
||||||
EMBED_CODE = """
|
EMBED_CODE = """
|
||||||
<script type="text/javascript" src="//d1nu2rn22elx8m.cloudfront.net/performable/embed/page.js"></script>
|
<script src="//d1nu2rn22elx8m.cloudfront.net/performable/embed/page.js"></script>
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var $f = new PerformableEmbed();
|
var $f = new PerformableEmbed();
|
||||||
$f.initialize({'host': '%(hostname)s', 'page': '%(page_id)s'});
|
$f.initialize({'host': '%(hostname)s', 'page': '%(page_id)s'});
|
||||||
$f.write();
|
$f.write();
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
@ -42,7 +43,7 @@ def performable(parser, token):
|
||||||
"""
|
"""
|
||||||
Performable template tag.
|
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``
|
supply your Performable API key in the ``PERFORMABLE_API_KEY``
|
||||||
setting.
|
setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,14 +55,15 @@ def performable(parser, token):
|
||||||
|
|
||||||
class PerformableNode(Node):
|
class PerformableNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.api_key = get_required_setting('PERFORMABLE_API_KEY', API_KEY_RE,
|
self.api_key = get_required_setting(
|
||||||
"must be a string looking like 'XXXXX'")
|
'PERFORMABLE_API_KEY', API_KEY_RE, "must be a string looking like 'XXXXX'"
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
html = SETUP_CODE % {'api_key': self.api_key}
|
html = SETUP_CODE % {'api_key': self.api_key}
|
||||||
identity = get_identity(context, 'performable')
|
identity = get_identity(context, 'performable')
|
||||||
if identity is not None:
|
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'):
|
if is_internal_ip(context, 'PERFORMABLE'):
|
||||||
html = disable_html(html, 'Performable')
|
html = disable_html(html, 'Performable')
|
||||||
return html
|
return html
|
||||||
|
|
@ -72,7 +74,13 @@ def performable_embed(hostname, page_id):
|
||||||
"""
|
"""
|
||||||
Include a Performable landing page.
|
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):
|
def contribute_to_analytical(add_node):
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
"""
|
|
||||||
Piwik template tags and filters.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
from itertools import chain
|
|
||||||
import re
|
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
|
||||||
|
|
||||||
from analytical.utils import (is_internal_ip, disable_html,
|
|
||||||
get_required_setting, get_identity)
|
|
||||||
|
|
||||||
|
|
||||||
# domain name (characters separated by a dot), optional URI path, no slash
|
|
||||||
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(/[^/?#@:]+)*$')
|
|
||||||
|
|
||||||
# numeric ID
|
|
||||||
SITEID_RE = re.compile(r'^\d+$')
|
|
||||||
|
|
||||||
TRACKING_CODE = """
|
|
||||||
<script type="text/javascript">
|
|
||||||
var _paq = _paq || [];
|
|
||||||
%(variables)s
|
|
||||||
_paq.push(['trackPageView']);
|
|
||||||
_paq.push(['enableLinkTracking']);
|
|
||||||
(function() {
|
|
||||||
var u=(("https:" == document.location.protocol) ? "https" : "http") + "://%(url)s/";
|
|
||||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
|
||||||
_paq.push(['setSiteId', %(siteid)s]);
|
|
||||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
|
|
||||||
g.defer=true; g.async=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<noscript><p><img src="http://%(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"]);'
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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,21 +2,15 @@
|
||||||
Rating@Mail.ru template tags and filters.
|
Rating@Mail.ru template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import is_internal_ip, disable_html, \
|
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||||
get_required_setting
|
|
||||||
|
|
||||||
|
|
||||||
COUNTER_ID_RE = re.compile(r'^\d{7}$')
|
COUNTER_ID_RE = re.compile(r'^\d{7}$')
|
||||||
COUNTER_CODE = """
|
COUNTER_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var _tmr = window._tmr || (window._tmr = []);
|
var _tmr = window._tmr || (window._tmr = []);
|
||||||
_tmr.push({id: "%(counter_id)s", type: "pageView", start: (new Date()).getTime()});
|
_tmr.push({id: "%(counter_id)s", type: "pageView", start: (new Date()).getTime()});
|
||||||
(function (d, w, id) {
|
(function (d, w, id) {
|
||||||
|
|
@ -30,7 +24,7 @@ COUNTER_CODE = """
|
||||||
<noscript><div style="position:absolute;left:-10000px;">
|
<noscript><div style="position:absolute;left:-10000px;">
|
||||||
<img src="//top-fwz1.mail.ru/counter?id=%(counter_id)s;js=na" style="border:0;" height="1" width="1" alt="Rating@Mail.ru" />
|
<img src="//top-fwz1.mail.ru/counter?id=%(counter_id)s;js=na" style="border:0;" height="1" width="1" alt="Rating@Mail.ru" />
|
||||||
</div></noscript>
|
</div></noscript>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
@ -41,7 +35,7 @@ def rating_mailru(parser, token):
|
||||||
"""
|
"""
|
||||||
Rating@Mail.ru counter template tag.
|
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
|
your website counter ID (as a string) in the
|
||||||
``RATING_MAILRU_COUNTER_ID`` setting.
|
``RATING_MAILRU_COUNTER_ID`` setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,8 +48,10 @@ def rating_mailru(parser, token):
|
||||||
class RatingMailruNode(Node):
|
class RatingMailruNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.counter_id = get_required_setting(
|
self.counter_id = get_required_setting(
|
||||||
'RATING_MAILRU_COUNTER_ID', COUNTER_ID_RE,
|
'RATING_MAILRU_COUNTER_ID',
|
||||||
"must be (a string containing) a number'")
|
COUNTER_ID_RE,
|
||||||
|
"must be (a string containing) a number'",
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
html = COUNTER_CODE % {
|
html = COUNTER_CODE % {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
SnapEngage template tags.
|
SnapEngage template tags.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -12,7 +10,6 @@ from django.utils import translation
|
||||||
|
|
||||||
from analytical.utils import get_identity, get_required_setting
|
from analytical.utils import get_identity, get_required_setting
|
||||||
|
|
||||||
|
|
||||||
BUTTON_LOCATION_LEFT = 0
|
BUTTON_LOCATION_LEFT = 0
|
||||||
BUTTON_LOCATION_RIGHT = 1
|
BUTTON_LOCATION_RIGHT = 1
|
||||||
BUTTON_LOCATION_TOP = 2
|
BUTTON_LOCATION_TOP = 2
|
||||||
|
|
@ -27,17 +24,21 @@ FORM_POSITION_TOP_RIGHT = 'tr'
|
||||||
FORM_POSITION_BOTTOM_LEFT = 'bl'
|
FORM_POSITION_BOTTOM_LEFT = 'bl'
|
||||||
FORM_POSITION_BOTTOM_RIGHT = 'br'
|
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 = """
|
SETUP_CODE = """
|
||||||
<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 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>
|
||||||
%(settings_code)s
|
%(settings_code)s
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
DOMAIN_CODE = 'SnapABug.setDomain("%s");'
|
DOMAIN_CODE = 'SnapABug.setDomain("%s");'
|
||||||
SECURE_CONNECTION_CODE = 'SnapABug.setSecureConnexion();'
|
SECURE_CONNECTION_CODE = 'SnapABug.setSecureConnexion();'
|
||||||
INIT_CODE = 'SnapABug.init("%s");'
|
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");'
|
SETBUTTON_CODE = 'SnapABug.setButton("%s");'
|
||||||
SETEMAIL_CODE = 'SnapABug.setUserEmail("%s"%s);'
|
SETEMAIL_CODE = 'SnapABug.setUserEmail("%s"%s);'
|
||||||
SETLOCALE_CODE = 'SnapABug.setLocale("%s");'
|
SETLOCALE_CODE = 'SnapABug.setLocale("%s");'
|
||||||
|
|
@ -58,7 +59,7 @@ def snapengage(parser, token):
|
||||||
"""
|
"""
|
||||||
SnapEngage set-up template tag.
|
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.
|
your widget ID in the ``SNAPENGAGE_WIDGET_ID`` setting.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
|
|
@ -69,21 +70,25 @@ def snapengage(parser, token):
|
||||||
|
|
||||||
class SnapEngageNode(Node):
|
class SnapEngageNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.widget_id = get_required_setting('SNAPENGAGE_WIDGET_ID',
|
self.widget_id = get_required_setting(
|
||||||
WIDGET_ID_RE, "must be a string looking like this: "
|
'SNAPENGAGE_WIDGET_ID',
|
||||||
"'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'")
|
WIDGET_ID_RE,
|
||||||
|
"must be a string looking like this: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'",
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
settings_code = []
|
settings_code = []
|
||||||
|
|
||||||
domain = self._get_setting(context, 'snapengage_domain',
|
domain = self._get_setting(context, 'snapengage_domain', 'SNAPENGAGE_DOMAIN')
|
||||||
'SNAPENGAGE_DOMAIN')
|
|
||||||
if domain is not None:
|
if domain is not None:
|
||||||
settings_code.append(DOMAIN_CODE % domain)
|
settings_code.append(DOMAIN_CODE % domain)
|
||||||
|
|
||||||
secure_connection = self._get_setting(context,
|
secure_connection = self._get_setting(
|
||||||
'snapengage_secure_connection', 'SNAPENGAGE_SECURE_CONNECTION',
|
context,
|
||||||
False)
|
'snapengage_secure_connection',
|
||||||
|
'SNAPENGAGE_SECURE_CONNECTION',
|
||||||
|
False,
|
||||||
|
)
|
||||||
if secure_connection:
|
if secure_connection:
|
||||||
settings_code.append(SECURE_CONNECTION_CODE)
|
settings_code.append(SECURE_CONNECTION_CODE)
|
||||||
|
|
||||||
|
|
@ -91,80 +96,101 @@ class SnapEngageNode(Node):
|
||||||
if email is None:
|
if email is None:
|
||||||
email = get_identity(context, 'snapengage', lambda u: u.email)
|
email = get_identity(context, 'snapengage', lambda u: u.email)
|
||||||
if email is not None:
|
if email is not None:
|
||||||
if self._get_setting(context, 'snapengage_readonly_email',
|
if self._get_setting(
|
||||||
'SNAPENGAGE_READONLY_EMAIL', False):
|
context, 'snapengage_readonly_email', 'SNAPENGAGE_READONLY_EMAIL', False
|
||||||
|
):
|
||||||
readonly_tail = ',true'
|
readonly_tail = ',true'
|
||||||
else:
|
else:
|
||||||
readonly_tail = ''
|
readonly_tail = ''
|
||||||
settings_code.append(SETEMAIL_CODE % (email, readonly_tail))
|
settings_code.append(SETEMAIL_CODE % (email, readonly_tail))
|
||||||
|
|
||||||
locale = self._get_setting(context, 'snapengage_locale',
|
locale = self._get_setting(context, 'snapengage_locale', 'SNAPENGAGE_LOCALE')
|
||||||
'SNAPENGAGE_LOCALE')
|
|
||||||
if locale is None:
|
if locale is None:
|
||||||
locale = translation.to_locale(translation.get_language())
|
locale = translation.to_locale(translation.get_language())
|
||||||
settings_code.append(SETLOCALE_CODE % locale)
|
settings_code.append(SETLOCALE_CODE % locale)
|
||||||
|
|
||||||
form_position = self._get_setting(context,
|
form_position = self._get_setting(
|
||||||
'snapengage_form_position', 'SNAPENGAGE_FORM_POSITION')
|
context, 'snapengage_form_position', 'SNAPENGAGE_FORM_POSITION'
|
||||||
|
)
|
||||||
if form_position is not None:
|
if form_position is not None:
|
||||||
settings_code.append(FORM_POSITION_CODE % form_position)
|
settings_code.append(FORM_POSITION_CODE % form_position)
|
||||||
|
|
||||||
form_top_position = self._get_setting(context,
|
form_top_position = self._get_setting(
|
||||||
'snapengage_form_top_position', 'SNAPENGAGE_FORM_TOP_POSITION')
|
context, 'snapengage_form_top_position', 'SNAPENGAGE_FORM_TOP_POSITION'
|
||||||
|
)
|
||||||
if form_top_position is not None:
|
if form_top_position is not None:
|
||||||
settings_code.append(FORM_TOP_POSITION_CODE % form_top_position)
|
settings_code.append(FORM_TOP_POSITION_CODE % form_top_position)
|
||||||
|
|
||||||
show_offline = self._get_setting(context, 'snapengage_show_offline',
|
show_offline = self._get_setting(
|
||||||
'SNAPENGAGE_SHOW_OFFLINE', True)
|
context, 'snapengage_show_offline', 'SNAPENGAGE_SHOW_OFFLINE', True
|
||||||
|
)
|
||||||
if not show_offline:
|
if not show_offline:
|
||||||
settings_code.append(DISABLE_OFFLINE_CODE)
|
settings_code.append(DISABLE_OFFLINE_CODE)
|
||||||
|
|
||||||
screenshots = self._get_setting(context, 'snapengage_screenshots',
|
screenshots = self._get_setting(
|
||||||
'SNAPENGAGE_SCREENSHOTS', True)
|
context, 'snapengage_screenshots', 'SNAPENGAGE_SCREENSHOTS', True
|
||||||
|
)
|
||||||
if not screenshots:
|
if not screenshots:
|
||||||
settings_code.append(DISABLE_SCREENSHOT_CODE)
|
settings_code.append(DISABLE_SCREENSHOT_CODE)
|
||||||
|
|
||||||
offline_screenshots = self._get_setting(context,
|
offline_screenshots = self._get_setting(
|
||||||
|
context,
|
||||||
'snapengage_offline_screenshots',
|
'snapengage_offline_screenshots',
|
||||||
'SNAPENGAGE_OFFLINE_SCREENSHOTS', True)
|
'SNAPENGAGE_OFFLINE_SCREENSHOTS',
|
||||||
|
True,
|
||||||
|
)
|
||||||
if not offline_screenshots:
|
if not offline_screenshots:
|
||||||
settings_code.append(DISABLE_OFFLINE_SCREENSHOT_CODE)
|
settings_code.append(DISABLE_OFFLINE_SCREENSHOT_CODE)
|
||||||
|
|
||||||
if not context.get('snapengage_proactive_chat', True):
|
if not context.get('snapengage_proactive_chat', True):
|
||||||
settings_code.append(DISABLE_PROACTIVE_CHAT_CODE)
|
settings_code.append(DISABLE_PROACTIVE_CHAT_CODE)
|
||||||
|
|
||||||
sounds = self._get_setting(context, 'snapengage_sounds',
|
sounds = self._get_setting(
|
||||||
'SNAPENGAGE_SOUNDS', True)
|
context, 'snapengage_sounds', 'SNAPENGAGE_SOUNDS', True
|
||||||
|
)
|
||||||
if not sounds:
|
if not sounds:
|
||||||
settings_code.append(DISABLE_SOUNDS_CODE)
|
settings_code.append(DISABLE_SOUNDS_CODE)
|
||||||
|
|
||||||
button_effect = self._get_setting(context, 'snapengage_button_effect',
|
button_effect = self._get_setting(
|
||||||
'SNAPENGAGE_BUTTON_EFFECT')
|
context, 'snapengage_button_effect', 'SNAPENGAGE_BUTTON_EFFECT'
|
||||||
|
)
|
||||||
if button_effect is not None:
|
if button_effect is not None:
|
||||||
settings_code.append(BUTTONEFFECT_CODE % button_effect)
|
settings_code.append(BUTTONEFFECT_CODE % button_effect)
|
||||||
|
|
||||||
button = self._get_setting(context, 'snapengage_button',
|
button = self._get_setting(
|
||||||
'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT)
|
context, 'snapengage_button', 'SNAPENGAGE_BUTTON', BUTTON_STYLE_DEFAULT
|
||||||
|
)
|
||||||
if button == BUTTON_STYLE_NONE:
|
if button == BUTTON_STYLE_NONE:
|
||||||
settings_code.append(INIT_CODE % self.widget_id)
|
settings_code.append(INIT_CODE % self.widget_id)
|
||||||
else:
|
else:
|
||||||
if not isinstance(button, int):
|
if not isinstance(button, int):
|
||||||
# Assume button as a URL to a custom image
|
# Assume button as a URL to a custom image
|
||||||
settings_code.append(SETBUTTON_CODE % button)
|
settings_code.append(SETBUTTON_CODE % button)
|
||||||
button_location = self._get_setting(context,
|
button_location = self._get_setting(
|
||||||
'snapengage_button_location', 'SNAPENGAGE_BUTTON_LOCATION',
|
context,
|
||||||
BUTTON_LOCATION_LEFT)
|
'snapengage_button_location',
|
||||||
button_offset = self._get_setting(context,
|
'SNAPENGAGE_BUTTON_LOCATION',
|
||||||
'snapengage_button_location_offset',
|
BUTTON_LOCATION_LEFT,
|
||||||
'SNAPENGAGE_BUTTON_LOCATION_OFFSET', '55%')
|
)
|
||||||
settings_code.append(ADDBUTTON_CODE % {
|
button_offset = self._get_setting(
|
||||||
'id': self.widget_id,
|
context,
|
||||||
'location': button_location,
|
'snapengage_button_location_offset',
|
||||||
'offset': button_offset,
|
'SNAPENGAGE_BUTTON_LOCATION_OFFSET',
|
||||||
'dynamic_tail': ',true' if (button == BUTTON_STYLE_LIVE) else '',
|
'55%',
|
||||||
})
|
)
|
||||||
html = SETUP_CODE % {'widget_id': self.widget_id,
|
settings_code.append(
|
||||||
'settings_code': " ".join(settings_code)}
|
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),
|
||||||
|
}
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def _get_setting(self, context, context_key, setting=None, default=None):
|
def _get_setting(self, context, context_key, setting=None, default=None):
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,16 @@
|
||||||
Spring Metrics template tags and filters.
|
Spring Metrics template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import get_identity, is_internal_ip, disable_html, \
|
from analytical.utils import (
|
||||||
get_required_setting
|
disable_html,
|
||||||
|
get_identity,
|
||||||
|
get_required_setting,
|
||||||
|
is_internal_ip,
|
||||||
|
)
|
||||||
|
|
||||||
TRACKING_ID_RE = re.compile(r'^[0-9a-f]+$')
|
TRACKING_ID_RE = re.compile(r'^[0-9a-f]+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
|
|
@ -29,8 +30,7 @@ TRACKING_CODE = """
|
||||||
)();
|
)();
|
||||||
%(custom_commands)s
|
%(custom_commands)s
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ def spring_metrics(parser, token):
|
||||||
"""
|
"""
|
||||||
Spring Metrics tracking template tag.
|
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
|
your Spring Metrics Tracking ID in the
|
||||||
``SPRING_METRICS_TRACKING_ID`` setting.
|
``SPRING_METRICS_TRACKING_ID`` setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -53,8 +53,8 @@ def spring_metrics(parser, token):
|
||||||
class SpringMetricsNode(Node):
|
class SpringMetricsNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tracking_id = get_required_setting(
|
self.tracking_id = get_required_setting(
|
||||||
'SPRING_METRICS_TRACKING_ID',
|
'SPRING_METRICS_TRACKING_ID', TRACKING_ID_RE, 'must be a hexadecimal string'
|
||||||
TRACKING_ID_RE, "must be a hexadecimal string")
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
custom = {}
|
custom = {}
|
||||||
|
|
@ -63,8 +63,7 @@ class SpringMetricsNode(Node):
|
||||||
if var.startswith('spring_metrics_'):
|
if var.startswith('spring_metrics_'):
|
||||||
custom[var[15:]] = val
|
custom[var[15:]] = val
|
||||||
if 'email' not in custom:
|
if 'email' not in custom:
|
||||||
identity = get_identity(context, 'spring_metrics',
|
identity = get_identity(context, 'spring_metrics', lambda u: u.email)
|
||||||
lambda u: u.email)
|
|
||||||
if identity is not None:
|
if identity is not None:
|
||||||
custom['email'] = identity
|
custom['email'] = identity
|
||||||
|
|
||||||
|
|
@ -81,9 +80,11 @@ class SpringMetricsNode(Node):
|
||||||
convert = params.pop('convert', None)
|
convert = params.pop('convert', None)
|
||||||
if convert is not None:
|
if convert is not None:
|
||||||
commands.append("_springMetq.push(['convert', '%s'])" % convert)
|
commands.append("_springMetq.push(['convert', '%s'])" % convert)
|
||||||
commands.extend("_springMetq.push(['setdata', {'%s': '%s'}]);"
|
commands.extend(
|
||||||
% (var, val) for var, val in params.items())
|
"_springMetq.push(['setdata', {'%s': '%s'}]);" % (var, val)
|
||||||
return " ".join(commands)
|
for var, val in params.items()
|
||||||
|
)
|
||||||
|
return ' '.join(commands)
|
||||||
|
|
||||||
|
|
||||||
def contribute_to_analytical(add_node):
|
def contribute_to_analytical(add_node):
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,17 @@
|
||||||
UserVoice template tags.
|
UserVoice template tags.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
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]*$')
|
WIDGET_KEY_RE = re.compile(r'^[a-zA-Z0-9]*$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
|
|
||||||
UserVoice=window.UserVoice||[];(function(){
|
UserVoice=window.UserVoice||[];(function(){
|
||||||
var uv=document.createElement('script');uv.type='text/javascript';
|
var uv=document.createElement('script');uv.type='text/javascript';
|
||||||
|
|
@ -37,7 +35,7 @@ def uservoice(parser, token):
|
||||||
"""
|
"""
|
||||||
UserVoice tracking template tag.
|
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``
|
your UserVoice Widget Key in the ``USERVOICE_WIDGET_KEY``
|
||||||
setting or the ``uservoice_widget_key`` template context variable.
|
setting or the ``uservoice_widget_key`` template context variable.
|
||||||
"""
|
"""
|
||||||
|
|
@ -49,8 +47,9 @@ def uservoice(parser, token):
|
||||||
|
|
||||||
class UserVoiceNode(Node):
|
class UserVoiceNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.default_widget_key = get_required_setting('USERVOICE_WIDGET_KEY',
|
self.default_widget_key = get_required_setting(
|
||||||
WIDGET_KEY_RE, "must be an alphanumeric string")
|
'USERVOICE_WIDGET_KEY', WIDGET_KEY_RE, 'must be an alphanumeric string'
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
widget_key = context.get('uservoice_widget_key')
|
widget_key = context.get('uservoice_widget_key')
|
||||||
|
|
@ -67,13 +66,16 @@ class UserVoiceNode(Node):
|
||||||
if identity:
|
if identity:
|
||||||
identity = IDENTITY % {'options': json.dumps(identity, sort_keys=True)}
|
identity = IDENTITY % {'options': json.dumps(identity, sort_keys=True)}
|
||||||
|
|
||||||
trigger = context.get('uservoice_add_trigger',
|
trigger = context.get(
|
||||||
getattr(settings, 'USERVOICE_ADD_TRIGGER', True))
|
'uservoice_add_trigger', getattr(settings, 'USERVOICE_ADD_TRIGGER', True)
|
||||||
|
)
|
||||||
|
|
||||||
html = TRACKING_CODE % {'widget_key': widget_key,
|
html = TRACKING_CODE % {
|
||||||
'options': json.dumps(options, sort_keys=True),
|
'widget_key': widget_key,
|
||||||
'trigger': TRIGGER if trigger else '',
|
'options': json.dumps(options, sort_keys=True),
|
||||||
'identity': identity if identity else ''}
|
'trigger': TRIGGER if trigger else '',
|
||||||
|
'identity': identity if identity else '',
|
||||||
|
}
|
||||||
return html
|
return html
|
||||||
|
|
||||||
def _identify(self, user):
|
def _identify(self, user):
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,26 @@
|
||||||
Woopra template tags and filters.
|
Woopra template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import (
|
from analytical.utils import (
|
||||||
|
AnalyticalException,
|
||||||
disable_html,
|
disable_html,
|
||||||
get_identity,
|
get_identity,
|
||||||
get_required_setting,
|
get_required_setting,
|
||||||
get_user_from_context,
|
get_user_from_context,
|
||||||
|
get_user_is_authenticated,
|
||||||
is_internal_ip,
|
is_internal_ip,
|
||||||
)
|
)
|
||||||
|
|
||||||
DOMAIN_RE = re.compile(r'^\S+$')
|
DOMAIN_RE = re.compile(r'^\S+$')
|
||||||
TRACKING_CODE = """
|
TRACKING_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var woo_settings = %(settings)s;
|
var woo_settings = %(settings)s;
|
||||||
var woo_visitor = %(visitor)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");
|
!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");
|
||||||
|
|
@ -28,7 +29,7 @@ TRACKING_CODE = """
|
||||||
woopra.identify(woo_visitor);
|
woopra.identify(woo_visitor);
|
||||||
woopra.track();
|
woopra.track();
|
||||||
</script>
|
</script>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
@ -38,7 +39,7 @@ def woopra(parser, token):
|
||||||
"""
|
"""
|
||||||
Woopra tracking template tag.
|
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.
|
your Woopra domain in the ``WOOPRA_DOMAIN`` setting.
|
||||||
"""
|
"""
|
||||||
bits = token.split_contents()
|
bits = token.split_contents()
|
||||||
|
|
@ -50,8 +51,8 @@ def woopra(parser, token):
|
||||||
class WoopraNode(Node):
|
class WoopraNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.domain = get_required_setting(
|
self.domain = get_required_setting(
|
||||||
'WOOPRA_DOMAIN', DOMAIN_RE,
|
'WOOPRA_DOMAIN', DOMAIN_RE, 'must be a domain name'
|
||||||
"must be a domain name")
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
settings = self._get_settings(context)
|
settings = self._get_settings(context)
|
||||||
|
|
@ -67,10 +68,42 @@ class WoopraNode(Node):
|
||||||
|
|
||||||
def _get_settings(self, context):
|
def _get_settings(self, context):
|
||||||
variables = {'domain': self.domain}
|
variables = {'domain': self.domain}
|
||||||
try:
|
woopra_int_settings = {
|
||||||
variables['idle_timeout'] = str(settings.WOOPRA_IDLE_TIMEOUT)
|
'idle_timeout': 'WOOPRA_IDLE_TIMEOUT',
|
||||||
except AttributeError:
|
}
|
||||||
pass
|
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
|
return variables
|
||||||
|
|
||||||
def _get_visitor(self, context):
|
def _get_visitor(self, context):
|
||||||
|
|
@ -81,9 +114,8 @@ class WoopraNode(Node):
|
||||||
params[var[7:]] = val
|
params[var[7:]] = val
|
||||||
if 'name' not in params and 'email' not in params:
|
if 'name' not in params and 'email' not in params:
|
||||||
user = get_user_from_context(context)
|
user = get_user_from_context(context)
|
||||||
if user is not None and user.is_authenticated():
|
if user is not None and get_user_is_authenticated(user):
|
||||||
params['name'] = get_identity(
|
params['name'] = get_identity(context, 'woopra', self._identify, user)
|
||||||
context, 'woopra', self._identify, user)
|
|
||||||
if user.email:
|
if user.email:
|
||||||
params['email'] = user.email
|
params['email'] = user.email
|
||||||
return params
|
return params
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,17 @@
|
||||||
Yandex.Metrica template tags and filters.
|
Yandex.Metrica template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.utils import is_internal_ip, disable_html, \
|
from analytical.utils import disable_html, get_required_setting, is_internal_ip
|
||||||
get_required_setting
|
|
||||||
|
|
||||||
|
|
||||||
COUNTER_ID_RE = re.compile(r'^\d{8}$')
|
COUNTER_ID_RE = re.compile(r'^\d{8}$')
|
||||||
COUNTER_CODE = """
|
COUNTER_CODE = """
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
(function (d, w, c) {
|
(function (d, w, c) {
|
||||||
(w[c] = w[c] || []).push(function() {
|
(w[c] = w[c] || []).push(function() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -37,7 +33,7 @@ COUNTER_CODE = """
|
||||||
})(document, window, "yandex_metrika_callbacks");
|
})(document, window, "yandex_metrika_callbacks");
|
||||||
</script>
|
</script>
|
||||||
<noscript><div><img src="https://mc.yandex.ru/watch/%(counter_id)s" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
<noscript><div><img src="https://mc.yandex.ru/watch/%(counter_id)s" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||||
"""
|
""" # noqa
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
@ -48,7 +44,7 @@ def yandex_metrica(parser, token):
|
||||||
"""
|
"""
|
||||||
Yandex.Metrica counter template tag.
|
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
|
your website counter ID (as a string) in the
|
||||||
``YANDEX_METRICA_COUNTER_ID`` setting.
|
``YANDEX_METRICA_COUNTER_ID`` setting.
|
||||||
"""
|
"""
|
||||||
|
|
@ -61,15 +57,17 @@ def yandex_metrica(parser, token):
|
||||||
class YandexMetricaNode(Node):
|
class YandexMetricaNode(Node):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.counter_id = get_required_setting(
|
self.counter_id = get_required_setting(
|
||||||
'YANDEX_METRICA_COUNTER_ID', COUNTER_ID_RE,
|
'YANDEX_METRICA_COUNTER_ID',
|
||||||
"must be (a string containing) a number'")
|
COUNTER_ID_RE,
|
||||||
|
"must be (a string containing) a number'",
|
||||||
|
)
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
options = {
|
options = {
|
||||||
'id': int(self.counter_id),
|
'id': int(self.counter_id),
|
||||||
'clickmap': True,
|
'clickmap': True,
|
||||||
'trackLinks': True,
|
'trackLinks': True,
|
||||||
'accurateTrackBounce': True
|
'accurateTrackBounce': True,
|
||||||
}
|
}
|
||||||
if getattr(settings, 'YANDEX_METRICA_WEBVISOR', False):
|
if getattr(settings, 'YANDEX_METRICA_WEBVISOR', False):
|
||||||
options['webvisor'] = True
|
options['webvisor'] = True
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
"""
|
|
||||||
Tests for the intercom template tags and filters.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User, AnonymousUser
|
|
||||||
from django.template import Context
|
|
||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
from analytical.templatetags.intercom import IntercomNode
|
|
||||||
from analytical.tests.utils import TagTestCase
|
|
||||||
from analytical.utils import AnalyticalException
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(INTERCOM_APP_ID='1234567890abcdef0123456789')
|
|
||||||
class IntercomTagTestCase(TagTestCase):
|
|
||||||
"""
|
|
||||||
Tests for the ``intercom`` template tag.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_tag(self):
|
|
||||||
rendered_tag = self.render_tag('intercom', 'intercom')
|
|
||||||
self.assertTrue(rendered_tag.startswith('<!-- Intercom disabled on internal IP address'))
|
|
||||||
|
|
||||||
def test_node(self):
|
|
||||||
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
|
|
||||||
rendered_tag = IntercomNode().render(Context({
|
|
||||||
'user': User(
|
|
||||||
username='test',
|
|
||||||
first_name='Firstname',
|
|
||||||
last_name='Lastname',
|
|
||||||
email="test@example.com",
|
|
||||||
date_joined=now)
|
|
||||||
}))
|
|
||||||
# Because the json isn't predictably ordered, we can't just test the whole thing verbatim.
|
|
||||||
self.assertEqual("""
|
|
||||||
<script id="IntercomSettingsScriptTag">
|
|
||||||
window.intercomSettings = {"app_id": "1234567890abcdef0123456789", "created_at": 1397074500, "email": "test@example.com", "name": "Firstname Lastname"};
|
|
||||||
</script>
|
|
||||||
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://static.intercomcdn.com/intercom.v1.js';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()</script>
|
|
||||||
""", rendered_tag)
|
|
||||||
|
|
||||||
@override_settings(INTERCOM_APP_ID=None)
|
|
||||||
def test_no_account_number(self):
|
|
||||||
self.assertRaises(AnalyticalException, IntercomNode)
|
|
||||||
|
|
||||||
@override_settings(INTERCOM_APP_ID='123abQ')
|
|
||||||
def test_wrong_account_number(self):
|
|
||||||
self.assertRaises(AnalyticalException, IntercomNode)
|
|
||||||
|
|
||||||
def test_identify_name_email_and_created_at(self):
|
|
||||||
now = datetime.datetime(2014, 4, 9, 15, 15, 0)
|
|
||||||
r = IntercomNode().render(Context({'user': User(username='test',
|
|
||||||
first_name='Firstname', last_name='Lastname',
|
|
||||||
email="test@example.com", date_joined=now)}))
|
|
||||||
self.assertTrue(
|
|
||||||
"""window.intercomSettings = {"app_id": "1234567890abcdef0123456789", "created_at": 1397074500, "email": "test@example.com", "name": "Firstname Lastname"};"""\
|
|
||||||
in r
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_custom(self):
|
|
||||||
r = IntercomNode().render(Context({
|
|
||||||
'intercom_var1': 'val1',
|
|
||||||
'intercom_var2': 'val2'
|
|
||||||
}))
|
|
||||||
self.assertTrue('var1": "val1", "var2": "val2"' in r)
|
|
||||||
|
|
||||||
def test_identify_name_and_email(self):
|
|
||||||
r = IntercomNode().render(Context({
|
|
||||||
'user': User(username='test',
|
|
||||||
first_name='Firstname',
|
|
||||||
last_name='Lastname',
|
|
||||||
email="test@example.com")
|
|
||||||
}))
|
|
||||||
self.assertTrue('"email": "test@example.com", "name": "Firstname Lastname"' in r)
|
|
||||||
|
|
||||||
def test_identify_username_no_email(self):
|
|
||||||
r = IntercomNode().render(Context({'user': User(username='test')}))
|
|
||||||
self.assertTrue('"name": "test"' in r, r)
|
|
||||||
|
|
||||||
def test_no_identify_when_explicit_name(self):
|
|
||||||
r = IntercomNode().render(Context({'intercom_name': 'explicit',
|
|
||||||
'user': User(username='implicit')}))
|
|
||||||
self.assertTrue('"name": "explicit"' in r, r)
|
|
||||||
|
|
||||||
def test_no_identify_when_explicit_email(self):
|
|
||||||
r = IntercomNode().render(Context({'intercom_email': 'explicit',
|
|
||||||
'user': User(username='implicit')}))
|
|
||||||
self.assertTrue('"email": "explicit"' in r, r)
|
|
||||||
|
|
||||||
def test_disable_for_anonymous_users(self):
|
|
||||||
r = IntercomNode().render(Context({'user': AnonymousUser()}))
|
|
||||||
self.assertTrue(r.startswith('<!-- Intercom disabled on internal IP address'), r)
|
|
||||||
|
|
@ -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='0123456789abcdef0123456789abcdef'
|
|
||||||
'01234567')
|
|
||||||
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/0123456789abcdef012345"
|
|
||||||
"6789abcdef01234567.1.js" in r, r)
|
|
||||||
|
|
||||||
def test_node(self):
|
|
||||||
r = KissMetricsNode().render(Context())
|
|
||||||
self.assertTrue("//doug1izaerwt3.cloudfront.net/0123456789abcdef012345"
|
|
||||||
"6789abcdef01234567.1.js" in r, r)
|
|
||||||
|
|
||||||
@override_settings(KISS_METRICS_API_KEY=None)
|
|
||||||
def test_no_api_key(self):
|
|
||||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
|
||||||
|
|
||||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef'
|
|
||||||
'0123456')
|
|
||||||
def test_api_key_too_short(self):
|
|
||||||
self.assertRaises(AnalyticalException, KissMetricsNode)
|
|
||||||
|
|
||||||
@override_settings(KISS_METRICS_API_KEY='0123456789abcdef0123456789abcdef'
|
|
||||||
'012345678')
|
|
||||||
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,92 +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,120 +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(' ? "https" : "http") + "://example.com/";' in r, r)
|
|
||||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
|
||||||
self.assertTrue('img src="http://example.com/piwik.php?idsite=345"'
|
|
||||||
in r, r)
|
|
||||||
|
|
||||||
def test_node(self):
|
|
||||||
r = PiwikNode().render(Context({}))
|
|
||||||
self.assertTrue(' ? "https" : "http") + "://example.com/";' in r, r)
|
|
||||||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r)
|
|
||||||
self.assertTrue('img src="http://example.com/piwik.php?idsite=345"'
|
|
||||||
in r, r)
|
|
||||||
|
|
||||||
@override_settings(PIWIK_DOMAIN_PATH='example.com/piwik',
|
|
||||||
PIWIK_SITE_ID='345')
|
|
||||||
def test_domain_path_valid(self):
|
|
||||||
r = self.render_tag('piwik', 'piwik')
|
|
||||||
self.assertTrue(' ? "https" : "http") + "://example.com/piwik/";' in r,
|
|
||||||
r)
|
|
||||||
|
|
||||||
@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(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))
|
|
||||||
|
|
@ -1,256 +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,87 +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.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
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):
|
def get_required_setting(setting, value_re, invalid_msg):
|
||||||
|
|
@ -20,13 +18,14 @@ def get_required_setting(setting, value_re, invalid_msg):
|
||||||
try:
|
try:
|
||||||
value = getattr(settings, setting)
|
value = getattr(settings, setting)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise AnalyticalException("%s setting: not found" % setting)
|
raise AnalyticalException('%s setting: not found' % setting)
|
||||||
if value is None:
|
if not value:
|
||||||
raise AnalyticalException("%s setting is set to None" % setting)
|
raise AnalyticalException('%s setting is not set' % setting)
|
||||||
value = str(value)
|
value = str(value)
|
||||||
if not value_re.search(value):
|
if not value_re.search(value):
|
||||||
raise AnalyticalException("%s setting: %s: '%s'"
|
raise AnalyticalException(
|
||||||
% (setting, invalid_msg, value))
|
"%s setting: %s: '%s'" % (setting, invalid_msg, value)
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -49,6 +48,19 @@ def get_user_from_context(context):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_is_authenticated(user):
|
||||||
|
"""Check if the user is authenticated.
|
||||||
|
|
||||||
|
This is a compatibility function needed to support both Django 1.x and 2.x;
|
||||||
|
Django 2.x turns the function into a proper boolean so function calls will
|
||||||
|
fail.
|
||||||
|
"""
|
||||||
|
if callable(user.is_authenticated):
|
||||||
|
return user.is_authenticated()
|
||||||
|
else:
|
||||||
|
return user.is_authenticated
|
||||||
|
|
||||||
|
|
||||||
def get_identity(context, prefix=None, identity_func=None, user=None):
|
def get_identity(context, prefix=None, identity_func=None, user=None):
|
||||||
"""
|
"""
|
||||||
Get the identity of a logged in user from a template context.
|
Get the identity of a logged in user from a template context.
|
||||||
|
|
@ -71,7 +83,7 @@ def get_identity(context, prefix=None, identity_func=None, user=None):
|
||||||
try:
|
try:
|
||||||
if user is None:
|
if user is None:
|
||||||
user = get_user_from_context(context)
|
user = get_user_from_context(context)
|
||||||
if user.is_authenticated():
|
if get_user_is_authenticated(user):
|
||||||
if identity_func is not None:
|
if identity_func is not None:
|
||||||
return identity_func(user)
|
return identity_func(user)
|
||||||
else:
|
else:
|
||||||
|
|
@ -100,6 +112,7 @@ def get_domain(context, prefix):
|
||||||
if domain is None:
|
if domain is None:
|
||||||
if 'django.contrib.sites' in settings.INSTALLED_APPS:
|
if 'django.contrib.sites' in settings.INSTALLED_APPS:
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
try:
|
try:
|
||||||
domain = Site.objects.get_current().domain
|
domain = Site.objects.get_current().domain
|
||||||
except (ImproperlyConfigured, Site.DoesNotExist):
|
except (ImproperlyConfigured, Site.DoesNotExist):
|
||||||
|
|
@ -150,4 +163,5 @@ class AnalyticalException(Exception):
|
||||||
Raised when an exception occurs in any django-analytical code that should
|
Raised when an exception occurs in any django-analytical code that should
|
||||||
be silenced in templates.
|
be silenced in templates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
silent_variable_failure = True
|
silent_variable_failure = True
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,21 @@
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.add_crossref_type(
|
app.add_crossref_type(
|
||||||
directivename="setting",
|
directivename='setting',
|
||||||
rolename="setting",
|
rolename='setting',
|
||||||
indextemplate="pair: %s; setting",
|
indextemplate='pair: %s; setting',
|
||||||
)
|
)
|
||||||
app.add_crossref_type(
|
app.add_crossref_type(
|
||||||
directivename="templatetag",
|
directivename='templatetag',
|
||||||
rolename="ttag",
|
rolename='ttag',
|
||||||
indextemplate="pair: %s; template tag"
|
indextemplate='pair: %s; template tag',
|
||||||
)
|
)
|
||||||
app.add_crossref_type(
|
app.add_crossref_type(
|
||||||
directivename="templatefilter",
|
directivename='templatefilter',
|
||||||
rolename="tfilter",
|
rolename='tfilter',
|
||||||
indextemplate="pair: %s; template filter"
|
indextemplate='pair: %s; template filter',
|
||||||
)
|
)
|
||||||
app.add_crossref_type(
|
app.add_crossref_type(
|
||||||
directivename="fieldlookup",
|
directivename='fieldlookup',
|
||||||
rolename="lookup",
|
rolename='lookup',
|
||||||
indextemplate="pair: %s; field lookup type",
|
indextemplate='pair: %s; field lookup type',
|
||||||
)
|
|
||||||
app.add_description_unit(
|
|
||||||
directivename="decorator",
|
|
||||||
rolename="dec",
|
|
||||||
indextemplate="pair: %s; function decorator",
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
24
docs/conf.py
24
docs/conf.py
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
# This file is execfile()d with the current directory set to its containing
|
# This file is execfile()d with the current directory set to its containing
|
||||||
# directory.
|
# directory.
|
||||||
|
|
@ -9,13 +8,12 @@ import sys
|
||||||
sys.path.append(os.path.join(os.path.abspath('.'), '_ext'))
|
sys.path.append(os.path.join(os.path.abspath('.'), '_ext'))
|
||||||
sys.path.append(os.path.dirname(os.path.abspath('.')))
|
sys.path.append(os.path.dirname(os.path.abspath('.')))
|
||||||
|
|
||||||
import analytical
|
import analytical # noqa
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration --------------------------------------------------
|
# -- General configuration --------------------------------------------------
|
||||||
|
|
||||||
project = u'django-analytical'
|
project = 'django-analytical'
|
||||||
copyright = u'2011-2016, Joost Cassee <joost@cassee.net>'
|
copyright = '2011, Joost Cassee <joost@cassee.net>'
|
||||||
|
|
||||||
release = analytical.__version__
|
release = analytical.__version__
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
|
|
@ -23,16 +21,15 @@ version = release.rsplit('.', 1)[0]
|
||||||
|
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'local']
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'local']
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
source_suffix = '.rst'
|
source_suffix = {'.rst': 'restructuredtext'}
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
add_function_parentheses = True
|
add_function_parentheses = True
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'http://docs.python.org/2.7': None,
|
'python': ('https://docs.python.org/3.13', None),
|
||||||
'http://docs.djangoproject.com/en/1.9':
|
'django': ('https://docs.djangoproject.com/en/stable', None),
|
||||||
'http://docs.djangoproject.com/en/1.9/_objects/',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -45,6 +42,11 @@ htmlhelp_basename = 'analyticaldoc'
|
||||||
# -- Options for LaTeX output -----------------------------------------------
|
# -- Options for LaTeX output -----------------------------------------------
|
||||||
|
|
||||||
latex_documents = [
|
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',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ initialization code if the client IP address is detected as one from the
|
||||||
:data:`ANALYTICAL_INTERNAL_IPS` setting. The default value for this
|
:data:`ANALYTICAL_INTERNAL_IPS` setting. The default value for this
|
||||||
setting is :data:`INTERNAL_IPS`.
|
setting is :data:`INTERNAL_IPS`.
|
||||||
|
|
||||||
Example::
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
ANALYTICAL_INTERNAL_IPS = ['192.168.1.45', '192.168.1.57']
|
ANALYTICAL_INTERNAL_IPS = ['192.168.1.45', '192.168.1.57']
|
||||||
|
|
||||||
|
|
@ -45,7 +47,9 @@ logged in through the standard Django authentication system and the
|
||||||
current user is accessible in the template context, the username can be
|
current user is accessible in the template context, the username can be
|
||||||
passed to the analytics services that support identifying users. This
|
passed to the analytics services that support identifying users. This
|
||||||
feature is configured by the :data:`ANALYTICAL_AUTO_IDENTIFY` setting
|
feature is configured by the :data:`ANALYTICAL_AUTO_IDENTIFY` setting
|
||||||
and is enabled by default. To disable::
|
and is enabled by default. To disable:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
ANALYTICAL_AUTO_IDENTIFY = False
|
ANALYTICAL_AUTO_IDENTIFY = False
|
||||||
|
|
||||||
|
|
@ -64,3 +68,52 @@ and is enabled by default. To disable::
|
||||||
Alternatively, add one of the variables to the context yourself
|
Alternatively, add one of the variables to the context yourself
|
||||||
when you render the template.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ version numbers. Patch-level increments indicate bug fixes, minor
|
||||||
version increments indicate new functionality and major version
|
version increments indicate new functionality and major version
|
||||||
increments indicate backwards incompatible changes.
|
increments indicate backwards incompatible changes.
|
||||||
|
|
||||||
Version 1.0.0 is the last to support Django < 1.7. Users of older django
|
Version 1.0.0 is the last to support Django < 1.7. Users of older Django
|
||||||
versions should stick to 1.0.0, and are encouraged to upgrade their setups.
|
versions should stick to 1.0.0, and are encouraged to upgrade their setups.
|
||||||
Starting with 2.0.0, dropping support for obsolete Django versions is not
|
Starting with 2.0.0, dropping support for obsolete Django versions is not
|
||||||
considered to be a backward-incompatible change.
|
considered to be a backward-incompatible change.
|
||||||
|
|
@ -23,8 +23,21 @@ considered to be a backward-incompatible change.
|
||||||
Credits
|
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:
|
.. _helping-out:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ django-analytical
|
||||||
The django-analytical application integrates analytics services into a
|
The django-analytical application integrates analytics services into a
|
||||||
Django_ project.
|
Django_ project.
|
||||||
|
|
||||||
.. _Django: http://www.djangoproject.com/
|
.. _Django: https://www.djangoproject.com/
|
||||||
|
|
||||||
:Package: http://pypi.python.org/pypi/django-analytical/
|
:Package: https://pypi.org/project/django-analytical/
|
||||||
:Source: http://github.com/jcassee/django-analytical
|
:Source: https://github.com/jazzband/django-analytical
|
||||||
|
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ get the development code:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ git clone https://github.com/jcassee/django-analytical.git
|
$ git clone https://github.com/jazzband/django-analytical.git
|
||||||
|
|
||||||
.. _PyPI: http://pypi.python.org/pypi/django-analytical/
|
.. _PyPI: https://pypi.org/project/django-analytical/
|
||||||
.. _GitHub: http://github.com/jcassee/django-analytical
|
.. _GitHub: http://github.com/jazzband/django-analytical
|
||||||
|
|
||||||
Then install the package by running the setup script:
|
Then install the package by running the setup script:
|
||||||
|
|
||||||
|
|
@ -68,32 +68,32 @@ file of your project:
|
||||||
Adding the template tags to the base template
|
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
|
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
|
HTML page, django-analytical provides four general-purpose template tags
|
||||||
that will render the code needed for the services you are using. Your
|
that will render the code needed for the services you are using. Your
|
||||||
base template should look like this:
|
base template should look like this:
|
||||||
|
|
||||||
.. code-block:: html
|
.. code-block:: django
|
||||||
|
|
||||||
{% load analytical %}
|
{% load analytical %}
|
||||||
<!DOCTYPE ... >
|
<!DOCTYPE ... >
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
{% analytical_head_top %}
|
{% analytical_head_top %}
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
{% analytical_head_bottom %}
|
{% analytical_head_bottom %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% analytical_body_top %}
|
{% analytical_body_top %}
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
{% analytical_body_bottom %}
|
{% analytical_body_bottom %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
Instead of using the generic tags, you can also just use tags specific
|
Instead of using the generic tags, you can also just use tags specific
|
||||||
for the analytics service(s) you are using. See :ref:`services` for
|
for the analytics service(s) you are using. See :ref:`services` for
|
||||||
|
|
@ -125,14 +125,26 @@ settings required to enable each service are listed here:
|
||||||
|
|
||||||
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
|
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
|
||||||
|
|
||||||
|
* :doc:`Facebook Pixel <services/facebook_pixel>`::
|
||||||
|
|
||||||
|
FACEBOOK_PIXEL_ID = '1234567890'
|
||||||
|
|
||||||
* :doc:`Gaug.es <services/gauges>`::
|
* :doc:`Gaug.es <services/gauges>`::
|
||||||
|
|
||||||
GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef'
|
GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef'
|
||||||
|
|
||||||
* :doc:`Google Analytics <services/google_analytics>`::
|
* :doc:`Google Analytics (legacy) <services/google_analytics>`::
|
||||||
|
|
||||||
GOOGLE_ANALYTICS_PROPERTY_ID = 'UA-1234567-8'
|
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>`::
|
* :doc:`HubSpot <services/hubspot>`::
|
||||||
|
|
||||||
HUBSPOT_PORTAL_ID = '1234'
|
HUBSPOT_PORTAL_ID = '1234'
|
||||||
|
|
@ -151,6 +163,15 @@ settings required to enable each service are listed here:
|
||||||
|
|
||||||
KISS_METRICS_API_KEY = '0123456789abcdef0123456789abcdef01234567'
|
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'
|
||||||
|
MATOMO_SITE_ID = '123'
|
||||||
|
|
||||||
* :doc:`Mixpanel <services/mixpanel>`::
|
* :doc:`Mixpanel <services/mixpanel>`::
|
||||||
|
|
||||||
MIXPANEL_API_TOKEN = '0123456789abcdef0123456789abcdef'
|
MIXPANEL_API_TOKEN = '0123456789abcdef0123456789abcdef'
|
||||||
|
|
@ -167,15 +188,14 @@ settings required to enable each service are listed here:
|
||||||
|
|
||||||
PERFORMABLE_API_KEY = '123abc'
|
PERFORMABLE_API_KEY = '123abc'
|
||||||
|
|
||||||
* :doc:`Piwik <services/piwik>`::
|
* :doc:`Rating\@Mail.ru <services/rating_mailru>`::
|
||||||
|
|
||||||
PIWIK_DOMAIN_PATH = 'your.piwik.server/optional/path'
|
|
||||||
PIWIK_SITE_ID = '123'
|
|
||||||
|
|
||||||
* :doc:`Rating@Mail.ru <services/rating_mailru>`::
|
|
||||||
|
|
||||||
RATING_MAILRU_COUNTER_ID = '1234567'
|
RATING_MAILRU_COUNTER_ID = '1234567'
|
||||||
|
|
||||||
|
* :doc:`SnapEngage <services/snapengage>`::
|
||||||
|
|
||||||
|
SNAPENGAGE_WIDGET_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
|
||||||
|
|
||||||
* :doc:`Woopra <services/woopra>`::
|
* :doc:`Woopra <services/woopra>`::
|
||||||
|
|
||||||
WOOPRA_DOMAIN = 'abcde.com'
|
WOOPRA_DOMAIN = 'abcde.com'
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ If you would like to have another analytics service supported by
|
||||||
django-analytical, please create an issue on the project
|
django-analytical, please create an issue on the project
|
||||||
`issue tracker`_. See also :ref:`helping-out`.
|
`issue tracker`_. See also :ref:`helping-out`.
|
||||||
|
|
||||||
.. _`issue tracker`: http://github.com/jcassee/django-analytical/issues
|
.. _`issue tracker`: http://github.com/jazzband/django-analytical/issues
|
||||||
|
|
||||||
|
|
||||||
Currently supported services:
|
Currently supported services:
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
:glob:
|
:glob:
|
||||||
|
|
||||||
services/*
|
services/*
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ contains a line that looks like this::
|
||||||
Here, ``XXXXX`` is your User ID. Set :const:`CHARTBEAT_USER_ID` in the
|
Here, ``XXXXX`` is your User ID. Set :const:`CHARTBEAT_USER_ID` in the
|
||||||
project :file:`settings.py` file::
|
project :file:`settings.py` file::
|
||||||
|
|
||||||
CHARTBEAT_SITE_ID = 'XXXXX'
|
CHARTBEAT_USER_ID = 'XXXXX'
|
||||||
|
|
||||||
If you do not set a User ID, the tracking code will not be rendered.
|
If you do not set a User ID, the tracking code will not be rendered.
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ important information about detecting the visitor IP address.
|
||||||
Setting the domain
|
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
|
If you use multiple subdomains this enables you to treat them as one
|
||||||
website in Chartbeat. If your project uses the sites framework, the
|
website in Chartbeat. If your project uses the sites framework, the
|
||||||
domain name of the current :class:`~django.contrib.sites.models.Site`
|
domain name of the current :class:`~django.contrib.sites.models.Site`
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
Clickmap -- visual click tracking
|
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:
|
.. 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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`clickmap-configuration`.
|
: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
|
tag. Load the :mod:`clickmap` template tag library and insert the
|
||||||
:ttag:`clickmap` tag. Because every page that you want to track must
|
: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
|
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,
|
Clickmap Tracker ID. If you don't have a Clickmap account yet,
|
||||||
`sign up`_ to get your Tracker ID.
|
`sign up`_ to get your Tracker ID.
|
||||||
|
|
||||||
.. _`sign up`: http://www.getclickmap.com/
|
.. _`sign up`: http://www.clickmap.ch/
|
||||||
|
|
||||||
|
|
||||||
.. _clickmap-tracker-id:
|
.. _clickmap-tracker-id:
|
||||||
|
|
@ -53,7 +54,7 @@ Setting the Tracker ID
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Clickmap gives you a unique Tracker ID, and the :ttag:`clickmap`
|
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
|
Tracker ID clicking the link named "Tracker" in the dashboard
|
||||||
of your Clickmap account. Set :const:`CLICKMAP_TRACKER_ID` in the project
|
of your Clickmap account. Set :const:`CLICKMAP_TRACKER_ID` in the project
|
||||||
:file:`settings.py` file::
|
:file:`settings.py` file::
|
||||||
|
|
@ -72,6 +73,6 @@ Internal IP addresses
|
||||||
Usually you do not want to track clicks from your development or
|
Usually you do not want to track clicks from your development or
|
||||||
internal IP addresses. By default, if the tags detect that the client
|
internal IP addresses. By default, if the tags detect that the client
|
||||||
comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` setting
|
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
|
commented out. See :ref:`identifying-visitors` for important information
|
||||||
about detecting the visitor IP address.
|
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
|
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*
|
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
|
page, in your Clicky account. Set :const:`CLICKY_SITE_ID` in the
|
||||||
project :file:`settings.py` file::
|
project :file:`settings.py` file::
|
||||||
|
|
@ -84,7 +84,7 @@ Custom data
|
||||||
|
|
||||||
As described in the Clicky `customized tracking`_ documentation page,
|
As described in the Clicky `customized tracking`_ documentation page,
|
||||||
the data that is tracked by Clicky can be customized by setting the
|
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`
|
code. Using template context variables, you can let the :ttag:`clicky`
|
||||||
tag pass custom data to Clicky automatically. You can set the context
|
tag pass custom data to Clicky automatically. You can set the context
|
||||||
variables in your view when you render a template containing the
|
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`
|
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
|
account number by clicking the link named "What's my code?" in the
|
||||||
dashboard of your Crazy Egg account. Set
|
dashboard of your Crazy Egg account. Set
|
||||||
:const:`CRAZY_EGG_ACCOUNT_NUMBER` in the project :file:`settings.py`
|
:const:`CRAZY_EGG_ACCOUNT_NUMBER` in the project :file:`settings.py`
|
||||||
|
|
|
||||||
84
docs/services/facebook_pixel.rst
Normal file
84
docs/services/facebook_pixel.rst
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
=======================================
|
||||||
|
Facebook Pixel -- advertising analytics
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
`Facebook Pixel`_ is Facebook's tool for conversion tracking, optimisation and remarketing.
|
||||||
|
|
||||||
|
.. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/
|
||||||
|
|
||||||
|
|
||||||
|
.. facebook-pixel-installation:
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
To start using the Facebook Pixel integration, you must have installed the
|
||||||
|
django-analytical package and have added the ``analytical`` application
|
||||||
|
to :const:`INSTALLED_APPS` in your project :file:`settings.py` file.
|
||||||
|
See :doc:`../install` for details.
|
||||||
|
|
||||||
|
Next you need to add the Facebook Pixel template tag to your templates.
|
||||||
|
This step is only needed if you are not using the generic
|
||||||
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
|
:ref:`facebook-pixel-configuration`.
|
||||||
|
|
||||||
|
The Facebook Pixel code is inserted into templates using template tags.
|
||||||
|
Because every page that you want to track must have the tag,
|
||||||
|
it is useful to add it to your base template.
|
||||||
|
At the top of the template, load the :mod:`facebook_pixel` template tag library.
|
||||||
|
Then insert the :ttag:`facebook_pixel_head` tag at the bottom of the head section,
|
||||||
|
and optionally insert the :ttag:`facebook_pixel_body` tag at the bottom of the body section::
|
||||||
|
|
||||||
|
{% load facebook_pixel %}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
...
|
||||||
|
{% facebook_pixel_head %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
...
|
||||||
|
{% facebook_pixel_body %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The :ttag:`facebook_pixel_body` tag code will only be used for browsers with JavaScript disabled.
|
||||||
|
It can be omitted if you don't need to support them.
|
||||||
|
|
||||||
|
|
||||||
|
.. _facebook-pixel-configuration:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
Before you can use the Facebook Pixel integration,
|
||||||
|
you must first set your Pixel ID.
|
||||||
|
|
||||||
|
|
||||||
|
.. _facebook-pixel-id:
|
||||||
|
|
||||||
|
Setting the Pixel ID
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Each Facebook Adverts account you have can have a Pixel ID,
|
||||||
|
and the :mod:`facebook_pixel` tags will include it in the rendered page.
|
||||||
|
You can find the Pixel ID on the "Pixels" section of your Facebook Adverts account.
|
||||||
|
Set :const:`FACEBOOK_PIXEL_ID` in the project :file:`settings.py` file::
|
||||||
|
|
||||||
|
FACEBOOK_PIXEL_ID = 'XXXXXXXXXX'
|
||||||
|
|
||||||
|
If you do not set a Pixel ID, the code will not be rendered.
|
||||||
|
|
||||||
|
|
||||||
|
.. _facebook-pixel-internal-ips:
|
||||||
|
|
||||||
|
Internal IP addresses
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Usually you do not want to track clicks from your development or
|
||||||
|
internal IP addresses. By default, if the tags detect that the client
|
||||||
|
comes from any address in the :const:`FACEBOOK_PIXEL_INTERNAL_IPS`
|
||||||
|
setting, the tracking code is commented out. It takes the value of
|
||||||
|
:const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is
|
||||||
|
:const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for
|
||||||
|
important information about detecting the visitor IP address.
|
||||||
|
|
@ -23,7 +23,7 @@ This step is only needed if you are not using the generic
|
||||||
:ttag:`analytical.*` tags. If you are, skip to
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`gauges-configuration`.
|
: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
|
template tag. Load the :mod:`gauges` template tag library and
|
||||||
insert the :ttag:`gauges` tag. Because every page that you want to
|
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.
|
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`
|
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
|
site id by clicking the *Tracking Code* link when logged into
|
||||||
the on the gaug.es website. A page will display containing
|
the on the gaug.es website. A page will display containing
|
||||||
HTML code looking like this::
|
HTML code looking like this::
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
var _gauges = _gauges || [];
|
var _gauges = _gauges || [];
|
||||||
(function() {
|
(function() {
|
||||||
var t = document.createElement('script');
|
var t = document.createElement('script');
|
||||||
|
|
@ -76,7 +76,7 @@ file::
|
||||||
|
|
||||||
GAUGES_SITE_ID = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
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.
|
rendered.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -88,6 +88,6 @@ Internal IP addresses
|
||||||
Usually you do not want to track clicks from your development or
|
Usually you do not want to track clicks from your development or
|
||||||
internal IP addresses. By default, if the tags detect that the client
|
internal IP addresses. By default, if the tags detect that the client
|
||||||
comes from any address in the :const:`ANALYTICAL_INTERNAL_IPS` setting
|
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
|
commented out. See :ref:`identifying-visitors` for important information
|
||||||
about detecting the visitor IP address.
|
about detecting the visitor IP address.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
======================================
|
==============================================
|
||||||
Google Analytics -- traffic analysis
|
Google Analytics (legacy) -- traffic analysis
|
||||||
======================================
|
==============================================
|
||||||
|
|
||||||
`Google Analytics`_ is the well-known web analytics service from
|
`Google Analytics`_ is the well-known web analytics service from
|
||||||
Google. The product is aimed more at marketers than webmasters or
|
Google. The product is aimed more at marketers than webmasters or
|
||||||
|
|
@ -15,7 +15,7 @@ features.
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
To start using the Google Analytics integration, you must have installed
|
To start using the Google Analytics (legacy) integration, you must have installed
|
||||||
the django-analytical package and have added the ``analytical``
|
the django-analytical package and have added the ``analytical``
|
||||||
application to :const:`INSTALLED_APPS` in your project
|
application to :const:`INSTALLED_APPS` in your project
|
||||||
:file:`settings.py` file. See :doc:`../install` for details.
|
:file:`settings.py` file. See :doc:`../install` for details.
|
||||||
|
|
@ -58,7 +58,7 @@ Setting the property ID
|
||||||
|
|
||||||
Every website you track with Google Analytics gets its own 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
|
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
|
of your account. Set :const:`GOOGLE_ANALYTICS_PROPERTY_ID` in the
|
||||||
project :file:`settings.py` file::
|
project :file:`settings.py` file::
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ Tracking multiple domains
|
||||||
|
|
||||||
The default code is suitable for tracking a single domain. If you track
|
The default code is suitable for tracking a single domain. If you track
|
||||||
multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE`
|
multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE`
|
||||||
setting to one of the :const:`analytical.templatetags.google_analytics.SCOPE_*`
|
setting to one of the :const:`analytical.templatetags.google_analytics.TRACK_*`
|
||||||
constants:
|
constants:
|
||||||
|
|
||||||
============================= ===== =============================================
|
============================= ===== =============================================
|
||||||
|
|
@ -220,7 +220,7 @@ You can configure the `Sample Rate`_ feature by setting the
|
||||||
The value is a percentage and can be between 0 and 100 and can be a string or
|
The value is a percentage and can be between 0 and 100 and can be a string or
|
||||||
decimal value of with up to two decimal places.
|
decimal value of with up to two decimal places.
|
||||||
|
|
||||||
.. _`Sample Rate`_: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsamplerate
|
.. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsamplerate
|
||||||
|
|
||||||
|
|
||||||
.. _google-analytics-site-speed-sample-rate:
|
.. _google-analytics-site-speed-sample-rate:
|
||||||
|
|
@ -236,7 +236,7 @@ You can configure the `Site Speed Sample Rate`_ feature by setting the
|
||||||
The value is a percentage and can be between 0 and 100 and can be a string or
|
The value is a percentage and can be between 0 and 100 and can be a string or
|
||||||
decimal value of with up to two decimal places.
|
decimal value of with up to two decimal places.
|
||||||
|
|
||||||
.. _`Site Speed Sample Rate`_: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsitespeedsamplerate
|
.. _`Site Speed Sample Rate`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsitespeedsamplerate
|
||||||
|
|
||||||
|
|
||||||
.. _google-analytics-session-cookie-timeout:
|
.. _google-analytics-session-cookie-timeout:
|
||||||
|
|
@ -251,7 +251,7 @@ You can configure the `Session Cookie Timeout`_ feature by setting the
|
||||||
|
|
||||||
The value is the session cookie timeout in milliseconds or 0 to delete the cookie when the browser is closed.
|
The value is the session cookie timeout in milliseconds or 0 to delete the cookie when the browser is closed.
|
||||||
|
|
||||||
.. _`Session Cookie Timeout`_: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout
|
.. _`Session Cookie Timeout`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout
|
||||||
|
|
||||||
|
|
||||||
.. _google-analytics-visitor-cookie-timeout:
|
.. _google-analytics-visitor-cookie-timeout:
|
||||||
|
|
@ -266,4 +266,4 @@ You can configure the `Visitor Cookie Timeout`_ feature by setting the
|
||||||
|
|
||||||
The value is the visitor cookie timeout in milliseconds or 0 to delete the cookie when the browser is closed.
|
The value is the visitor cookie timeout in milliseconds or 0 to delete the cookie when the browser is closed.
|
||||||
|
|
||||||
.. _`Visitor Cookie Timeout`_: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setvisitorcookietimeout
|
.. _`Visitor Cookie Timeout`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setvisitorcookietimeout
|
||||||
|
|
|
||||||
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
|
||||||
238
docs/services/google_analytics_js.rst
Normal file
238
docs/services/google_analytics_js.rst
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
====================================================
|
||||||
|
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. 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:
|
||||||
|
|
||||||
|
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-js`.
|
||||||
|
|
||||||
|
The Google Analytics tracking code is inserted into templates using a
|
||||||
|
template tag. Load the :mod:`google_analytics_js` template tag library and
|
||||||
|
insert the :ttag:`google_analytics_js` tag. Because every page that you
|
||||||
|
want to track must have the tag, it is useful to add it to your base
|
||||||
|
template. Insert the tag at the bottom of the HTML head::
|
||||||
|
|
||||||
|
{% load google_analytics_js %}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
...
|
||||||
|
{% google_analytics_js %}
|
||||||
|
</head>
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-configuration-js:
|
||||||
|
|
||||||
|
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-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
|
||||||
|
of your account. Set :const:`GOOGLE_ANALYTICS_JS_PROPERTY_ID` in the
|
||||||
|
project :file:`settings.py` file::
|
||||||
|
|
||||||
|
GOOGLE_ANALYTICS_JS_PROPERTY_ID = 'UA-XXXXXXXX-X'
|
||||||
|
|
||||||
|
If you do not set a property ID, the tracking code will not be rendered.
|
||||||
|
|
||||||
|
|
||||||
|
Tracking multiple domains
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
The default code is suitable for tracking a single domain. If you track
|
||||||
|
multiple domains, set the :const:`GOOGLE_ANALYTICS_TRACKING_STYLE`
|
||||||
|
setting to one of the :const:`analytical.templatetags.google_analytics_js.TRACK_*`
|
||||||
|
constants:
|
||||||
|
|
||||||
|
============================= ===== =============================================
|
||||||
|
Constant Value Description
|
||||||
|
============================= ===== =============================================
|
||||||
|
``TRACK_SINGLE_DOMAIN`` 1 Track one domain.
|
||||||
|
``TRACK_MULTIPLE_SUBDOMAINS`` 2 Track multiple subdomains of the same top
|
||||||
|
domain (e.g. `fr.example.com` and
|
||||||
|
`nl.example.com`).
|
||||||
|
``TRACK_MULTIPLE_DOMAINS`` 3 Track multiple top domains (e.g. `example.fr`
|
||||||
|
and `example.nl`).
|
||||||
|
============================= ===== =============================================
|
||||||
|
|
||||||
|
As noted, the default tracking style is
|
||||||
|
:const:`~analytical.templatetags.google_analytics_js.TRACK_SINGLE_DOMAIN`.
|
||||||
|
|
||||||
|
When you track multiple (sub)domains, django-analytical needs to know
|
||||||
|
what domain name to pass to Google Analytics. If you use the contrib
|
||||||
|
sites app, the domain is automatically picked up from the current
|
||||||
|
:const:`~django.contrib.sites.models.Site` instance. Otherwise, you may
|
||||||
|
either pass the domain to the template tag through the context variable
|
||||||
|
:const:`google_analytics_domain` (fallback: :const:`analytical_domain`)
|
||||||
|
or set it in the project :file:`settings.py` file using
|
||||||
|
:const:`GOOGLE_ANALYTICS_DOMAIN` (fallback: :const:`ANALYTICAL_DOMAIN`).
|
||||||
|
|
||||||
|
Display Advertising
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Display Advertising allows you to view Demographics and Interests reports,
|
||||||
|
add Remarketing Lists and support DoubleClick Campain Manager integration.
|
||||||
|
|
||||||
|
You can enable `Display Advertising features`_ by setting the
|
||||||
|
:const:`GOOGLE_ANALYTICS_DISPLAY_ADVERTISING` configuration setting::
|
||||||
|
|
||||||
|
GOOGLE_ANALYTICS_DISPLAY_ADVERTISING = True
|
||||||
|
|
||||||
|
By default, display advertising features are disabled.
|
||||||
|
|
||||||
|
.. _`Display Advertising features`: https://support.google.com/analytics/answer/3450482
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-js-internal-ips:
|
||||||
|
|
||||||
|
Internal IP addresses
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Usually you do not want to track clicks from your development or
|
||||||
|
internal IP addresses. By default, if the tags detect that the client
|
||||||
|
comes from any address in the :const:`GOOGLE_ANALYTICS_INTERNAL_IPS`
|
||||||
|
setting, the tracking code is commented out. It takes the value of
|
||||||
|
:const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is
|
||||||
|
:const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for
|
||||||
|
important information about detecting the visitor IP address.
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-js-custom-variables:
|
||||||
|
|
||||||
|
Custom variables
|
||||||
|
----------------
|
||||||
|
|
||||||
|
As described in the Google Analytics `custom variables`_ documentation
|
||||||
|
page, you can define custom segments. Using template context variables
|
||||||
|
``google_analytics_var1`` through ``google_analytics_var5``, you can let
|
||||||
|
the :ttag:`google_analytics_js` tag pass custom variables to Google
|
||||||
|
Analytics automatically. You can set the context variables in your view
|
||||||
|
when your render a template containing the tracking code::
|
||||||
|
|
||||||
|
context = RequestContext({'google_analytics_var1': ('gender', 'female'),
|
||||||
|
'google_analytics_var2': ('visit', 1)})
|
||||||
|
return some_template.render(context)
|
||||||
|
|
||||||
|
The value of the context variable is a tuple *(name, value)*.
|
||||||
|
|
||||||
|
You may want to set custom variables in a context processor that you add
|
||||||
|
to the :data:`TEMPLATE_CONTEXT_PROCESSORS` list in :file:`settings.py`::
|
||||||
|
|
||||||
|
def google_analytics_segment_language(request):
|
||||||
|
try:
|
||||||
|
return {'google_analytics_var3': request.LANGUAGE_CODE}
|
||||||
|
except AttributeError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
Just remember that if you set the same context variable in the
|
||||||
|
:class:`~django.template.context.RequestContext` constructor and in a
|
||||||
|
context processor, the latter clobbers the former.
|
||||||
|
|
||||||
|
.. _`custom variables`: https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#custom-vars
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-js-anonimyze-ips:
|
||||||
|
|
||||||
|
Anonymize IPs
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can enable the `IP anonymization`_ feature by setting the
|
||||||
|
:const:`GOOGLE_ANALYTICS_ANONYMIZE_IP` configuration setting::
|
||||||
|
|
||||||
|
GOOGLE_ANALYTICS_ANONYMIZE_IP = True
|
||||||
|
|
||||||
|
This may be mandatory for deployments in countries that have a firm policies
|
||||||
|
concerning data privacy (e.g. Germany).
|
||||||
|
|
||||||
|
By default, IPs are not anonymized.
|
||||||
|
|
||||||
|
.. _`IP anonymization`: https://support.google.com/analytics/bin/answer.py?hl=en&answer=2763052
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-js-sample-rate:
|
||||||
|
|
||||||
|
Sample Rate
|
||||||
|
-----------
|
||||||
|
|
||||||
|
You can configure the `Sample Rate`_ feature by setting the
|
||||||
|
:const:`GOOGLE_ANALYTICS_SAMPLE_RATE` configuration setting::
|
||||||
|
|
||||||
|
GOOGLE_ANALYTICS_SAMPLE_RATE = 10
|
||||||
|
|
||||||
|
The value is a percentage and can be between 0 and 100 and can be a string or
|
||||||
|
integer value.
|
||||||
|
|
||||||
|
.. _`Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#sampleRate
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-js-site-speed-sample-rate:
|
||||||
|
|
||||||
|
Site Speed Sample Rate
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
You can configure the `Site Speed Sample Rate`_ feature by setting the
|
||||||
|
:const:`GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE` configuration setting::
|
||||||
|
|
||||||
|
GOOGLE_ANALYTICS_SITE_SPEED_SAMPLE_RATE = 10
|
||||||
|
|
||||||
|
The value is a percentage and can be between 0 and 100 and can be a string or
|
||||||
|
integer value.
|
||||||
|
|
||||||
|
.. _`Site Speed Sample Rate`: https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#siteSpeedSampleRate
|
||||||
|
|
||||||
|
|
||||||
|
.. _google-analytics-cookie-expiration:
|
||||||
|
|
||||||
|
Cookie Expiration
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
You can configure the `Cookie Expiration`_ feature by setting the
|
||||||
|
:const:`GOOGLE_ANALYTICS_COOKIE_EXPIRATION` configuration setting::
|
||||||
|
|
||||||
|
GOOGLE_ANALYTICS_COOKIE_EXPIRATION = 3600000
|
||||||
|
|
||||||
|
The value is the cookie expiration in seconds or 0 to delete the cookie when the browser is closed.
|
||||||
|
|
||||||
|
.. _`Cookie Expiration`: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_setsessioncookietimeout
|
||||||
|
|
||||||
|
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.
|
||||||
73
docs/services/hotjar.rst
Normal file
73
docs/services/hotjar.rst
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
=====================================
|
||||||
|
Hotjar -- analytics and user feedback
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
`Hotjar`_ is a website analytics and user feedback tool.
|
||||||
|
|
||||||
|
.. _`Hotjar`: https://www.hotjar.com/
|
||||||
|
|
||||||
|
|
||||||
|
.. hotjar-installation:
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
To start using the Hotjar integration, you must have installed the
|
||||||
|
django-analytical package and have added the ``analytical`` application
|
||||||
|
to :const:`INSTALLED_APPS` in your project :file:`settings.py` file.
|
||||||
|
See :doc:`../install` for details.
|
||||||
|
|
||||||
|
Next you need to add the Hotjar template tag to your templates.
|
||||||
|
This step is only needed if you are not using the generic
|
||||||
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
|
:ref:`hotjar-configuration`.
|
||||||
|
|
||||||
|
The Hotjar code is inserted into templates using template tags.
|
||||||
|
Because every page that you want to track must have the tag,
|
||||||
|
it is useful to add it to your base template.
|
||||||
|
At the top of the template, load the :mod:`hotjar` template tag library.
|
||||||
|
Then insert the :ttag:`hotjar` tag at the bottom of the head section::
|
||||||
|
|
||||||
|
{% load hotjar %}
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
...
|
||||||
|
{% hotjar %}
|
||||||
|
</head>
|
||||||
|
...
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
.. _hotjar-configuration:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
Before you can use the Hotjar integration, you must first set your Site ID.
|
||||||
|
|
||||||
|
|
||||||
|
.. _hotjar-id:
|
||||||
|
|
||||||
|
Setting the Hotjar Site ID
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
You can find the Hotjar Site ID in the "Sites & Organizations" section of your Hotjar account.
|
||||||
|
Set :const:`HOTJAR_SITE_ID` in the project :file:`settings.py` file::
|
||||||
|
|
||||||
|
HOTJAR_SITE_ID = 'XXXXXXXXX'
|
||||||
|
|
||||||
|
If you do not set a Hotjar Site ID, the code will not be rendered.
|
||||||
|
|
||||||
|
|
||||||
|
.. _hotjar-internal-ips:
|
||||||
|
|
||||||
|
Internal IP addresses
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Usually you do not want to track clicks from your development or
|
||||||
|
internal IP addresses. By default, if the tags detect that the client
|
||||||
|
comes from any address in the :const:`HOTJAR_INTERNAL_IPS`
|
||||||
|
setting, the tracking code is commented out. It takes the value of
|
||||||
|
:const:`ANALYTICAL_INTERNAL_IPS` by default (which in turn is
|
||||||
|
:const:`INTERNAL_IPS` by default). See :ref:`identifying-visitors` for
|
||||||
|
important information about detecting the visitor IP address.
|
||||||
|
|
@ -23,7 +23,7 @@ This step is only needed if you are not using the generic
|
||||||
:ttag:`analytical.*` tags. If you are, skip to
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`intercom-configuration`.
|
: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
|
template tag. Load the :mod:`intercom` template tag library and
|
||||||
insert the :ttag:`intercom` tag. Because every page that you want to
|
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.
|
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`
|
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
|
app id by clicking the *Tracking Code* link when logged into
|
||||||
the on the intercom.io website. A page will display containing
|
the on the intercom.io website. A page will display containing
|
||||||
HTML code looking like this::
|
HTML code looking like this::
|
||||||
|
|
@ -71,7 +71,7 @@ file::
|
||||||
|
|
||||||
INTERCOM_APP_ID = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
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.
|
rendered.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -120,22 +120,41 @@ Context variable Description
|
||||||
-------------------- -------------------------------------------
|
-------------------- -------------------------------------------
|
||||||
``intercom_email`` The visitor's email address.
|
``intercom_email`` The visitor's email address.
|
||||||
-------------------- -------------------------------------------
|
-------------------- -------------------------------------------
|
||||||
|
``intercom_user_id`` The visitor's user id.
|
||||||
|
-------------------- -------------------------------------------
|
||||||
``created_at`` The date the visitor created an account
|
``created_at`` The date the visitor created an account
|
||||||
==================== ===========================================
|
==================== ===========================================
|
||||||
|
|
||||||
|
|
||||||
.. _`custom visitor data`: http://docs.intercom.io/custom-data/adding-custom-data
|
.. _`custom visitor data`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/customize-intercom-to-be-about-your-users/send-custom-user-attributes-to-intercom
|
||||||
|
|
||||||
|
|
||||||
Identifying authenticated users
|
Identifying authenticated users
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
If you have not set the ``intercom_name`` or ``intercom_email`` variables
|
If you have not set the ``intercom_name``, ``intercom_email``, or ``intercom_user_id`` variables
|
||||||
explicitly, the username and email address of an authenticated user are
|
explicitly, the username and email address of an authenticated user are
|
||||||
passed to Intercom automatically. See :ref:`identifying-visitors`.
|
passed to Intercom automatically. See :ref:`identifying-visitors`.
|
||||||
|
|
||||||
.. _intercom-internal-ips:
|
.. _intercom-internal-ips:
|
||||||
|
|
||||||
|
|
||||||
|
Verifying identified users
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Intercom supports HMAC authentication of users identified by user ID or email, in order to prevent impersonation.
|
||||||
|
For more information, see `Enable identity verification on your web product`_ in the Intercom documentation.
|
||||||
|
|
||||||
|
To enable this, configure your Intercom account's HMAC secret key::
|
||||||
|
|
||||||
|
INTERCOM_HMAC_SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXX'
|
||||||
|
|
||||||
|
(You can find this secret key under the "Identity verification" section of your Intercom account settings page.)
|
||||||
|
|
||||||
|
.. _`Enable identity verification on your web product`: https://www.intercom.com/help/configure-intercom-for-your-product-or-site/staying-secure/enable-identity-verification-on-your-web-product
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Internal IP addresses
|
Internal IP addresses
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,10 @@ Setting the account number and site code
|
||||||
|
|
||||||
In order to install the survey code, you need to set your KISSinsights
|
In order to install the survey code, you need to set your KISSinsights
|
||||||
account number and website code. The :ttag:`kiss_insights` tag will
|
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
|
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
|
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
|
``//s3.amazonaws.com/ki.js/XXXXX/YYY.js``. Here ``XXXXX`` is the
|
||||||
account number and ``YYY`` the website code. Set
|
account number and ``YYY`` the website code. Set
|
||||||
:const:`KISS_INSIGHTS_ACCOUNT_NUMBER` and
|
: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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`kiss-metrics-configuration`.
|
: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
|
template tag. Load the :mod:`kiss_metrics` template tag library and
|
||||||
insert the :ttag:`kiss_metrics` tag. Because every page that you want
|
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
|
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
|
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
|
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
|
website *Product center* on your KISSmetrics dashboard. Set
|
||||||
:const:`KISS_METRICS_API_KEY` in the project :file:`settings.py` file::
|
:const:`KISS_METRICS_API_KEY` in the project :file:`settings.py` file::
|
||||||
|
|
||||||
|
|
@ -144,7 +144,7 @@ For example::
|
||||||
})
|
})
|
||||||
return some_template.render(context)
|
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.
|
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.
|
||||||
182
docs/services/matomo.rst
Normal file
182
docs/services/matomo.rst
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
====================================================
|
||||||
|
Matomo (formerly Piwik) -- open source web analytics
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
Matomo_ is an open analytics platform currently used by individuals,
|
||||||
|
companies and governments all over the world.
|
||||||
|
|
||||||
|
.. _Matomo: http://matomo.org/
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
To start using the Matomo 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 Matomo 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:`matomo-configuration`.
|
||||||
|
|
||||||
|
The Matomo tracking code is inserted into templates using a template
|
||||||
|
tag. Load the :mod:`matomo` template tag library and insert the
|
||||||
|
:ttag:`matomo` 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
|
||||||
|
`Matomo best practice for Integration Plugins`_::
|
||||||
|
|
||||||
|
{% load matomo %}
|
||||||
|
...
|
||||||
|
{% matomo %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
.. _`Matomo best practice for Integration Plugins`: http://matomo.org/integrate/how-to/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _matomo-configuration:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
Before you can use the Matomo integration, you must first define
|
||||||
|
domain name and optional URI path to your Matomo server, as well as
|
||||||
|
the Matomo ID of the website you're tracking with your Matomo server,
|
||||||
|
in your project settings.
|
||||||
|
|
||||||
|
|
||||||
|
Setting the domain
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Your Django project needs to know where your Matomo server is located.
|
||||||
|
Typically, you'll have Matomo installed on a subdomain of its own
|
||||||
|
(e.g. ``matomo.example.com``), otherwise it runs in a subdirectory of
|
||||||
|
a website of yours (e.g. ``www.example.com/matomo``). Set
|
||||||
|
:const:`MATOMO_DOMAIN_PATH` in the project :file:`settings.py` file
|
||||||
|
accordingly::
|
||||||
|
|
||||||
|
MATOMO_DOMAIN_PATH = 'matomo.example.com'
|
||||||
|
|
||||||
|
If you do not set a domain the tracking code will not be rendered.
|
||||||
|
|
||||||
|
|
||||||
|
Setting the site ID
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Your Matomo 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 Matomo Dashboard). Set
|
||||||
|
:const:`MATOMO_SITE_ID` in the project :file:`settings.py` file to
|
||||||
|
the value corresponding to the website you're tracking::
|
||||||
|
|
||||||
|
MATOMO_SITE_ID = '4'
|
||||||
|
|
||||||
|
If you do not set the site ID the tracking code will not be rendered.
|
||||||
|
|
||||||
|
|
||||||
|
.. _matomo-uservars:
|
||||||
|
|
||||||
|
User variables
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Matomo supports sending `custom variables`_ along with default statistics. If
|
||||||
|
you want to set a custom variable, use the context variable ``matomo_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({
|
||||||
|
'matomo_vars': [(1, 'foo', 'Sir Lancelot of Camelot'),
|
||||||
|
(2, 'bar', 'To seek the Holy Grail', 'page'),
|
||||||
|
(3, 'spam', 'Blue', 'visit')]
|
||||||
|
})
|
||||||
|
return some_template.render(context)
|
||||||
|
|
||||||
|
Matomo default settings allow up to 5 custom variables for both scope. Variable
|
||||||
|
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
|
||||||
|
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.matomo.org/guides/tracking-javascript-guide#custom-variables
|
||||||
|
|
||||||
|
|
||||||
|
.. _matomo-user-tracking:
|
||||||
|
|
||||||
|
User tracking
|
||||||
|
-------------
|
||||||
|
|
||||||
|
If you use the standard Django authentication system, you can allow Matomo to
|
||||||
|
`track individual users`_ by setting the :data:`ANALYTICAL_AUTO_IDENTIFY`
|
||||||
|
setting to :const:`True`. This is enabled by default. Matomo 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
|
||||||
|
``matomo_identity`` (for Matomo specific configuration). Setting one to
|
||||||
|
:const:`None` will disable the user tracking feature::
|
||||||
|
|
||||||
|
# Matomo 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')
|
||||||
|
|
||||||
|
# Matomo will identify this user as 'Guido van Rossum'
|
||||||
|
request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||||
|
context = Context({
|
||||||
|
'matomo_identity': request.user.get_full_name()
|
||||||
|
})
|
||||||
|
|
||||||
|
# Matomo will not identify this user (but will still collect statistics)
|
||||||
|
request.user = User(username='BDFL', first_name='Guido', last_name='van Rossum')
|
||||||
|
context = Context({
|
||||||
|
'matomo_identity': None
|
||||||
|
})
|
||||||
|
|
||||||
|
.. _`track individual users`: http://developer.matomo.org/guides/tracking-javascript-guide#user-id
|
||||||
|
|
||||||
|
Disabling cookies
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If you want to `disable cookies`_, set :data:`MATOMO_DISABLE_COOKIES` to
|
||||||
|
:const:`True`. This is disabled by default.
|
||||||
|
|
||||||
|
.. _`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:`MATOMO_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:
|
||||||
|
|
||||||
|
`matomo_give_consent` - class name for element to click when visitors want to **give** consent
|
||||||
|
`matomo_remove_consent` - class name for element to click when visitors want to **remove** consent
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
# button to allow tracking
|
||||||
|
<button class="matomo_give_consent">Track me!</button>
|
||||||
|
|
||||||
|
# button to remove tracking consent
|
||||||
|
<button class="matomo_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.
|
||||||
|
|
@ -24,7 +24,7 @@ step is only needed if you are not using the generic
|
||||||
:ttag:`analytical.*` tags. If you are, skip to
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`mixpanel-configuration`.
|
: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
|
template tag. Load the :mod:`mixpanel` template tag library and
|
||||||
insert the :ttag:`mixpanel` tag. Because every page that you want
|
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
|
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,
|
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.
|
code. You can find the project token on the Mixpanel *projects* page.
|
||||||
Set :const:`MIXPANEL_API_TOKEN` in the project :file:`settings.py`
|
Set :const:`MIXPANEL_API_TOKEN` in the project :file:`settings.py`
|
||||||
file::
|
file::
|
||||||
|
|
@ -137,16 +137,16 @@ For example::
|
||||||
Tracking events
|
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
|
templates. To tracking events in views or other parts of Django, you
|
||||||
can use Wes Winham's `mixpanel-celery`_ package.
|
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
|
notation, as described in the section titled
|
||||||
`"Asynchronous Tracking with Javascript"`_ in the Mixpanel
|
`"Asynchronous Tracking with JavaScript"`_ in the Mixpanel
|
||||||
documentation. For example::
|
documentation. For example::
|
||||||
|
|
||||||
mixpanel.track("play-game", {"level": "12", "weapon": "sword", "character": "knight"});
|
mixpanel.track("play-game", {"level": "12", "weapon": "sword", "character": "knight"});
|
||||||
|
|
||||||
.. _mixpanel-celery: http://github.com/winhamwr/mixpanel-celery
|
.. _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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`olark-configuration`.
|
: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
|
tag. Load the :mod:`olark` template tag library and insert the
|
||||||
:ttag:`olark` tag. Because every page that you want to track
|
: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
|
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.
|
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.
|
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::
|
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
|
:class:`~django.template.context.RequestContext` constructor and in a
|
||||||
context processor, the latter clobbers the former.
|
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.
|
documentation.
|
||||||
|
|
||||||
.. _`api.chat.updateVisitorNickname`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorNickname
|
.. _`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)
|
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.
|
documentation.
|
||||||
|
|
||||||
.. _`api.chat.updateVisitorStatus`: http://www.olark.com/documentation/javascript/api.chat.updateVisitorStatus
|
.. _`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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`optimizely-configuration`.
|
: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
|
template tag. Load the :mod:`optimizely` template tag library and
|
||||||
insert the :ttag:`optimizely` tag. Because every page that you want to
|
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.
|
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`
|
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
|
account number by clicking the *Implementation* link in the top
|
||||||
right-hand corner of the Optimizely website. A pop-up window will
|
right-hand corner of the Optimizely website. A pop-up window will
|
||||||
appear containing HTML code looking like this::
|
appear containing HTML code looking like this::
|
||||||
|
|
@ -66,7 +66,7 @@ file::
|
||||||
|
|
||||||
OPTIMIZELY_ACCOUNT_NUMBER = 'XXXXXXX'
|
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.
|
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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`performable-configuration`.
|
: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
|
template tag. Load the :mod:`performable` template tag library and
|
||||||
insert the :ttag:`performable` tag. Because every page that you want to
|
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.
|
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`
|
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
|
API key on the *Account Settings* page (click 'Account Settings' in the
|
||||||
top right-hand corner of your Performable dashboard). Set
|
top right-hand corner of your Performable dashboard). Set
|
||||||
:const:`PERFORMABLE_API_KEY` in the project :file:`settings.py` file::
|
:const:`PERFORMABLE_API_KEY` in the project :file:`settings.py` file::
|
||||||
|
|
||||||
PERFORMABLE_API_KEY = 'XXXXXX'
|
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:
|
.. _performable-identity-user:
|
||||||
|
|
@ -116,7 +116,7 @@ Embedding a landing page
|
||||||
========================
|
========================
|
||||||
|
|
||||||
You can embed a Performable landing page in your Django website. The
|
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::
|
the page. It takes two arguments: the hostname and the page ID::
|
||||||
|
|
||||||
{% performable_embed HOSTNAME PAGE_ID %}
|
{% performable_embed HOSTNAME PAGE_ID %}
|
||||||
|
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
==================================
|
|
||||||
Piwik -- 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/
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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/
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
===================================
|
===================================
|
||||||
Rating@Mail.ru -- traffic analysis
|
Rating\@Mail.ru -- traffic analysis
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
`Rating@Mail.ru`_ is an analytics tool like as google analytics.
|
`Rating\@Mail.ru`_ is an analytics tool like as google analytics.
|
||||||
|
|
||||||
.. _`Rating@Mail.ru`: http://top.mail.ru/
|
.. _`Rating\@Mail.ru`: http://top.mail.ru/
|
||||||
|
|
||||||
|
|
||||||
.. rating-mailru-installation:
|
.. rating-mailru-installation:
|
||||||
|
|
@ -12,17 +12,17 @@ Rating@Mail.ru -- traffic analysis
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
To start using the Rating@Mail.ru integration, you must have installed the
|
To start using the Rating\@Mail.ru integration, you must have installed the
|
||||||
django-analytical package and have added the ``analytical`` application
|
django-analytical package and have added the ``analytical`` application
|
||||||
to :const:`INSTALLED_APPS` in your project :file:`settings.py` file.
|
to :const:`INSTALLED_APPS` in your project :file:`settings.py` file.
|
||||||
See :doc:`../install` for details.
|
See :doc:`../install` for details.
|
||||||
|
|
||||||
Next you need to add the Rating@Mail.ru template tag to your templates. This
|
Next you need to add the Rating\@Mail.ru template tag to your templates. This
|
||||||
step is only needed if you are not using the generic
|
step is only needed if you are not using the generic
|
||||||
:ttag:`analytical.*` tags. If you are, skip to
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`rating-mailru-configuration`.
|
:ref:`rating-mailru-configuration`.
|
||||||
|
|
||||||
The Rating@Mail.ru counter code is inserted into templates using a template
|
The Rating\@Mail.ru counter code is inserted into templates using a template
|
||||||
tag. Load the :mod:`rating_mailru` template tag library and insert the
|
tag. Load the :mod:`rating_mailru` template tag library and insert the
|
||||||
:ttag:`rating_mailru` tag. Because every page that you want to track must
|
:ttag:`rating_mailru` tag. Because every page that you want to track must
|
||||||
have the tag, it is useful to add it to your base template. Insert
|
have the tag, it is useful to add it to your base template. Insert
|
||||||
|
|
@ -42,7 +42,7 @@ the tag at the bottom of the HTML head::
|
||||||
Configuration
|
Configuration
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Before you can use the Rating@Mail.ru integration, you must first set
|
Before you can use the Rating\@Mail.ru integration, you must first set
|
||||||
your website counter ID.
|
your website counter ID.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -51,9 +51,9 @@ your website counter ID.
|
||||||
Setting the counter ID
|
Setting the counter ID
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Every website you track with Rating@Mail.ru gets its own 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
|
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
|
of your account. Set :const:`RATING_MAILRU_COUNTER_ID` in the
|
||||||
project :file:`settings.py` file::
|
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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`snapengage-configuration`.
|
: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
|
template tag. Load the :mod:`SnapEngage` template tag library and
|
||||||
insert the :ttag:`SnapEngage` tag. Because every page that you want to
|
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.
|
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 -- 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
|
converting sources, search keywords and landing pages. The real-time
|
||||||
dashboard shows you how customers interact with your website and how
|
dashboard shows you how customers interact with your website and how
|
||||||
to increase conversion.
|
to increase conversion.
|
||||||
|
|
@ -53,7 +53,7 @@ Setting the Tracking ID
|
||||||
|
|
||||||
Every website you track with Spring Metrics gets its own 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
|
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`
|
of your Spring Metrics account. Set :const:`SPRING_METRICS_TRACKING_ID`
|
||||||
in the project :file:`settings.py` file::
|
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
|
:ttag:`analytical.*` tags. If you are, skip to
|
||||||
:ref:`uservoice-configuration`.
|
: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
|
template tag. Load the :mod:`uservoice` template tag library and insert
|
||||||
the :ttag:`uservoice` tag. Because every page that you want to have
|
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
|
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
|
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
|
you want to show. You can find the widget keys in the *Channels* tab on
|
||||||
your UserVoice *Settings* page. Under the *Javascript Widget* heading,
|
your UserVoice *Settings* page. Under the *JavaScript Widget* heading,
|
||||||
find the Javascript embed code of the widget. The widget key is the
|
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
|
alphanumerical string contained in the URL of the script imported by the
|
||||||
embed code::
|
embed code::
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script>
|
||||||
|
|
||||||
UserVoice=window.UserVoice||[];(function(){
|
UserVoice=window.UserVoice||[];(function(){
|
||||||
var uv=document.createElement('script');uv.type='text/javascript';
|
var uv=document.createElement('script');uv.type='text/javascript';
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ the tag at the bottom of the HTML head::
|
||||||
</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
|
section increases the chances that a page view is going to be tracked
|
||||||
before the visitor leaves the page. See for details the `Asynchronous
|
before the visitor leaves the page. See for details the `Asynchronous
|
||||||
JavaScript Developer’s Guide`_ on the Woopra website.
|
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.
|
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
|
Internal IP addresses
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ Setting the counter ID
|
||||||
|
|
||||||
Every website you track with Yandex.Metrica gets its own 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
|
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
|
of your account. Set :const:`YANDEX_METRICA_COUNTER_ID` in the
|
||||||
project :file:`settings.py` file::
|
project :file:`settings.py` file::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ Here's a full list of all available settings, in alphabetical order, and
|
||||||
their default values.
|
their default values.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. data:: ANALYTICAL_AUTO_IDENTIFY
|
.. data:: ANALYTICAL_AUTO_IDENTIFY
|
||||||
|
|
||||||
Default: ``True``
|
Default: ``True``
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,9 @@ Setting up basic tracking
|
||||||
|
|
||||||
To get started with django-analytical, the package must first be
|
To get started with django-analytical, the package must first be
|
||||||
installed. You can download and install the latest stable package from
|
installed. You can download and install the latest stable package from
|
||||||
the Python Package Index automatically by using ``easy_install``::
|
the Python Package Index automatically by using ``easy_install``:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
$ easy_install django-analytical
|
$ easy_install django-analytical
|
||||||
|
|
||||||
|
|
@ -37,7 +39,9 @@ For more ways to install django-analytical, see
|
||||||
:ref:`installing-the-package`.
|
:ref:`installing-the-package`.
|
||||||
|
|
||||||
After you install django-analytical, you need to add it to the list of
|
After you install django-analytical, you need to add it to the list of
|
||||||
installed applications in the ``settings.py`` file of your project::
|
installed applications in the ``settings.py`` file of your project:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
...
|
...
|
||||||
|
|
@ -46,7 +50,9 @@ installed applications in the ``settings.py`` file of your project::
|
||||||
]
|
]
|
||||||
|
|
||||||
Then you have to add the general-purpose django-analytical template tags
|
Then you have to add the general-purpose django-analytical template tags
|
||||||
to your base template::
|
to your base template:
|
||||||
|
|
||||||
|
.. code-block:: django
|
||||||
|
|
||||||
{% load analytical %}
|
{% load analytical %}
|
||||||
<!DOCTYPE ... >
|
<!DOCTYPE ... >
|
||||||
|
|
@ -69,7 +75,9 @@ to your base template::
|
||||||
|
|
||||||
Finally, you need to configure the Clicky Site ID and the Crazy Egg
|
Finally, you need to configure the Clicky Site ID and the Crazy Egg
|
||||||
account number. Add the following to your project :file:`settings.py`
|
account number. Add the following to your project :file:`settings.py`
|
||||||
file (replacing the ``x``'s with your own codes)::
|
file (replacing the ``x``'s with your own codes):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
CLICKY_SITE_ID = 'xxxxxxxx'
|
CLICKY_SITE_ID = 'xxxxxxxx'
|
||||||
CRAZY_EGG_ACCOUNT_NUMBER = 'xxxxxxxx'
|
CRAZY_EGG_ACCOUNT_NUMBER = 'xxxxxxxx'
|
||||||
|
|
@ -110,7 +118,9 @@ protocol version.
|
||||||
|
|
||||||
In order to filter on protocol version in Crazy Egg, you need to
|
In order to filter on protocol version in Crazy Egg, you need to
|
||||||
include the visitor IP protocol version in the Crazy Egg tracking code.
|
include the visitor IP protocol version in the Crazy Egg tracking code.
|
||||||
The easiest way to do this is by using a context processor::
|
The easiest way to do this is by using a context processor:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
def track_ip_proto(request):
|
def track_ip_proto(request):
|
||||||
addr = request.META.get('HTTP_X_FORWARDED_FOR', '')
|
addr = request.META.get('HTTP_X_FORWARDED_FOR', '')
|
||||||
|
|
|
||||||
127
pyproject.toml
Normal file
127
pyproject.toml
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
[build-system]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
requires = ["setuptools>=80"]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "django-analytical"
|
||||||
|
dynamic = ["version"]
|
||||||
|
description = "Analytics service integration for Django projects"
|
||||||
|
readme = "README.rst"
|
||||||
|
license = "MIT"
|
||||||
|
license-files = ["LICENSE.txt"]
|
||||||
|
authors = [
|
||||||
|
{name = "Joost Cassee", email = "joost@cassee.net"},
|
||||||
|
{name = "Joshua Krall", email = "joshuakrall@pobox.com"},
|
||||||
|
{name = "Aleck Landgraf", email = "aleck.landgraf@buildingenergy.com"},
|
||||||
|
{name = "Alexandre Pocquet", email = "apocquet@lecko.fr"},
|
||||||
|
{name = "Bateau Knowledge", email = "info@bateauknowledge.nl"},
|
||||||
|
{name = "Bogdan Bodnar", email = "bogdanbodnar@mail.com"},
|
||||||
|
{name = "Brad Pitcher", email = "bradpitcher@gmail.com"},
|
||||||
|
{name = "Corentin Mercier", email = "corentin@mercier.link"},
|
||||||
|
{name = "Craig Bruce", email = "craig@eyesopen.com"},
|
||||||
|
{name = "Daniel Vitiello", email = "ezdismissal@gmail.com"},
|
||||||
|
{name = "David Smith", email = "smithdc@gmail.com"},
|
||||||
|
{name = "Diederik van der Boor", email = "vdboor@edoburu.nl"},
|
||||||
|
{name = "Eric Amador", email = "eric.amador14@gmail.com"},
|
||||||
|
{name = "Eric Davis", email = "eric@davislv.com"},
|
||||||
|
{name = "Eric Wang", email = "gnawrice@gmail.com"},
|
||||||
|
{name = "Erick Massip", email = "ericmassip1@gmail.com"},
|
||||||
|
{name = "Garrett Coakley", email = "garrettc@users.noreply.github.com"},
|
||||||
|
{name = "Garrett Robinson", email = "garrett.f.robinson@gmail.com"},
|
||||||
|
{name = "GreenKahuna", email = "info@greenkahuna.com"},
|
||||||
|
{name = "Hugo Osvaldo Barrera", email = "hugo@barrera.io"},
|
||||||
|
{name = "Ian Ramsay", email = "ianalexr@yahoo.com"},
|
||||||
|
{name = "Iván Raskovsky", email = "raskovsky+git@gmail.com"},
|
||||||
|
{name = "James Paden", email = "james@xemion.com"},
|
||||||
|
{name = "Jannis Leidel", email = "jannis@leidel.info"},
|
||||||
|
{name = "Julien Grenier", email = "julien.grenier42@gmail.com"},
|
||||||
|
{name = "Kevin Olbrich", email = "ko@sv01.de"},
|
||||||
|
{name = "Marc Bourqui", email = "m.bourqui@edsi-tech.com"},
|
||||||
|
{name = "Martey Dodoo", email = "martey@mobolic.com"},
|
||||||
|
{name = "Martín Gaitán", email = "gaitan@gmail.com"},
|
||||||
|
{name = "Matthäus G. Chajdas", email = "dev@anteru.net"},
|
||||||
|
{name = "Max Arnold", email = "arnold.maxim@gmail.com"},
|
||||||
|
{name = "Nikolay Korotkiy", email = "sikmir@gmail.com"},
|
||||||
|
{name = "Paul Oswald", email = "pauloswald@gmail.com"},
|
||||||
|
{name = "Peter Bittner", email = "django@bittner.it"},
|
||||||
|
{name = "Petr Dlouhý", email = "petr.dlouhy@email.cz"},
|
||||||
|
{name = "Philippe O. Wagner", email = "admin@arteria.ch"},
|
||||||
|
{name = "Pi Delport", email = "pjdelport@gmail.com"},
|
||||||
|
{name = "Sandra Mau", email = "sandra.mau@gmail.com"},
|
||||||
|
{name = "Scott Adams", email = "scottadams80@gmail.com"},
|
||||||
|
{name = "Scott Karlin", email = "gitlab@karlin-online.com"},
|
||||||
|
{name = "Sean Wallace", email = "sean@lowpro.ca"},
|
||||||
|
{name = "Sid Mitra", email = "sidmitra.del@gmail.com"},
|
||||||
|
{name = "Simon Ye", email = "sye737@gmail.com"},
|
||||||
|
{name = "Steve Schwarz", email = "steve@agilitynerd.com"},
|
||||||
|
{name = "Steven Skoczen", email = "steven.skoczen@wk.com"},
|
||||||
|
{name = "Tim Gates", email = "tim.gates@iress.com"},
|
||||||
|
{name = "Tinnet Coronam", email = "tinnet@coronam.net"},
|
||||||
|
{name = "Uros Trebec", email = "uros@trebec.org"},
|
||||||
|
{name = "Walter Renner", email = "walter.renner@me.com"},
|
||||||
|
{name = "Julian Haluska", email = "mail@julianhaluska.de"},
|
||||||
|
{name = "Ronard Luna", email = "rlunag@proton.me"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Jazzband community", email = "jazzband-bot@users.noreply.github.com"},
|
||||||
|
{name = "Peter Bittner", email = "django@bittner.it"},
|
||||||
|
]
|
||||||
|
keywords=[
|
||||||
|
"django",
|
||||||
|
"analytics",
|
||||||
|
]
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Web Environment",
|
||||||
|
"Framework :: Django",
|
||||||
|
"Framework :: Django :: 4.2",
|
||||||
|
"Framework :: Django :: 5.1",
|
||||||
|
"Framework :: Django :: 5.2",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Topic :: Internet :: WWW/HTTP",
|
||||||
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"django>=4.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/jazzband/django-analytical"
|
||||||
|
Documentation = "https://django-analytical.readthedocs.io/"
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
show_missing = true
|
||||||
|
skip_covered = true
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["analytical"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--junitxml=tests/unittests-report.xml --color=yes --verbose"
|
||||||
|
DJANGO_SETTINGS_MODULE = "tests.testproject.settings"
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "single"
|
||||||
|
|
||||||
|
[tool.ruff.lint.flake8-quotes]
|
||||||
|
inline-quotes = "single"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = [
|
||||||
|
"analytical",
|
||||||
|
"analytical.templatetags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "analytical.__version__"}
|
||||||
|
|
@ -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
|
|
||||||
108
setup.py
108
setup.py
|
|
@ -1,108 +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
|
|
||||||
|
|
||||||
try:
|
|
||||||
from sphinx_pypi_upload import UploadDoc
|
|
||||||
|
|
||||||
cmdclass['upload_sphinx'] = UploadDoc
|
|
||||||
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(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
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name='django-analytical',
|
|
||||||
version=analytical.__version__,
|
|
||||||
license=analytical.__license__,
|
|
||||||
description='Analytics service integration for Django projects',
|
|
||||||
long_description=read('README.rst'),
|
|
||||||
author=analytical.__author__,
|
|
||||||
author_email=analytical.__email__,
|
|
||||||
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.7',
|
|
||||||
'Framework :: Django :: 1.8',
|
|
||||||
'Framework :: Django :: 1.9',
|
|
||||||
'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.2',
|
|
||||||
'Programming Language :: Python :: 3.3',
|
|
||||||
'Programming Language :: Python :: 3.4',
|
|
||||||
'Programming Language :: Python :: 3.5',
|
|
||||||
],
|
|
||||||
platforms=['any'],
|
|
||||||
url='https://github.com/jcassee/django-analytical',
|
|
||||||
download_url='https://github.com/jcassee/django-analytical/archive/master.zip',
|
|
||||||
cmdclass=cmdclass,
|
|
||||||
install_requires=[
|
|
||||||
'Django>=1.7.0',
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
@ -22,3 +22,12 @@ MIDDLEWARE_CLASSES = (
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'APP_DIRS': True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
USE_TZ = False
|
||||||
|
|
@ -2,23 +2,22 @@
|
||||||
Dummy testing template tags and filters.
|
Dummy testing template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from django.template import Library, Node, TemplateSyntaxError
|
from django.template import Library, Node, TemplateSyntaxError
|
||||||
|
|
||||||
from analytical.templatetags.analytical import TAG_LOCATIONS
|
from analytical.templatetags.analytical import TAG_LOCATIONS
|
||||||
|
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
||||||
def _location_node(location):
|
def _location_node(location):
|
||||||
class DummyNode(Node):
|
class DummyNode(Node):
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
return "<!-- dummy_%s -->" % location
|
return '<!-- dummy_%s -->' % location
|
||||||
|
|
||||||
return DummyNode
|
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):
|
def _location_tag(location):
|
||||||
|
|
@ -27,8 +26,10 @@ def _location_tag(location):
|
||||||
if len(bits) > 1:
|
if len(bits) > 1:
|
||||||
raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0])
|
raise TemplateSyntaxError("'%s' tag takes no arguments" % bits[0])
|
||||||
return _location_nodes[location]
|
return _location_nodes[location]
|
||||||
|
|
||||||
return dummy_tag
|
return dummy_tag
|
||||||
|
|
||||||
|
|
||||||
for loc in TAG_LOCATIONS:
|
for loc in TAG_LOCATIONS:
|
||||||
register.tag('dummy_%s' % loc, _location_tag(loc))
|
register.tag('dummy_%s' % loc, _location_tag(loc))
|
||||||
|
|
||||||
|
|
@ -3,9 +3,9 @@ Tests for the generic template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
|
from utils import TagTestCase
|
||||||
|
|
||||||
from analytical.templatetags import analytical
|
from analytical.templatetags import analytical
|
||||||
from analytical.tests.utils import TagTestCase
|
|
||||||
|
|
||||||
|
|
||||||
class AnalyticsTagTestCase(TagTestCase):
|
class AnalyticsTagTestCase(TagTestCase):
|
||||||
|
|
@ -14,24 +14,23 @@ class AnalyticsTagTestCase(TagTestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(AnalyticsTagTestCase, self).setUp()
|
super().setUp()
|
||||||
self._tag_modules = analytical.TAG_MODULES
|
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()
|
analytical.template_nodes = analytical._load_template_nodes()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
analytical.TAG_MODULES = self._tag_modules
|
analytical.TAG_MODULES = self._tag_modules
|
||||||
analytical.template_nodes = analytical._load_template_nodes()
|
analytical.template_nodes = analytical._load_template_nodes()
|
||||||
super(AnalyticsTagTestCase, self).tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
def render_location_tag(self, location, vars=None):
|
def render_location_tag(self, location, vars=None):
|
||||||
if vars is None:
|
if vars is None:
|
||||||
vars = {}
|
vars = {}
|
||||||
t = Template("{%% load analytical %%}{%% analytical_%s %%}"
|
t = Template('{%% load analytical %%}{%% analytical_%s %%}' % location)
|
||||||
% location)
|
|
||||||
return t.render(Context(vars))
|
return t.render(Context(vars))
|
||||||
|
|
||||||
def test_location_tags(self):
|
def test_location_tags(self):
|
||||||
for l in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
for loc in ['head_top', 'head_bottom', 'body_top', 'body_bottom']:
|
||||||
r = self.render_location_tag(l)
|
r = self.render_location_tag(loc)
|
||||||
self.assertTrue('dummy_%s' % l in r, r)
|
assert f'dummy_{loc}' in r
|
||||||
|
|
@ -4,14 +4,14 @@ Tests for the Chartbeat template tags and filters.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template import Context
|
from django.template import Context
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
from utils import TagTestCase
|
||||||
|
|
||||||
from analytical.templatetags.chartbeat import ChartbeatTopNode, \
|
from analytical.templatetags.chartbeat import ChartbeatBottomNode, ChartbeatTopNode
|
||||||
ChartbeatBottomNode
|
|
||||||
from analytical.tests.utils import TagTestCase
|
|
||||||
from analytical.utils import AnalyticalException
|
from analytical.utils import AnalyticalException
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,27 +22,29 @@ class ChartbeatTagTestCaseNoSites(TestCase):
|
||||||
self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r)
|
self.assertTrue('var _sf_async_config={"uid": "12345"};' in r, r)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(INSTALLED_APPS=(
|
@override_settings(
|
||||||
'analytical',
|
INSTALLED_APPS=(
|
||||||
'django.contrib.sites',
|
'analytical',
|
||||||
'django.contrib.auth',
|
'django.contrib.sites',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.auth',
|
||||||
))
|
'django.contrib.contenttypes',
|
||||||
|
)
|
||||||
|
)
|
||||||
@override_settings(CHARTBEAT_USER_ID='12345')
|
@override_settings(CHARTBEAT_USER_ID='12345')
|
||||||
class ChartbeatTagTestCaseWithSites(TestCase):
|
class ChartbeatTagTestCaseWithSites(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
call_command("migrate", verbosity=0)
|
|
||||||
|
call_command('migrate', verbosity=0)
|
||||||
|
|
||||||
def test_rendering_setup_site(self):
|
def test_rendering_setup_site(self):
|
||||||
from django.contrib.sites.models import Site
|
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):
|
with override_settings(SITE_ID=site.id):
|
||||||
r = ChartbeatBottomNode().render(Context())
|
r = ChartbeatBottomNode().render(Context())
|
||||||
self.assertTrue(re.search(
|
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
|
||||||
'var _sf_async_config={.*"uid": "12345".*};', r), r)
|
assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r)
|
||||||
self.assertTrue(re.search(
|
|
||||||
'var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
|
||||||
|
|
||||||
@override_settings(CHARTBEAT_AUTO_DOMAIN=False)
|
@override_settings(CHARTBEAT_AUTO_DOMAIN=False)
|
||||||
def test_auto_domain_false(self):
|
def test_auto_domain_false(self):
|
||||||
|
|
@ -52,7 +54,7 @@ class ChartbeatTagTestCaseWithSites(TestCase):
|
||||||
in _sf_async_config.
|
in _sf_async_config.
|
||||||
"""
|
"""
|
||||||
r = ChartbeatBottomNode().render(Context())
|
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')
|
@override_settings(CHARTBEAT_USER_ID='12345')
|
||||||
|
|
@ -62,38 +64,48 @@ class ChartbeatTagTestCase(TagTestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_top_tag(self):
|
def test_top_tag(self):
|
||||||
r = self.render_tag('chartbeat', 'chartbeat_top',
|
r = self.render_tag(
|
||||||
{'chartbeat_domain': "test.com"})
|
'chartbeat', 'chartbeat_top', {'chartbeat_domain': 'test.com'}
|
||||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
)
|
||||||
|
assert 'var _sf_startpt=(new Date()).getTime()' in r
|
||||||
|
|
||||||
def test_bottom_tag(self):
|
def test_bottom_tag(self):
|
||||||
r = self.render_tag('chartbeat', 'chartbeat_bottom',
|
r = self.render_tag(
|
||||||
{'chartbeat_domain': "test.com"})
|
'chartbeat', 'chartbeat_bottom', {'chartbeat_domain': 'test.com'}
|
||||||
self.assertTrue(re.search(
|
)
|
||||||
'var _sf_async_config={.*"uid": "12345".*};', r), r)
|
assert re.search('var _sf_async_config={.*"uid": "12345".*};', r)
|
||||||
self.assertTrue(re.search(
|
assert re.search('var _sf_async_config={.*"domain": "test.com".*};', r)
|
||||||
'var _sf_async_config={.*"domain": "test.com".*};', r), r)
|
|
||||||
|
|
||||||
def test_top_node(self):
|
def test_top_node(self):
|
||||||
r = ChartbeatTopNode().render(
|
r = ChartbeatTopNode().render(
|
||||||
Context({'chartbeat_domain': "test.com"}))
|
Context(
|
||||||
self.assertTrue('var _sf_startpt=(new Date()).getTime()' in r, r)
|
{
|
||||||
|
'chartbeat_domain': 'test.com',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert 'var _sf_startpt=(new Date()).getTime()' in r
|
||||||
|
|
||||||
def test_bottom_node(self):
|
def test_bottom_node(self):
|
||||||
r = ChartbeatBottomNode().render(
|
r = ChartbeatBottomNode().render(
|
||||||
Context({'chartbeat_domain': "test.com"}))
|
Context(
|
||||||
self.assertTrue(re.search(
|
{
|
||||||
'var _sf_async_config={.*"uid": "12345".*};', r), r)
|
'chartbeat_domain': 'test.com',
|
||||||
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_USER_ID=None)
|
@override_settings(CHARTBEAT_USER_ID=None)
|
||||||
def test_no_user_id(self):
|
def test_no_user_id(self):
|
||||||
self.assertRaises(AnalyticalException, ChartbeatBottomNode)
|
with pytest.raises(AnalyticalException):
|
||||||
|
ChartbeatBottomNode()
|
||||||
|
|
||||||
@override_settings(CHARTBEAT_USER_ID='123abc')
|
@override_settings(CHARTBEAT_USER_ID='123abc')
|
||||||
def test_wrong_user_id(self):
|
def test_wrong_user_id(self):
|
||||||
self.assertRaises(AnalyticalException, ChartbeatBottomNode)
|
with pytest.raises(AnalyticalException):
|
||||||
|
ChartbeatBottomNode()
|
||||||
|
|
||||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||||
def test_render_internal_ip(self):
|
def test_render_internal_ip(self):
|
||||||
|
|
@ -101,6 +113,5 @@ class ChartbeatTagTestCase(TagTestCase):
|
||||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||||
context = Context({'request': req})
|
context = Context({'request': req})
|
||||||
r = ChartbeatBottomNode().render(context)
|
r = ChartbeatBottomNode().render(context)
|
||||||
self.assertTrue(r.startswith(
|
assert r.startswith('<!-- Chartbeat disabled on internal IP address')
|
||||||
'<!-- Chartbeat disabled on internal IP address'), r)
|
assert r.endswith('-->')
|
||||||
self.assertTrue(r.endswith('-->'), r)
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
Tests for the Clickmap template tags and filters.
|
Tests for the Clickmap template tags and filters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template import Context
|
from django.template import Context
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
from utils import TagTestCase
|
||||||
|
|
||||||
from analytical.templatetags.clickmap import ClickmapNode
|
from analytical.templatetags.clickmap import ClickmapNode
|
||||||
from analytical.tests.utils import TagTestCase
|
|
||||||
from analytical.utils import AnalyticalException
|
from analytical.utils import AnalyticalException
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,19 +20,21 @@ class ClickmapTagTestCase(TagTestCase):
|
||||||
|
|
||||||
def test_tag(self):
|
def test_tag(self):
|
||||||
r = self.render_tag('clickmap', 'clickmap')
|
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):
|
def test_node(self):
|
||||||
r = ClickmapNode().render(Context({}))
|
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)
|
@override_settings(CLICKMAP_TRACKER_ID=None)
|
||||||
def test_no_site_id(self):
|
def test_no_site_id(self):
|
||||||
self.assertRaises(AnalyticalException, ClickmapNode)
|
with pytest.raises(AnalyticalException):
|
||||||
|
ClickmapNode()
|
||||||
|
|
||||||
@override_settings(CLICKMAP_TRACKER_ID='ab#c')
|
@override_settings(CLICKMAP_TRACKER_ID='ab#c')
|
||||||
def test_wrong_site_id(self):
|
def test_wrong_site_id(self):
|
||||||
self.assertRaises(AnalyticalException, ClickmapNode)
|
with pytest.raises(AnalyticalException):
|
||||||
|
ClickmapNode()
|
||||||
|
|
||||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||||
def test_render_internal_ip(self):
|
def test_render_internal_ip(self):
|
||||||
|
|
@ -39,6 +42,5 @@ class ClickmapTagTestCase(TagTestCase):
|
||||||
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
req.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||||
context = Context({'request': req})
|
context = Context({'request': req})
|
||||||
r = ClickmapNode().render(context)
|
r = ClickmapNode().render(context)
|
||||||
self.assertTrue(r.startswith(
|
assert r.startswith('<!-- Clickmap disabled on internal IP address')
|
||||||
'<!-- Clickmap disabled on internal IP address'), r)
|
assert r.endswith('-->')
|
||||||
self.assertTrue(r.endswith('-->'), r)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue