Reworked implementation of the urlescape filter, made it Python3 compatible, document...
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 7 Jan 2012 16:46:40 +0000 (17:46 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 7 Jan 2012 16:46:40 +0000 (17:46 +0100)
CHANGES
jinja2/filters.py
jinja2/testsuite/filters.py
jinja2/utils.py

diff --git a/CHANGES b/CHANGES
index 7f3388d8ef01e5acb1e38df8907be161c6bfc2d1..8838a21542b0e1b2bc3adf04276b4c967fd82673 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -10,6 +10,9 @@ Version 2.7
   advertised.
 - Fixed filesizeformat.
 - Added a non-silent option for babel extraction.
+- Added `urlescape` filter that automatically quotes values for
+  URL safe usage with utf-8 as only supported encoding.  If applications
+  want to change this encoding they can override the filter.
 
 Version 2.6
 -----------
index 74f3113b687cd440f59fbf6dbcdba2bd3ce1ead0..bbe5a8cd82d190c6bd346fee1ed36bd1bd2d85ab 100644 (file)
 """
 import re
 import math
-import urllib
 from random import choice
 from operator import itemgetter
 from itertools import imap, groupby
-from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
+from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
+     unicode_urlescape
 from jinja2.runtime import Undefined
 from jinja2.exceptions import FilterArgumentError
 
@@ -72,22 +72,23 @@ def do_forceescape(value):
 
 
 def do_urlescape(value):
-    """Escape strings for use in URLs (uses UTF-8 encoding)."""
-    def utf8(o):
-        return unicode(o).encode('utf8')
+    """Escape strings for use in URLs (uses UTF-8 encoding).  It accepts both
+    dictionaries and regular strings as well as pairwise iterables.
 
-    if isinstance(value, basestring):
-        return urllib.quote(utf8(value))
-
-    if hasattr(value, 'items'):
-        # convert dictionaries to list of 2-tuples
-        value = value.items()
-
-    if hasattr(value, 'next'):
-        # convert generators to list
-        value = list(value)
-
-    return urllib.urlencode((utf8(k), utf8(v)) for (k, v) in value)
+    .. versionadded:: 2.7
+    """
+    itemiter = None
+    if isinstance(value, dict):
+        itemiter = value.iteritems()
+    elif not isinstance(value, basestring):
+        try:
+            itemiter = iter(value)
+        except TypeError:
+            pass
+    if itemiter is None:
+        return unicode_urlescape(value)
+    return u'&'.join(unicode_urlescape(k) + '=' +
+                     unicode_urlescape(v) for k, v in itemiter)
 
 
 @evalcontextfilter
index 59795be47ced2637153a2f5786534433fcbca4c6..15451e5948a3e73c52a0ce1b5233631514820232 100644 (file)
@@ -366,7 +366,7 @@ class FilterTestCase(JinjaTestCase):
         assert tmpl.render() == '<div>foo</div>'
         tmpl = env.from_string('{{ "<div>foo</div>" }}')
         assert tmpl.render() == '&lt;div&gt;foo&lt;/div&gt;'
-    
+
     def test_urlescape(self):
         env = Environment(autoescape=True)
         tmpl = env.from_string('{{ "Hello, world!"|urlescape }}')
@@ -377,6 +377,8 @@ class FilterTestCase(JinjaTestCase):
         assert tmpl.render(o=(('f', 1), ("z", 2))) == "f=1&amp;z=2"
         assert tmpl.render(o=((u"\u203d", 1),)) == "%E2%80%BD=1"
         assert tmpl.render(o={u"\u203d": 1}) == "%E2%80%BD=1"
+        assert tmpl.render(o={0: 1}) == "0=1"
+
 
 def suite():
     suite = unittest.TestSuite()
index 49e9e9ae0737dd5876b1ca67d8f00185dcad0370..1554a143889da9dc2c0699ed6c56d3d5bd1d79b0 100644 (file)
 import re
 import sys
 import errno
+try:
+    from urllib.parse import quote_from_bytes as url_quote
+except ImportError:
+    from urllib import quote as url_quote
 try:
     from thread import allocate_lock
 except ImportError:
@@ -349,6 +353,21 @@ def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
     return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
 
 
+def unicode_urlescape(obj, charset='utf-8'):
+    """URL escapes a single bytestring or unicode string with the
+    given charset if applicable to URL safe quoting under all rules
+    that need to be considered under all supported Python versions.
+
+    If non strings are provided they are converted to their unicode
+    representation first.
+    """
+    if not isinstance(obj, basestring):
+        obj = unicode(obj)
+    if isinstance(obj, unicode):
+        obj = obj.encode(charset)
+    return unicode(url_quote(obj))
+
+
 class LRUCache(object):
     """A simple LRU Cache implementation."""