Fixed #17323 -- Renamed HttpRequest.raw_post_data to request.body. Thanks for the patch, dstufft

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17210 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2011-12-16 23:40:32 +00:00
parent 61f0aff811
commit 3f003a3c4b
9 changed files with 92 additions and 50 deletions

View File

@ -4,6 +4,8 @@ import datetime
import os import os
import re import re
import time import time
import warnings
from pprint import pformat from pprint import pformat
from urllib import urlencode, quote from urllib import urlencode, quote
from urlparse import urljoin from urlparse import urljoin
@ -315,14 +317,19 @@ class HttpRequest(object):
parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
return parser.parse() return parser.parse()
def _get_raw_post_data(self): @property
if not hasattr(self, '_raw_post_data'): def body(self):
if not hasattr(self, '_body'):
if self._read_started: if self._read_started:
raise Exception("You cannot access raw_post_data after reading from request's data stream") raise Exception("You cannot access body after reading from request's data stream")
self._raw_post_data = self.read() self._body = self.read()
self._stream = StringIO(self._raw_post_data) self._stream = StringIO(self._body)
return self._raw_post_data return self._body
raw_post_data = property(_get_raw_post_data)
@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): def _mark_post_parse_error(self):
self._post = QueryDict('') self._post = QueryDict('')
@ -334,14 +341,14 @@ class HttpRequest(object):
if self.method != 'POST': if self.method != 'POST':
self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict()
return 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() self._mark_post_parse_error()
return return
if self.META.get('CONTENT_TYPE', '').startswith('multipart'): if self.META.get('CONTENT_TYPE', '').startswith('multipart'):
if hasattr(self, '_raw_post_data'): if hasattr(self, '_body'):
# Use already read data # Use already read data
data = StringIO(self._raw_post_data) data = StringIO(self._body)
else: else:
data = self data = self
try: try:
@ -357,14 +364,14 @@ class HttpRequest(object):
self._mark_post_parse_error() self._mark_post_parse_error()
raise raise
else: 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. ## File-like and iterator interface.
## ##
## Expects self._stream to be set to an appropriate source of bytes by ## Expects self._stream to be set to an appropriate source of bytes by
## a corresponding request subclass (WSGIRequest or ModPythonRequest). ## a corresponding request subclass (WSGIRequest or ModPythonRequest).
## Also when request data has already been read by request.POST or ## 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. ## containing that data.
def read(self, *args, **kwargs): def read(self, *args, **kwargs):

View File

@ -257,6 +257,10 @@ these changes.
* Setting the ``is_safe`` and ``needs_autoescape`` flags as attributes of * Setting the ``is_safe`` and ``needs_autoescape`` flags as attributes of
template filter functions will no longer be supported. 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 2.0
--- ---

View File

