summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/auth/tests/views.py13
-rw-r--r--django/contrib/auth/views.py2
-rw-r--r--django/http/__init__.py25
-rw-r--r--docs/ref/request-response.txt26
-rw-r--r--tests/regressiontests/httpwrappers/tests.py10
5 files changed, 60 insertions, 16 deletions
diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
index 88867d538c..014c8195fc 100644
--- a/django/contrib/auth/tests/views.py
+++ b/django/contrib/auth/tests/views.py
@@ -265,7 +265,7 @@ class LoginURLSettings(AuthViewsTestCase):
querystring = QueryDict('', mutable=True)
querystring['next'] = '/login_required/'
self.assertEqual(login_required_url,
- 'http://testserver%s?%s' % (login_url, querystring.urlencode()))
+ 'http://testserver%s?%s' % (login_url, querystring.urlencode('/')))
def test_remote_login_url(self):
login_url = 'http://remote.example.com/login'
@@ -273,7 +273,7 @@ class LoginURLSettings(AuthViewsTestCase):
querystring = QueryDict('', mutable=True)
querystring['next'] = 'http://testserver/login_required/'
self.assertEqual(login_required_url,
- '%s?%s' % (login_url, querystring.urlencode()))
+ '%s?%s' % (login_url, querystring.urlencode('/')))
def test_https_login_url(self):
login_url = 'https:///login/'
@@ -281,7 +281,7 @@ class LoginURLSettings(AuthViewsTestCase):
querystring = QueryDict('', mutable=True)
querystring['next'] = 'http://testserver/login_required/'
self.assertEqual(login_required_url,
- '%s?%s' % (login_url, querystring.urlencode()))
+ '%s?%s' % (login_url, querystring.urlencode('/')))
def test_login_url_with_querystring(self):
login_url = '/login/?pretty=1'
@@ -289,7 +289,7 @@ class LoginURLSettings(AuthViewsTestCase):
querystring = QueryDict('pretty=1', mutable=True)
querystring['next'] = '/login_required/'
self.assertEqual(login_required_url, 'http://testserver/login/?%s' %
- querystring.urlencode())
+ querystring.urlencode('/'))
def test_remote_login_url_with_next_querystring(self):
login_url = 'http://remote.example.com/login/'
@@ -298,8 +298,9 @@ class LoginURLSettings(AuthViewsTestCase):
querystring = QueryDict('', mutable=True)
querystring['next'] = 'http://testserver/login_required/'
self.assertEqual(login_required_url, '%s?%s' % (login_url,
- querystring.urlencode()))
-
+ querystring.urlencode('/')))
+
+
class LogoutTest(AuthViewsTestCase):
urls = 'django.contrib.auth.tests.urls'
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index 42ad68d3c3..0325643c11 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -99,7 +99,7 @@ def redirect_to_login(next, login_url=None,
if redirect_field_name:
querystring = QueryDict(login_url_parts[4], mutable=True)
querystring[redirect_field_name] = next
- login_url_parts[4] = querystring.urlencode()
+ login_url_parts[4] = querystring.urlencode(safe='/')
return HttpResponseRedirect(urlparse.urlunparse(login_url_parts))
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 42027f0beb..90c55c6319 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -3,7 +3,7 @@ import os
import re
import time
from pprint import pformat
-from urllib import urlencode
+from urllib import urlencode, quote
from urlparse import urljoin
try:
from cStringIO import StringIO
@@ -363,11 +363,30 @@ class QueryDict(MultiValueDict):
"""Returns a mutable copy of this object."""
return self.__deepcopy__({})
- def urlencode(self):
+ def urlencode(self, safe=None):
+ """
+ Returns an encoded string of all query string arguments.
+
+ :arg safe: Used to specify characters which do not require quoting, for
+ example::
+
+ >>> q = QueryDict('', mutable=True)
+ >>> q['next'] = '/a&b/'
+ >>> q.urlencode()
+ 'next=%2Fa%26b%2F'
+ >>> q.urlencode(safe='/')
+ 'next=/a%26b/'
+
+ """
output = []
+ if safe:
+ encode = lambda k, v: '%s=%s' % ((quote(k, safe), quote(v, safe)))
+ else:
+ encode = lambda k, v: urlencode({k: v})
for k, list_ in self.lists():
k = smart_str(k, self.encoding)
- output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_])
+ output.extend([encode(k, smart_str(v, self.encoding))
+ for v in list_])
return '&'.join(output)
class CompatCookie(SimpleCookie):
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index cc89229725..7ea7643c75 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -430,14 +430,28 @@ In addition, ``QueryDict`` has the following methods:
Like :meth:`items()`, except it includes all values, as a list, for each
member of the dictionary. For example::
- >>> q = QueryDict('a=1&a=2&a=3')
- >>> q.lists()
- [(u'a', [u'1', u'2', u'3'])]
+ >>> q = QueryDict('a=1&a=2&a=3')
+ >>> q.lists()
+ [(u'a', [u'1', u'2', u'3'])]
-.. method:: QueryDict.urlencode()
+.. method:: QueryDict.urlencode([safe])
- Returns a string of the data in query-string format.
- Example: ``"a=2&b=3&b=5"``.
+ Returns a string of the data in query-string format. Example::
+
+ >>> q = QueryDict('a=2&b=3&b=5')
+ >>> q.urlencode()
+ 'a=2&b=3&b=5'
+
+ .. versionchanged:: 1.3
+ The ``safe`` parameter was added.
+
+ Optionally, urlencode can be passed characters which
+ do not require encoding. For example::
+
+ >>> q = QueryDict('', mutable=True)
+ >>> q['next'] = '/a&b/'
+ >>> q.urlencode(safe='/')
+ 'next=/a%26b/'
HttpResponse objects
====================
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index 6d452524fe..65b06a1d77 100644
--- a/tests/regressiontests/httpwrappers/tests.py
+++ b/tests/regressiontests/httpwrappers/tests.py
@@ -71,6 +71,16 @@ class QueryDictTests(unittest.TestCase):
self.assertEqual(q.urlencode(), 'foo=bar')
+ def test_urlencode(self):
+ q = QueryDict('', mutable=True)
+ q['next'] = '/a&b/'
+ self.assertEqual(q.urlencode(), 'next=%2Fa%26b%2F')
+ self.assertEqual(q.urlencode(safe='/'), 'next=/a%26b/')
+ q = QueryDict('', mutable=True)
+ q['next'] = u'/t\xebst&key/'
+ self.assertEqual(q.urlencode(), 'next=%2Ft%C3%ABst%26key%2F')
+ self.assertEqual(q.urlencode(safe='/'), 'next=/t%C3%ABst%26key/')
+
def test_mutable_copy(self):
"""A copy of a QueryDict is mutable."""
q = QueryDict('').copy()