diff --git a/AUTHORS b/AUTHORS index 9300c07a4f..e41a4aa4db 100644 --- a/AUTHORS +++ b/AUTHORS @@ -317,6 +317,7 @@ answer newbie questions, and generally made Django that much better: Michael Josephson jpellerin@gmail.com junzhang.jn@gmail.com + Krzysztof Jurewicz Xia Kai Antti Kaihola Peter van Kampen diff --git a/django/test/client.py b/django/test/client.py index ae7d7a5fb7..3c58eae4b5 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -37,6 +37,7 @@ BOUNDARY = 'BoUnDaRyStRiNg' MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY CONTENT_TYPE_RE = re.compile('.*; charset=([\w\d-]+);?') + class FakePayload(object): """ A wrapper around BytesIO that restricts what can be read since data from @@ -123,6 +124,7 @@ class ClientHandler(BaseHandler): return response + def store_rendered_templates(store, signal, sender, template, context, **kwargs): """ Stores templates and contexts that are rendered. @@ -133,6 +135,7 @@ def store_rendered_templates(store, signal, sender, template, context, **kwargs) store.setdefault('templates', []).append(template) store.setdefault('context', ContextList()).append(copy(context)) + def encode_multipart(boundary, data): """ Encodes multipart POST data from a dictionary of form values. @@ -178,6 +181,7 @@ def encode_multipart(boundary, data): ]) return b'\r\n'.join(lines) + def encode_file(boundary, key, file): to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET) if hasattr(file, 'content_type'): @@ -189,8 +193,8 @@ def encode_file(boundary, key, file): content_type = 'application/octet-stream' return [ to_bytes('--%s' % boundary), - to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' \ - % (key, os.path.basename(file.name))), + to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' + % (key, os.path.basename(file.name))), to_bytes('Content-Type: %s' % content_type), b'', file.read() @@ -274,14 +278,11 @@ class RequestFactory(object): def get(self, path, data={}, **extra): "Construct a GET request." - parsed = urlparse(path) r = { - 'PATH_INFO': self._get_path(parsed), - 'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]), - 'REQUEST_METHOD': str('GET'), + 'QUERY_STRING': urlencode(data, doseq=True), } r.update(extra) - return self.request(**r) + return self.generic('GET', path, **r) def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): @@ -289,32 +290,19 @@ class RequestFactory(object): post_data = self._encode_data(data, content_type) - parsed = urlparse(path) - r = { - 'CONTENT_LENGTH': len(post_data), - 'CONTENT_TYPE': content_type, - 'PATH_INFO': self._get_path(parsed), - 'QUERY_STRING': force_str(parsed[4]), - 'REQUEST_METHOD': str('POST'), - 'wsgi.input': FakePayload(post_data), - } - r.update(extra) - return self.request(**r) + return self.generic('POST', path, post_data, content_type, **extra) def head(self, path, data={}, **extra): "Construct a HEAD request." - parsed = urlparse(path) r = { - 'PATH_INFO': self._get_path(parsed), - 'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]), - 'REQUEST_METHOD': str('HEAD'), + 'QUERY_STRING': urlencode(data, doseq=True), } r.update(extra) - return self.request(**r) + return self.generic('HEAD', path, **r) def options(self, path, data='', content_type='application/octet-stream', - **extra): + **extra): "Construct an OPTIONS request." return self.generic('OPTIONS', path, data, content_type, **extra) @@ -324,22 +312,22 @@ class RequestFactory(object): return self.generic('PUT', path, data, content_type, **extra) def patch(self, path, data='', content_type='application/octet-stream', - **extra): + **extra): "Construct a PATCH request." return self.generic('PATCH', path, data, content_type, **extra) def delete(self, path, data='', content_type='application/octet-stream', - **extra): + **extra): "Construct a DELETE request." return self.generic('DELETE', path, data, content_type, **extra) def generic(self, method, path, data='', content_type='application/octet-stream', **extra): + """Constructs an arbitrary HTTP request.""" parsed = urlparse(path) data = force_bytes(data, settings.DEFAULT_CHARSET) r = { 'PATH_INFO': self._get_path(parsed), - 'QUERY_STRING': force_str(parsed[4]), 'REQUEST_METHOD': str(method), } if data: @@ -349,8 +337,12 @@ class RequestFactory(object): 'wsgi.input': FakePayload(data), }) r.update(extra) + # If QUERY_STRING is absent or empty, we want to extract it from the URL. + if not r.get('QUERY_STRING'): + r['QUERY_STRING'] = force_str(parsed[4]) return self.request(**r) + class Client(RequestFactory): """ A class that can act as a client for testing purposes. @@ -392,7 +384,6 @@ class Client(RequestFactory): return {} session = property(_session) - def request(self, **request): """ The master request method. Composes the environment dictionary @@ -485,12 +476,11 @@ class Client(RequestFactory): return response def options(self, path, data='', content_type='application/octet-stream', - follow=False, **extra): + follow=False, **extra): """ Request a response from the server using OPTIONS. """ - response = super(Client, self).options(path, - data=data, content_type=content_type, **extra) + response = super(Client, self).options(path, data=data, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) return response @@ -500,14 +490,13 @@ class Client(RequestFactory): """ Send a resource to the server using PUT. """ - response = super(Client, self).put(path, - data=data, content_type=content_type, **extra) + response = super(Client, self).put(path, data=data, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) return response def patch(self, path, data='', content_type='application/octet-stream', - follow=False, **extra): + follow=False, **extra): """ Send a resource to the server using PATCH. """ @@ -518,12 +507,12 @@ class Client(RequestFactory): return response def delete(self, path, data='', content_type='application/octet-stream', - follow=False, **extra): + follow=False, **extra): """ Send a DELETE request to the server. """ - response = super(Client, self).delete(path, - data=data, content_type=content_type, **extra) + response = super(Client, self).delete( + path, data=data, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) return response