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)