From cd9394467e0149a5b0e1be2e1b68019cb0105f53 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Tue, 17 Oct 2017 16:00:54 +0200 Subject: [PATCH] Add initial Facebook Pixel support --- analytical/templatetags/analytical.py | 1 + analytical/templatetags/facebook_pixel.py | 97 +++++++++++++++++++++ analytical/tests/test_tag_facebook_pixel.py | 87 ++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 analytical/templatetags/facebook_pixel.py create mode 100644 analytical/tests/test_tag_facebook_pixel.py diff --git a/analytical/templatetags/analytical.py b/analytical/templatetags/analytical.py index ad4fba7..7851268 100644 --- a/analytical/templatetags/analytical.py +++ b/analytical/templatetags/analytical.py @@ -20,6 +20,7 @@ TAG_MODULES = [ 'analytical.clickmap', 'analytical.clicky', 'analytical.crazy_egg', + 'analytical.facebook_pixel', 'analytical.gauges', 'analytical.google_analytics', 'analytical.gosquared', diff --git a/analytical/templatetags/facebook_pixel.py b/analytical/templatetags/facebook_pixel.py new file mode 100644 index 0000000..c855784 --- /dev/null +++ b/analytical/templatetags/facebook_pixel.py @@ -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 = """\ + +""" + +FACEBOOK_PIXEL_BODY_CODE = """\ + +""" + +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) diff --git a/analytical/tests/test_tag_facebook_pixel.py b/analytical/tests/test_tag_facebook_pixel.py new file mode 100644 index 0000000..373c603 --- /dev/null +++ b/analytical/tests/test_tag_facebook_pixel.py @@ -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 = """\ + +""" + + +expected_body_html = """\ + +""" + + +@override_settings(FACEBOOK_PIXEL_ID='1234567890') +class FacebookPixelTagTestCase(TagTestCase): + + maxDiff = None + + def test_head_tag(self): + html = self.render_tag('facebook_pixel', 'facebook_pixel_head') + self.assertEqual(expected_head_html, html) + + 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([ + '', + ]) + + 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)