From 4361b9aacd7b1d1f080fc7b154dff725ef220508 Mon Sep 17 00:00:00 2001 From: James Socol Date: Fri, 9 Nov 2012 14:41:57 -0500 Subject: [PATCH] Include monkeypatch from nuggets. Fix #11. --- README.rst | 9 ++++ docs/index.rst | 9 ++++ jingo/monkey.py | 85 ++++++++++++++++++++++++++++++++++++++ jingo/tests/test_monkey.py | 24 +++++++++++ jingo/tests/urls.py | 1 + 5 files changed, 128 insertions(+) create mode 100644 jingo/monkey.py create mode 100644 jingo/tests/test_monkey.py diff --git a/README.rst b/README.rst index 21dc190..a17d8d0 100644 --- a/README.rst +++ b/README.rst @@ -152,6 +152,15 @@ The other method uses Jinja's ``trans`` tag:: directly. Both methods are useful, pick the one that makes you happy. +Forms +----- + +Django marks its form HTML "safe" according to its own rules, which Jinja2 does +not recognize. + +.. automodule:: jingo.monkey + + Testing ------- diff --git a/docs/index.rst b/docs/index.rst index 9fcbf87..25b8f99 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -152,6 +152,15 @@ The other method uses Jinja's ``trans`` tag:: directly. Both methods are useful, pick the one that makes you happy. +Forms +----- + +Django marks its form HTML "safe" according to its own rules, which Jinja2 does +not recognize. + +.. automodule:: jingo.monkey + + Testing ------- diff --git a/jingo/monkey.py b/jingo/monkey.py new file mode 100644 index 0000000..3daaee3 --- /dev/null +++ b/jingo/monkey.py @@ -0,0 +1,85 @@ +""" +This monkeypatches Django to support the __html__ protocol used in Jinja +templates. Form, BoundField, ErrorList, and other form objects that +render HTML through their __unicode__ method are extended with __html__ +so they can be rendered in Jinja templates without adding |safe. + +Call the patch() function to execute the patch. It must be called +before django.forms is imported for the conditional_escape patch to work +properly. The root URLconf is the recommended location for calling patch(). + +Usage:: + + import jingo.monkey + jingo.monkey.patch() + +This patch was originally developed by Jeff Balogh and this version is taken +from the nuggets project at +https://github.com/mozilla/nuggets/blob/master/safe_django_forms.py + +""" +import django.utils.encoding +import django.utils.html +import django.utils.safestring + + +# This function gets directly imported within Django, so this change needs to +# happen before too many Django imports happen. +def conditional_escape(html): + """ + Similar to escape(), except that it doesn't operate on pre-escaped strings. + """ + if hasattr(html, '__html__'): + return html.__html__() + elif isinstance(html, django.utils.safestring.SafeData): + return html + return django.utils.html.escape(html) + + +# Django uses SafeData to mark a string that has already been escaped or +# otherwise deemed safe. This __html__ method lets Jinja know about that too. +def __html__(self): + """ + Returns the html representation of a string. + + Allows interoperability with other template engines. + """ + return self + + +# Django uses StrAndUnicode for classes like Form, BoundField, Widget which +# have a __unicode__ method which returns escaped html. We replace +# StrAndUnicode with SafeStrAndUnicode to get the __html__ method. +class SafeStrAndUnicode(django.utils.encoding.StrAndUnicode): + """A class whose __str__ and __html__ returns __unicode__.""" + + def __html__(self): + return unicode(self) + + +def patch(): + django.utils.html.conditional_escape = conditional_escape + django.utils.safestring.SafeData.__html__ = __html__ + + # forms imports have to come after we patch conditional_escape. + from django.forms import forms, formsets, util, widgets + + # Replace StrAndUnicode with SafeStrAndUnicode in the inheritance + # for all these classes. + classes = ( + forms.BaseForm, + forms.BoundField, + formsets.BaseFormSet, + util.ErrorDict, + util.ErrorList, + widgets.Media, + widgets.RadioInput, + widgets.RadioFieldRenderer, + ) + + for cls in classes: + bases = list(cls.__bases__) + if django.utils.encoding.StrAndUnicode in bases: + idx = bases.index(django.utils.encoding.StrAndUnicode) + bases[idx] = SafeStrAndUnicode + cls.__bases__ = tuple(bases) diff --git a/jingo/tests/test_monkey.py b/jingo/tests/test_monkey.py new file mode 100644 index 0000000..ef662cc --- /dev/null +++ b/jingo/tests/test_monkey.py @@ -0,0 +1,24 @@ +from django import forms + +from jinja2 import escape +from nose.tools import eq_ + +import jingo +import jingo.monkey +from test_helpers import render + + +class MyForm(forms.Form): + email = forms.EmailField() + + +def test_monkey_patch(): + form = MyForm() + html = form.as_ul() + context = {'form': form} + t = '{{ form.as_ul() }}' + + eq_(escape(html), render(t, context)) + + jingo.monkey.patch() + eq_(html, render(t, context)) diff --git a/jingo/tests/urls.py b/jingo/tests/urls.py index e0e4cba..4a4bfa1 100644 --- a/jingo/tests/urls.py +++ b/jingo/tests/urls.py @@ -1,5 +1,6 @@ from django.conf.urls.defaults import patterns + urlpatterns = patterns('', (r'^url/(\d+)/(\w+)/$', lambda r: None, {}, "url-args"), (r'^url/(?P\d+)/(?P\w+)/$', lambda r: None, {}, "url-kwargs"),