@ -30,6 +30,20 @@ Attributes
All attributes except ``session`` should be considered read-only. 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 .. attribute:: HttpRequest.path
A string representing the full path to the requested page, not including 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 support activated. See the :doc:`session documentation
</topics/http/sessions>` for full details. </topics/http/sessions>` 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 .. attribute:: HttpRequest.urlconf
Not defined by Django itself, but will be read if other code (e.g., a custom Not defined by Django itself, but will be read if other code (e.g., a custom

View File

@ -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. 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 .. _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.

View File

@ -44,7 +44,7 @@ def raw_post_view(request):
"""A view which expects raw XML to be posted and returns content extracted """A view which expects raw XML to be posted and returns content extracted
from the XML""" from the XML"""
if request.method == 'POST': if request.method == 'POST':
root = parseString(request.raw_post_data) root = parseString(request.body)
first_book = root.firstChild.firstChild first_book = root.firstChild.firstChild
title, author = [n.firstChild.nodeValue for n in first_book.childNodes] title, author = [n.firstChild.nodeValue for n in first_book.childNodes]
t = Template("{{ title }} - {{ author }}", name="Book template") t = Template("{{ title }} - {{ author }}", name="Book template")

View File

@ -1,4 +1,5 @@
import time import time
import warnings
from datetime import datetime, timedelta from datetime import datetime, timedelta
from StringIO import StringIO from StringIO import StringIO
@ -6,6 +7,7 @@ from django.conf import settings
from django.core.handlers.modpython import ModPythonRequest from django.core.handlers.modpython import ModPythonRequest
from django.core.handlers.wsgi import WSGIRequest, LimitedStream from django.core.handlers.wsgi import WSGIRequest, LimitedStream
from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr 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 import unittest
from django.utils.http import cookie_date from django.utils.http import cookie_date
@ -294,19 +296,19 @@ class RequestsTests(unittest.TestCase):
def test_read_after_value(self): def test_read_after_value(self):
""" """
Reading from request is allowed after accessing request contents as Reading from request is allowed after accessing request contents as
POST or raw_post_data. POST or body.
""" """
payload = 'name=value' payload = 'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': StringIO(payload)}) 'wsgi.input': StringIO(payload)})
self.assertEqual(request.POST, {u'name': [u'value']}) 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') self.assertEqual(request.read(), 'name=value')
def test_value_after_read(self): 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. from request.
""" """
payload = 'name=value' payload = 'name=value'
@ -314,16 +316,16 @@ class RequestsTests(unittest.TestCase):
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': StringIO(payload)}) 'wsgi.input': StringIO(payload)})
self.assertEqual(request.read(2), 'na') self.assertEqual(request.read(2), 'na')
self.assertRaises(Exception, lambda: request.raw_post_data) self.assertRaises(Exception, lambda: request.body)
self.assertEqual(request.POST, {}) 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, # 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 # 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([ payload = "\r\n".join([
'--boundary', '--boundary',
'Content-Disposition: form-data; name="name"', 'Content-Disposition: form-data; name="name"',
@ -336,7 +338,7 @@ class RequestsTests(unittest.TestCase):
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': StringIO(payload)}) 'wsgi.input': StringIO(payload)})
self.assertEqual(request.POST, {u'name': [u'value']}) 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): def test_POST_multipart_with_content_length_zero(self):
""" """
@ -366,33 +368,33 @@ class RequestsTests(unittest.TestCase):
'wsgi.input': StringIO(payload)}) 'wsgi.input': StringIO(payload)})
self.assertEqual(list(request), ['name=value']) 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' payload = 'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': StringIO(payload)}) 'wsgi.input': StringIO(payload)})
raw_data = request.raw_post_data raw_data = request.body
self.assertEqual(request.POST, {u'name': [u'value']}) 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. the stream is read second.
""" """
payload = 'name=value' payload = 'name=value'
request = WSGIRequest({'REQUEST_METHOD': 'POST', request = WSGIRequest({'REQUEST_METHOD': 'POST',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': StringIO(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.read(1), u'n')
self.assertEqual(request.POST, {u'name': [u'value']}) 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. the stream is read second. Using multipart/form-data instead of urlencoded.
""" """
payload = "\r\n".join([ payload = "\r\n".join([
@ -406,7 +408,23 @@ class RequestsTests(unittest.TestCase):
'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
'CONTENT_LENGTH': len(payload), 'CONTENT_LENGTH': len(payload),
'wsgi.input': StringIO(payload)}) 'wsgi.input': StringIO(payload)})
raw_data = request.raw_post_data raw_data = request.body
# Consume enough data to mess up the parsing: # Consume enough data to mess up the parsing:
self.assertEqual(request.read(13), u'--boundary\r\nC') self.assertEqual(request.read(13), u'--boundary\r\nC')
self.assertEqual(request.POST, {u'name': [u'value']}) 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)

View File

@ -975,15 +975,16 @@ class ResponseTemplateDeprecationTests(TestCase):
class ReadLimitedStreamTest(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. HttpRequest.read(BUFFER) have proper LimitedStream behaviour.
Refs #14753, #15785 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.""" 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): def test_read_from_empty_request(self):
"""HttpRequest.read() on a test client GET request should return the """HttpRequest.read() on a test client GET request should return the

View File

@ -31,7 +31,7 @@ urlpatterns = patterns('',
(r'^parse_unicode_json/$', views.return_json_file), (r'^parse_unicode_json/$', views.return_json_file),
(r'^check_headers/$', views.check_headers), (r'^check_headers/$', views.check_headers),
(r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/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_all/$', views.read_all),
(r'^read_buffer/$', views.read_buffer), (r'^read_buffer/$', views.read_buffer),
(r'^request_context_view/$', views.request_context_view), (r'^request_context_view/$', views.request_context_view),

View File

@ -1,3 +1,5 @@
import warnings
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
@ -79,7 +81,7 @@ def return_json_file(request):
charset = settings.DEFAULT_CHARSET charset = settings.DEFAULT_CHARSET
# This just checks that the uploaded data is JSON # 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, obj_json = simplejson.dumps(obj_dict, encoding=charset,
cls=DjangoJSONEncoder, cls=DjangoJSONEncoder,
ensure_ascii=False) ensure_ascii=False)
@ -92,9 +94,9 @@ def check_headers(request):
"A view that responds with value of the X-ARG-CHECK header" "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')) return HttpResponse('HTTP_X_ARG_CHECK: %s' % request.META.get('HTTP_X_ARG_CHECK', 'Undefined'))
def raw_post_data(request): def body(request):
"A view that is requested with GET and accesses request.raw_post_data. Refs #14753." "A view that is requested with GET and accesses request.body. Refs #14753."
return HttpResponse(request.raw_post_data) return HttpResponse(request.body)
def read_all(request): def read_all(request):
"A view that is requested with accesses request.read()." "A view that is requested with accesses request.read()."