summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/http/response.py28
-rw-r--r--docs/ref/request-response.txt27
-rw-r--r--docs/releases/1.8.txt4
-rw-r--r--tests/httpwrappers/tests.py12
-rw-r--r--tests/responses/tests.py25
5 files changed, 93 insertions, 3 deletions
diff --git a/django/http/response.py b/django/http/response.py
index 0cc14d1346..9e8280a307 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -112,6 +112,7 @@ class HttpResponseBase(six.Iterator):
# historical behavior of request_finished.
self._handler_class = None
self.cookies = SimpleCookie()
+ self.closed = False
if status is not None:
self.status_code = status
if reason is not None:
@@ -313,16 +314,26 @@ class HttpResponseBase(six.Iterator):
closable.close()
except Exception:
pass
+ self.closed = True
signals.request_finished.send(sender=self._handler_class)
def write(self, content):
- raise Exception("This %s instance is not writable" % self.__class__.__name__)
+ raise IOError("This %s instance is not writable" % self.__class__.__name__)
def flush(self):
pass
def tell(self):
- raise Exception("This %s instance cannot tell its position" % self.__class__.__name__)
+ raise IOError("This %s instance cannot tell its position" % self.__class__.__name__)
+
+ # These methods partially implement a stream-like object interface.
+ # See https://docs.python.org/library/io.html#io.IOBase
+
+ def writable(self):
+ return False
+
+ def writelines(self, lines):
+ raise IOError("This %s instance is not writable" % self.__class__.__name__)
class HttpResponse(HttpResponseBase):
@@ -373,6 +384,16 @@ class HttpResponse(HttpResponseBase):
def tell(self):
return len(self.content)
+ def getvalue(self):
+ return self.content
+
+ def writable(self):
+ return True
+
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+
class StreamingHttpResponse(HttpResponseBase):
"""
@@ -410,6 +431,9 @@ class StreamingHttpResponse(HttpResponseBase):
def __iter__(self):
return self.streaming_content
+ def getvalue(self):
+ return b''.join(self.streaming_content)
+
class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index 2a43756889..a86f416620 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -651,6 +651,12 @@ Attributes
This attribute exists so middleware can treat streaming responses
differently from regular responses.
+.. attribute:: HttpResponse.closed
+
+ .. versionadded:: 1.8
+
+ ``True`` if the response has been closed.
+
Methods
-------
@@ -769,6 +775,27 @@ Methods
This method makes an :class:`HttpResponse` instance a file-like object.
+.. method:: HttpResponse.getvalue()
+
+ .. versionadded:: 1.8
+
+ Returns the value of :attr:`HttpResponse.content`. This method makes
+ an :class:`HttpResponse` instance a stream-like object.
+
+.. method:: HttpResponse.writable()
+
+ .. versionadded:: 1.8
+
+ Always ``True``. This method makes an :class:`HttpResponse` instance a
+ stream-like object.
+
+.. method:: HttpResponse.writelines(lines)
+
+ .. versionadded:: 1.8
+
+ Writes a list of lines to the response. Line seperators are not added. This
+ method makes an :class:`HttpResponse` instance a stream-like object.
+
.. _HTTP status code: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
.. _ref-httpresponse-subclasses:
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 492fad478f..00a2c9844c 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -385,6 +385,10 @@ Requests and Responses
<django.http.HttpRequest.get_full_path>` method now escapes unsafe characters
from the path portion of a Uniform Resource Identifier (URI) properly.
+* :class:`~django.http.HttpResponse` now implements a few additional methods
+ like :meth:`~django.http.HttpResponse.getvalue` so that instances can be used
+ as stream objects.
+
Tests
^^^^^
diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py
index 9b9b19e180..4e705e2aeb 100644
--- a/tests/httpwrappers/tests.py
+++ b/tests/httpwrappers/tests.py
@@ -407,6 +407,15 @@ class HttpResponseTests(unittest.TestCase):
r.write(b'def')
self.assertEqual(r.content, b'abcdef')
+ def test_stream_interface(self):
+ r = HttpResponse('asdf')
+ self.assertEqual(r.getvalue(), b'asdf')
+
+ r = HttpResponse()
+ self.assertEqual(r.writable(), True)
+ r.writelines(['foo\n', 'bar\n', 'baz\n'])
+ self.assertEqual(r.content, b'foo\nbar\nbaz\n')
+
def test_unsafe_redirect(self):
bad_urls = [
'data:text/html,<script>window.alert("xss")</script>',
@@ -537,6 +546,9 @@ class StreamingHttpResponseTests(TestCase):
with self.assertRaises(Exception):
r.tell()
+ r = StreamingHttpResponse(iter(['hello', 'world']))
+ self.assertEqual(r.getvalue(), b'helloworld')
+
class FileCloseTests(TestCase):
diff --git a/tests/responses/tests.py b/tests/responses/tests.py
index e80e466a56..9642790b9a 100644
--- a/tests/responses/tests.py
+++ b/tests/responses/tests.py
@@ -4,14 +4,37 @@ from __future__ import unicode_literals
from django.conf import settings
from django.http import HttpResponse
+from django.http.response import HttpResponseBase
from django.test import SimpleTestCase
UTF8 = 'utf-8'
ISO88591 = 'iso-8859-1'
-class HttpResponseTests(SimpleTestCase):
+class HttpResponseBaseTests(SimpleTestCase):
+ def test_closed(self):
+ r = HttpResponseBase()
+ self.assertIs(r.closed, False)
+
+ r.close()
+ self.assertIs(r.closed, True)
+
+ def test_write(self):
+ r = HttpResponseBase()
+ self.assertIs(r.writable(), False)
+
+ with self.assertRaisesMessage(IOError, 'This HttpResponseBase instance is not writable'):
+ r.write('asdf')
+ with self.assertRaisesMessage(IOError, 'This HttpResponseBase instance is not writable'):
+ r.writelines(['asdf\n', 'qwer\n'])
+ def test_tell(self):
+ r = HttpResponseBase()
+ with self.assertRaisesMessage(IOError, 'This HttpResponseBase instance cannot tell its position'):
+ r.tell()
+
+
+class HttpResponseTests(SimpleTestCase):
def test_status_code(self):
resp = HttpResponse(status=418)
self.assertEqual(resp.status_code, 418)