From dceaa82dec9f97ac77754dfdc737852d8171c8a2 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Wed, 1 Dec 2010 22:25:17 +0000 Subject: [PATCH] Fixed #14809 -- broken login related tests after r14733. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14764 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/views.py | 13 ++++++----- django/contrib/auth/views.py | 2 +- django/http/__init__.py | 25 +++++++++++++++++--- docs/ref/request-response.txt | 26 ++++++++++++++++----- tests/regressiontests/httpwrappers/tests.py | 10 ++++++++ 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()