diff --git a/django/http/__init__.py b/django/http/__init__.py index 414440b9d9..93142d9200 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -4,6 +4,8 @@ import datetime import os import re import time +import warnings + from pprint import pformat from urllib import urlencode, quote from urlparse import urljoin @@ -315,14 +317,19 @@ class HttpRequest(object): parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) return parser.parse() - def _get_raw_post_data(self): - if not hasattr(self, '_raw_post_data'): + @property + def body(self): + if not hasattr(self, '_body'): if self._read_started: - raise Exception("You cannot access raw_post_data after reading from request's data stream") - self._raw_post_data = self.read() - self._stream = StringIO(self._raw_post_data) - return self._raw_post_data - raw_post_data = property(_get_raw_post_data) + raise Exception("You cannot access body after reading from request's data stream") + self._body = self.read() + self._stream = StringIO(self._body) + return self._body + + @property + def raw_post_data(self): + warnings.warn('HttpRequest.raw_post_data has been deprecated. Use HttpRequest.body instead.', PendingDeprecationWarning) + return self.body def _mark_post_parse_error(self): self._post = QueryDict('') @@ -334,14 +341,14 @@ class HttpRequest(object): if self.method != 'POST': self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() return - if self._read_started and not hasattr(self, '_raw_post_data'): + if self._read_started and not hasattr(self, '_body'): self._mark_post_parse_error() return if self.META.get('CONTENT_TYPE', '').startswith('multipart'): - if hasattr(self, '_raw_post_data'): + if hasattr(self, '_body'): # Use already read data - data = StringIO(self._raw_post_data) + data = StringIO(self._body) else: data = self try: @@ -357,14 +364,14 @@ class HttpRequest(object): self._mark_post_parse_error() raise else: - self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict() + self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() ## File-like and iterator interface. ## ## Expects self._stream to be set to an appropriate source of bytes by ## a corresponding request subclass (WSGIRequest or ModPythonRequest). ## Also when request data has already been read by request.POST or - ## request.raw_post_data, self._stream points to a StringIO instance + ## request.body, self._stream points to a StringIO instance ## containing that data. def read(self, *args, **kwargs): diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 91fde966fc..cae3f9f36c 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -257,6 +257,10 @@ these changes. * Setting the ``is_safe`` and ``needs_autoescape`` flags as attributes of template filter functions will no longer be supported. +* The attribute ``HttpRequest.raw_post_data`` was renamed to ``HttpRequest.body`` + in 1.4. The backward compatibility will be removed -- + ``HttpRequest.raw_post_data`` will no longer work. + 2.0 --- diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 64d0e10ca3..b0be311de9 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -30,6 +30,20 @@ Attributes All attributes except ``session`` should be considered read-only. +.. attribute:: HttpRequest.body + + .. versionchanged:: 1.4 + Before Django 1.4, ``HttpRequest.body`` was named ``HttpRequest.raw_post_data``. + + The raw HTTP request body as a byte string. This is useful for processing + data in different ways than conventional HTML forms: binary images, + XML payload etc. For processing conventional form data, use ``HttpRequest.POST``. + + .. versionadded:: 1.3 + + You can also read from an HttpRequest using a file-like interface. See + :meth:`HttpRequest.read()`. + .. attribute:: HttpRequest.path A string representing the full path to the requested page, not including @@ -170,17 +184,6 @@ All attributes except ``session`` should be considered read-only. support activated. See the :doc:`session documentation ` for full details. -.. attribute:: HttpRequest.raw_post_data - - The raw HTTP POST data as a byte string. This is useful for processing - data in different formats than of conventional HTML forms: binary images, - XML payload etc. For processing form data use ``HttpRequest.POST``. - - .. versionadded:: 1.3 - - You can also read from an HttpRequest using file-like interface. See - :meth:`HttpRequest.read()`. - .. attribute:: HttpRequest.urlconf Not defined by Django itself, but will be read if other code (e.g., a custom diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index f614dee5f8..7ffc1aa654 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -995,3 +995,10 @@ useful, it was removed in Django 1.4. If you relied on it, you must edit your settings file to list all your applications explicitly. .. _this can't be done reliably: http://docs.python.org/tutorial/modules.html#importing-from-a-package + +``HttpRequest.raw_post_data`` renamed to ``HttpRequest.body`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This attribute was confusingly named ``HttpRequest.raw_post_data``, but it +actually provided the body of the HTTP request. It's been renamed to +``HttpRequest.body``, and ``HttpRequest.raw_post_data`` has been deprecated. diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index a86064edf9..6ea7213a02 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -44,7 +44,7 @@ def raw_post_view(request): """A view which expects raw XML to be posted and returns content extracted from the XML""" if request.method == 'POST': - root = parseString(request.raw_post_data) + root = parseString(request.body) first_book = root.firstChild.firstChild title, author = [n.firstChild.nodeValue for n in first_book.childNodes] t = Template("{{ title }} - {{ author }}", name="Book template") diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index e96f3129be..7927d27098 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -1,4 +1,5 @@ import time +import warnings from datetime import datetime, timedelta from StringIO import StringIO @@ -6,6 +7,7 @@ from django.conf import settings from django.core.handlers.modpython import ModPythonRequest from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr +from django.test.utils import get_warnings_state, restore_warnings_state from django.utils import unittest from django.utils.http import cookie_date @@ -294,19 +296,19 @@ class RequestsTests(unittest.TestCase): def test_read_after_value(self): """ Reading from request is allowed after accessing request contents as - POST or raw_post_data. + POST or body. """ payload = 'name=value' request = WSGIRequest({'REQUEST_METHOD': 'POST', 'CONTENT_LENGTH': len(payload), 'wsgi.input': StringIO(payload)}) self.assertEqual(request.POST, {u'name': [u'value']}) - self.assertEqual(request.raw_post_data, 'name=value') + self.assertEqual(request.body, 'name=value') self.assertEqual(request.read(), 'name=value') def test_value_after_read(self): """ - Construction of POST or raw_post_data is not allowed after reading + Construction of POST or body is not allowed after reading from request. """ payload = 'name=value' @@ -314,16 +316,16 @@ class RequestsTests(unittest.TestCase): 'CONTENT_LENGTH': len(payload), 'wsgi.input': StringIO(payload)}) self.assertEqual(request.read(2), 'na') - self.assertRaises(Exception, lambda: request.raw_post_data) + self.assertRaises(Exception, lambda: request.body) self.assertEqual(request.POST, {}) - def test_raw_post_data_after_POST_multipart(self): + def test_body_after_POST_multipart(self): """ - Reading raw_post_data after parsing multipart is not allowed + Reading body after parsing multipart is not allowed """ # Because multipart is used for large amounts fo data i.e. file uploads, # we don't want the data held in memory twice, and we don't want to - # silence the error by setting raw_post_data = '' either. + # silence the error by setting body = '' either. payload = "\r\n".join([ '--boundary', 'Content-Disposition: form-data; name="name"', @@ -336,7 +338,7 @@ class RequestsTests(unittest.TestCase): 'CONTENT_LENGTH': len(payload), 'wsgi.input': StringIO(payload)}) self.assertEqual(request.POST, {u'name': [u'value']}) - self.assertRaises(Exception, lambda: request.raw_post_data) + self.assertRaises(Exception, lambda: request.body) def test_POST_multipart_with_content_length_zero(self): """ @@ -366,33 +368,33 @@ class RequestsTests(unittest.TestCase): 'wsgi.input': StringIO(payload)}) self.assertEqual(list(request), ['name=value']) - def test_POST_after_raw_post_data_read(self): + def test_POST_after_body_read(self): """ - POST should be populated even if raw_post_data is read first + POST should be populated even if body is read first """ payload = 'name=value' request = WSGIRequest({'REQUEST_METHOD': 'POST', 'CONTENT_LENGTH': len(payload), 'wsgi.input': StringIO(payload)}) - raw_data = request.raw_post_data + raw_data = request.body self.assertEqual(request.POST, {u'name': [u'value']}) - def test_POST_after_raw_post_data_read_and_stream_read(self): + def test_POST_after_body_read_and_stream_read(self): """ - POST should be populated even if raw_post_data is read first, and then + POST should be populated even if body is read first, and then the stream is read second. """ payload = 'name=value' request = WSGIRequest({'REQUEST_METHOD': 'POST', 'CONTENT_LENGTH': len(payload), 'wsgi.input': StringIO(payload)}) - raw_data = request.raw_post_data + raw_data = request.body self.assertEqual(request.read(1), u'n') self.assertEqual(request.POST, {u'name': [u'value']}) - def test_POST_after_raw_post_data_read_and_stream_read_multipart(self): + def test_POST_after_body_read_and_stream_read_multipart(self): """ - POST should be populated even if raw_post_data is read first, and then + POST should be populated even if body is read first, and then the stream is read second. Using multipart/form-data instead of urlencoded. """ payload = "\r\n".join([ @@ -406,7 +408,23 @@ class RequestsTests(unittest.TestCase): 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', 'CONTENT_LENGTH': len(payload), 'wsgi.input': StringIO(payload)}) - raw_data = request.raw_post_data + raw_data = request.body # Consume enough data to mess up the parsing: self.assertEqual(request.read(13), u'--boundary\r\nC') self.assertEqual(request.POST, {u'name': [u'value']}) + + def test_raw_post_data_returns_body(self): + """ + HttpRequest.raw_post_body should be the same as HttpRequest.body + """ + payload = 'Hello There!' + request = WSGIRequest({ + 'REQUEST_METHOD': 'POST', + 'CONTENT_LENGTH': len(payload), + 'wsgi.input': StringIO(payload) + }) + + warnings_state = get_warnings_state() + warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.http') + self.assertEqual(request.body, request.raw_post_data) + restore_warnings_state(warnings_state) diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 7d0b0e412f..4ddd957055 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -975,15 +975,16 @@ class ResponseTemplateDeprecationTests(TestCase): class ReadLimitedStreamTest(TestCase): """ - Tests that ensure that HttpRequest.raw_post_data, HttpRequest.read() and + Tests that ensure that HttpRequest.body, HttpRequest.read() and HttpRequest.read(BUFFER) have proper LimitedStream behaviour. Refs #14753, #15785 """ - def test_raw_post_data_from_empty_request(self): - """HttpRequest.raw_post_data on a test client GET request should return + + def test_body_from_empty_request(self): + """HttpRequest.body on a test client GET request should return the empty string.""" - self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '') + self.assertEquals(self.client.get("/test_client_regress/body/").content, '') def test_read_from_empty_request(self): """HttpRequest.read() on a test client GET request should return the diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py index 93f7a2e2f2..d869c234eb 100644 --- a/tests/regressiontests/test_client_regress/urls.py +++ b/tests/regressiontests/test_client_regress/urls.py @@ -31,7 +31,7 @@ urlpatterns = patterns('', (r'^parse_unicode_json/$', views.return_json_file), (r'^check_headers/$', views.check_headers), (r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/check_headers/')), - (r'^raw_post_data/$', views.raw_post_data), + (r'^body/$', views.body), (r'^read_all/$', views.read_all), (r'^read_buffer/$', views.read_buffer), (r'^request_context_view/$', views.request_context_view), diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index b3982938b7..ebb68c4c82 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/regressiontests/test_client_regress/views.py @@ -1,3 +1,5 @@ +import warnings + from django.conf import settings from django.contrib.auth.decorators import login_required from django.http import HttpResponse, HttpResponseRedirect @@ -79,7 +81,7 @@ def return_json_file(request): charset = settings.DEFAULT_CHARSET # This just checks that the uploaded data is JSON - obj_dict = simplejson.loads(request.raw_post_data.decode(charset)) + obj_dict = simplejson.loads(request.body.decode(charset)) obj_json = simplejson.dumps(obj_dict, encoding=charset, cls=DjangoJSONEncoder, ensure_ascii=False) @@ -92,9 +94,9 @@ def check_headers(request): "A view that responds with value of the X-ARG-CHECK header" return HttpResponse('HTTP_X_ARG_CHECK: %s' % request.META.get('HTTP_X_ARG_CHECK', 'Undefined')) -def raw_post_data(request): - "A view that is requested with GET and accesses request.raw_post_data. Refs #14753." - return HttpResponse(request.raw_post_data) +def body(request): + "A view that is requested with GET and accesses request.body. Refs #14753." + return HttpResponse(request.body) def read_all(request): "A view that is requested with accesses request.read()."