mirror of
https://github.com/jazzband/django-analytical.git
synced 2026-03-16 22:20:25 +00:00
Merge pull request #115 from pjdelport/add-facebook-pixel-support
Add Facebook Pixel support
This commit is contained in:
commit
6f71ddf04f
6 changed files with 302 additions and 0 deletions
|
|
@ -53,6 +53,7 @@ Currently Supported Services
|
|||
* `Clickmap`_ visual click tracking
|
||||
* `Clicky`_ traffic analysis
|
||||
* `Crazy Egg`_ visual click tracking
|
||||
* `Facebook Pixel`_ advertising analytics
|
||||
* `Gaug.es`_ real time web analytics
|
||||
* `Google Analytics`_ traffic analysis
|
||||
* `GoSquared`_ traffic monitoring
|
||||
|
|
@ -76,6 +77,7 @@ Currently Supported Services
|
|||
.. _`Clickmap`: http://getclickmap.com/
|
||||
.. _`Clicky`: http://getclicky.com/
|
||||
.. _`Crazy Egg`: http://www.crazyegg.com/
|
||||
.. _`Facebook Pixel`: https://developers.facebook.com/docs/facebook-pixel/
|
||||
.. _`Gaug.es`: http://get.gaug.es/
|
||||
.. _`Google Analytics`: http://www.google.com/analytics/
|
||||
.. _`GoSquared`: http://www.gosquared.com/
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ TAG_MODULES = [
|
|||
'analytical.clickmap',
|
||||
'analytical.clicky',
|
||||
'analytical.crazy_egg',
|
||||
'analytical.facebook_pixel',
|
||||
'analytical.gauges',
|
||||
'analytical.google_analytics',
|
||||
'analytical.gosquared',
|
||||
|
|
|
|||
97
analytical/templatetags/facebook_pixel.py
Normal file
97
analytical/templatetags/facebook_pixel.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
Facebook Pixel template tags and filters.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
|
||||
from analytical.utils import get_required_setting, is_internal_ip, disable_html
|
||||
|
||||
|
||||
FACEBOOK_PIXEL_HEAD_CODE = """\
|
||||
<script>
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '%(FACEBOOK_PIXEL_ID)s');
|
||||
fbq('track', 'PageView');
|
||||
</script>
|
||||
"""
|
||||
|
||||
FACEBOOK_PIXEL_BODY_CODE = """\
|
||||
<noscript><img height="1" width="1" style="display:none"
|
||||
src="https://www.facebook.com/tr?id=%(FACEBOOK_PIXEL_ID)s&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
"""
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
def _validate_no_args(token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) > 1:
|
||||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0])
|
||||
|
||||
|
||||
@register.tag
|
||||
def facebook_pixel_head(parser, token):
|
||||
"""
|
||||
Facebook Pixel head template tag.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return FacebookPixelHeadNode()
|
||||
|
||||
|
||||
@register.tag
|
||||
def facebook_pixel_body(parser, token):
|
||||
"""
|
||||
Facebook Pixel body template tag.
|
||||
"""
|
||||
_validate_no_args(token)
|
||||
return FacebookPixelBodyNode()
|
||||
|
||||
|
||||
class _FacebookPixelNode(Node):
|
||||
"""
|
||||
Base class: override and provide code_template.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.pixel_id = get_required_setting(
|
||||
'FACEBOOK_PIXEL_ID',
|
||||
re.compile(r'^\d+$'),
|
||||
"must be (a string containing) a number",
|
||||
)
|
||||
|
||||
def render(self, context):
|
||||
html = self.code_template % {'FACEBOOK_PIXEL_ID': self.pixel_id}
|
||||
if is_internal_ip(context, 'FACEBOOK_PIXEL'):
|
||||
return disable_html(html, 'Facebook Pixel')
|
||||
else:
|
||||
return html
|
||||
|
||||
@property
|
||||
def code_template(self):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
|
||||
class FacebookPixelHeadNode(_FacebookPixelNode):
|
||||
code_template = FACEBOOK_PIXEL_HEAD_CODE
|
||||
|
||||
|
||||
class FacebookPixelBodyNode(_FacebookPixelNode):
|
||||
code_template = FACEBOOK_PIXEL_BODY_CODE
|
||||
|
||||
|
||||
def contribute_to_analytical(add_node):
|
||||
# ensure properly configured
|
||||
FacebookPixelHeadNode()
|
||||
FacebookPixelBodyNode()
|
||||
add_node('head_bottom', FacebookPixelHeadNode)
|
||||
add_node('body_bottom', FacebookPixelBodyNode)
|
||||
114
analytical/tests/test_tag_facebook_pixel.py
Normal file
114
analytical/tests/test_tag_facebook_pixel.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"""
|
||||
Tests for the Facebook Pixel template tags.
|
||||
"""
|
||||
from django.http import HttpRequest
|
||||
from django.template import Context, Template, TemplateSyntaxError
|
||||
from django.test import override_settings
|
||||
|
||||
from analytical.templatetags.analytical import _load_template_nodes
|
||||
from analytical.templatetags.facebook_pixel import FacebookPixelHeadNode, FacebookPixelBodyNode
|
||||
from analytical.tests.utils import TagTestCase
|
||||
from analytical.utils import AnalyticalException
|
||||
|
||||
|
||||
expected_head_html = """\
|
||||
<script>
|
||||
!function(f,b,e,v,n,t,s)
|
||||
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
||||
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
||||
n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];
|
||||
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
||||
'https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', '1234567890');
|
||||
fbq('track', 'PageView');
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
expected_body_html = """\
|
||||
<noscript><img height="1" width="1" style="display:none"
|
||||
src="https://www.facebook.com/tr?id=1234567890&ev=PageView&noscript=1"
|
||||
/></noscript>
|
||||
"""
|
||||
|
||||
|
||||
@override_settings(FACEBOOK_PIXEL_ID='1234567890')
|
||||
class FacebookPixelTagTestCase(TagTestCase):
|
||||
|
||||
maxDiff = None
|
||||
|
||||
def test_head_tag(self):
|
||||
html = self.render_tag('facebook_pixel', 'facebook_pixel_head')
|
||||
self.assertEqual(expected_head_html, html)
|
||||
|
||||
def test_head_node(self):
|
||||
html = FacebookPixelHeadNode().render(Context({}))
|
||||
self.assertEqual(expected_head_html, html)
|
||||
|
||||
def test_body_tag(self):
|
||||
html = self.render_tag('facebook_pixel', 'facebook_pixel_body')
|
||||
self.assertEqual(expected_body_html, html)
|
||||
|
||||
def test_body_node(self):
|
||||
html = FacebookPixelBodyNode().render(Context({}))
|
||||
self.assertEqual(expected_body_html, html)
|
||||
|
||||
def test_tags_take_no_args(self):
|
||||
self.assertRaisesRegexp(
|
||||
TemplateSyntaxError,
|
||||
r"^'facebook_pixel_head' takes no arguments$",
|
||||
lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_head "arg" %}')
|
||||
.render(Context({}))),
|
||||
)
|
||||
self.assertRaisesRegexp(
|
||||
TemplateSyntaxError,
|
||||
r"^'facebook_pixel_body' takes no arguments$",
|
||||
lambda: (Template('{% load facebook_pixel %}{% facebook_pixel_body "arg" %}')
|
||||
.render(Context({}))),
|
||||
)
|
||||
|
||||
@override_settings(FACEBOOK_PIXEL_ID=None)
|
||||
def test_no_id(self):
|
||||
expected_pattern = r'^FACEBOOK_PIXEL_ID setting is not set$'
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelHeadNode)
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelBodyNode)
|
||||
|
||||
@override_settings(FACEBOOK_PIXEL_ID='invalid')
|
||||
def test_invalid_id(self):
|
||||
expected_pattern = (
|
||||
r"^FACEBOOK_PIXEL_ID setting: must be \(a string containing\) a number: 'invalid'$")
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelHeadNode)
|
||||
self.assertRaisesRegexp(AnalyticalException, expected_pattern, FacebookPixelBodyNode)
|
||||
|
||||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1'])
|
||||
def test_render_internal_ip(self):
|
||||
request = HttpRequest()
|
||||
request.META['REMOTE_ADDR'] = '1.1.1.1'
|
||||
context = Context({'request': request})
|
||||
|
||||
def _disabled(html):
|
||||
return '\n'.join([
|
||||
'<!-- Facebook Pixel disabled on internal IP address',
|
||||
html,
|
||||
'-->',
|
||||
])
|
||||
|
||||
head_html = FacebookPixelHeadNode().render(context)
|
||||
self.assertEqual(_disabled(expected_head_html), head_html)
|
||||
|
||||
body_html = FacebookPixelBodyNode().render(context)
|
||||
self.assertEqual(_disabled(expected_body_html), body_html)
|
||||
|
||||
def test_contribute_to_analytical(self):
|
||||
"""
|
||||
`facebook_pixel.contribute_to_analytical` registers the head and body nodes.
|
||||
"""
|
||||
template_nodes = _load_template_nodes()
|
||||
self.assertEqual({
|
||||
'head_top': [],
|
||||
'head_bottom': [FacebookPixelHeadNode],
|
||||
'body_top': [],
|
||||
'body_bottom': [FacebookPixelBodyNode],
|
||||
}, template_nodes)
|
||||
|
|
@ -125,6 +125,10 @@ settings required to enable each service are listed here:
|
|||
|
||||
CRAZY_EGG_ACCOUNT_NUMBER = '12345678'
|
||||
|
||||
* :doc:`Facebook Pixel <services/facebook_pixel>`::
|
||||
|
||||
FACEBOOK_PIXEL_ID = '1234567890'
|
||||
|
||||
* :doc:`Gaug.es <services/gauges>`::
|
||||
|
||||
GAUGES_SITE_ID = '0123456789abcdef0123456789abcdef'
|
||||
|
|
|
|||
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.
|
||||
Loading…
Reference in a new issue