Merge pull request #115 from pjdelport/add-facebook-pixel-support

Add Facebook Pixel support
This commit is contained in:
Joost Cassee 2017-10-18 21:06:52 +02:00 committed by GitHub
commit 6f71ddf04f
6 changed files with 302 additions and 0 deletions

View file

@ -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/

View file

@ -20,6 +20,7 @@ TAG_MODULES = [
'analytical.clickmap',
'analytical.clicky',
'analytical.crazy_egg',
'analytical.facebook_pixel',
'analytical.gauges',
'analytical.google_analytics',
'analytical.gosquared',

View 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)

View 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)

View file

@ -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'

View 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.