Add initial Facebook Pixel support

This commit is contained in:
Pi Delport 2017-10-17 16:00:54 +02:00
parent c7b14bdf5c
commit cd9394467e
3 changed files with 185 additions and 0 deletions

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
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,87 @@
"""
Tests for the Facebook Pixel template tags.
"""
from django.http import HttpRequest
from django.template import Context
from django.test import override_settings
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)
@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)