diff --git a/django/http/__init__.py b/django/http/__init__.py
index 476a625..bb1c3b6 100644
a
|
b
|
|
4 | 4 | import os |
5 | 5 | import re |
6 | 6 | import time |
| 7 | import warnings |
| 8 | |
7 | 9 | from pprint import pformat |
8 | 10 | from urllib import urlencode, quote |
9 | 11 | from urlparse import urljoin |
… |
… |
def parse_file_upload(self, META, post_data):
|
300 | 302 | parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding) |
301 | 303 | return parser.parse() |
302 | 304 | |
303 | | def _get_raw_post_data(self): |
304 | | if not hasattr(self, '_raw_post_data'): |
| 305 | @property |
| 306 | def body(self): |
| 307 | if not hasattr(self, '_body'): |
305 | 308 | if self._read_started: |
306 | | raise Exception("You cannot access raw_post_data after reading from request's data stream") |
307 | | self._raw_post_data = self.read() |
308 | | self._stream = StringIO(self._raw_post_data) |
309 | | return self._raw_post_data |
310 | | raw_post_data = property(_get_raw_post_data) |
| 309 | raise Exception("You cannot access body after reading from request's data stream") |
| 310 | self._body = self.read() |
| 311 | self._stream = StringIO(self._body) |
| 312 | return self._body |
| 313 | |
| 314 | @property |
| 315 | def raw_post_data(self): |
| 316 | warnings.warn( |
| 317 | 'The raw_post_data attribute has been renamed to body and the original ' |
| 318 | 'name has been deprecated.', PendingDeprecationWarning) |
| 319 | return self.body |
311 | 320 | |
312 | 321 | def _mark_post_parse_error(self): |
313 | 322 | self._post = QueryDict('') |
… |
… |
def _load_post_and_files(self):
|
319 | 328 | if self.method != 'POST': |
320 | 329 | self._post, self._files = QueryDict('', encoding=self._encoding), MultiValueDict() |
321 | 330 | return |
322 | | if self._read_started and not hasattr(self, '_raw_post_data'): |
| 331 | if self._read_started and not hasattr(self, '_body'): |
323 | 332 | self._mark_post_parse_error() |
324 | 333 | return |
325 | 334 | |
326 | 335 | if self.META.get('CONTENT_TYPE', '').startswith('multipart'): |
327 | | if hasattr(self, '_raw_post_data'): |
| 336 | if hasattr(self, '_body'): |
328 | 337 | # Use already read data |
329 | | data = StringIO(self._raw_post_data) |
| 338 | data = StringIO(self._body) |
330 | 339 | else: |
331 | 340 | data = self |
332 | 341 | try: |
… |
… |
def _load_post_and_files(self):
|
342 | 351 | self._mark_post_parse_error() |
343 | 352 | raise |
344 | 353 | else: |
345 | | self._post, self._files = QueryDict(self.raw_post_data, encoding=self._encoding), MultiValueDict() |
| 354 | self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() |
346 | 355 | |
347 | 356 | ## File-like and iterator interface. |
348 | 357 | ## |
349 | 358 | ## Expects self._stream to be set to an appropriate source of bytes by |
350 | 359 | ## a corresponding request subclass (WSGIRequest or ModPythonRequest). |
351 | 360 | ## Also when request data has already been read by request.POST or |
352 | | ## request.raw_post_data, self._stream points to a StringIO instance |
| 361 | ## request.body, self._stream points to a StringIO instance |
353 | 362 | ## containing that data. |
354 | 363 | |
355 | 364 | def read(self, *args, **kwargs): |
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 91fde96..9fcb681 100644
a
|
b
|
these changes.
|
257 | 257 | * Setting the ``is_safe`` and ``needs_autoescape`` flags as attributes of |
258 | 258 | template filter functions will no longer be supported. |
259 | 259 | |
| 260 | * The attribute ``HttpRequest.raw_post_data`` was renamed to ``HttpRequest.body`` |
| 261 | in 1.4. The backwards compatibility shim will be removed. |
| 262 | |
260 | 263 | 2.0 |
261 | 264 | --- |
262 | 265 | |
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index 64d0e10..8c2b7a6 100644
a
|
b
|
Attributes
|
30 | 30 | |
31 | 31 | All attributes except ``session`` should be considered read-only. |
32 | 32 | |
| 33 | .. attribute:: HttpRequest.body |
| 34 | |
| 35 | .. versionchanged:: 1.4 |
| 36 | Prior to 1.4, ``HttpRequest.body`` was named ``HttpRequest.raw_post_data``. |
| 37 | |
| 38 | The raw HTTP request body as a byte string. This is useful for processing |
| 39 | data in different formats than of conventional HTML forms: binary images, |
| 40 | XML payload etc. For processing form data use ``HttpRequest.POST``. |
| 41 | |
| 42 | .. versionadded:: 1.3 |
| 43 | |
| 44 | You can also read from an HttpRequest using file-like interface. See |
| 45 | :meth:`HttpRequest.read()`. |
| 46 | |
33 | 47 | .. attribute:: HttpRequest.path |
34 | 48 | |
35 | 49 | A string representing the full path to the requested page, not including |
… |
… |
All attributes except ``session`` should be considered read-only.
|
170 | 184 | support activated. See the :doc:`session documentation |
171 | 185 | </topics/http/sessions>` for full details. |
172 | 186 | |
173 | | .. attribute:: HttpRequest.raw_post_data |
174 | | |
175 | | The raw HTTP POST data as a byte string. This is useful for processing |
176 | | data in different formats than of conventional HTML forms: binary images, |
177 | | XML payload etc. For processing form data use ``HttpRequest.POST``. |
178 | | |
179 | | .. versionadded:: 1.3 |
180 | | |
181 | | You can also read from an HttpRequest using file-like interface. See |
182 | | :meth:`HttpRequest.read()`. |
183 | | |
184 | 187 | .. attribute:: HttpRequest.urlconf |
185 | 188 | |
186 | 189 | 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 f614dee..385010b 100644
a
|
b
|
useful, it was removed in Django 1.4. If you relied on it, you must edit your
|
995 | 995 | settings file to list all your applications explicitly. |
996 | 996 | |
997 | 997 | .. _this can't be done reliably: http://docs.python.org/tutorial/modules.html#importing-from-a-package |
| 998 | |
| 999 | ``HttpRequest.raw_post_data renamed to HttpRequest.body`` |
| 1000 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1001 | This attribute was named ``HttpRequest.raw_post_data`` but what it actually provided |
| 1002 | was the body of the HTTP request. It has been renamed to ``HttpRequest.body`` to |
| 1003 | better match what it actually provides and ``HttpRequest.raw_post_data`` has been |
| 1004 | deprecated. |
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index a86064e..6ea7213 100644
a
|
b
|
def raw_post_view(request):
|
44 | 44 | """A view which expects raw XML to be posted and returns content extracted |
45 | 45 | from the XML""" |
46 | 46 | if request.method == 'POST': |
47 | | root = parseString(request.raw_post_data) |
| 47 | root = parseString(request.body) |
48 | 48 | first_book = root.firstChild.firstChild |
49 | 49 | title, author = [n.firstChild.nodeValue for n in first_book.childNodes] |
50 | 50 | t = Template("{{ title }} - {{ author }}", name="Book template") |
diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
index e96f312..7927d27 100644
a
|
b
|
|
1 | 1 | import time |
| 2 | import warnings |
2 | 3 | from datetime import datetime, timedelta |
3 | 4 | from StringIO import StringIO |
4 | 5 | |
… |
… |
|
6 | 7 | from django.core.handlers.modpython import ModPythonRequest |
7 | 8 | from django.core.handlers.wsgi import WSGIRequest, LimitedStream |
8 | 9 | from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr |
| 10 | from django.test.utils import get_warnings_state, restore_warnings_state |
9 | 11 | from django.utils import unittest |
10 | 12 | from django.utils.http import cookie_date |
11 | 13 | |
… |
… |
def test_stream(self):
|
294 | 296 | def test_read_after_value(self): |
295 | 297 | """ |
296 | 298 | Reading from request is allowed after accessing request contents as |
297 | | POST or raw_post_data. |
| 299 | POST or body. |
298 | 300 | """ |
299 | 301 | payload = 'name=value' |
300 | 302 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
301 | 303 | 'CONTENT_LENGTH': len(payload), |
302 | 304 | 'wsgi.input': StringIO(payload)}) |
303 | 305 | self.assertEqual(request.POST, {u'name': [u'value']}) |
304 | | self.assertEqual(request.raw_post_data, 'name=value') |
| 306 | self.assertEqual(request.body, 'name=value') |
305 | 307 | self.assertEqual(request.read(), 'name=value') |
306 | 308 | |
307 | 309 | def test_value_after_read(self): |
308 | 310 | """ |
309 | | Construction of POST or raw_post_data is not allowed after reading |
| 311 | Construction of POST or body is not allowed after reading |
310 | 312 | from request. |
311 | 313 | """ |
312 | 314 | payload = 'name=value' |
… |
… |
def test_value_after_read(self):
|
314 | 316 | 'CONTENT_LENGTH': len(payload), |
315 | 317 | 'wsgi.input': StringIO(payload)}) |
316 | 318 | self.assertEqual(request.read(2), 'na') |
317 | | self.assertRaises(Exception, lambda: request.raw_post_data) |
| 319 | self.assertRaises(Exception, lambda: request.body) |
318 | 320 | self.assertEqual(request.POST, {}) |
319 | 321 | |
320 | | def test_raw_post_data_after_POST_multipart(self): |
| 322 | def test_body_after_POST_multipart(self): |
321 | 323 | """ |
322 | | Reading raw_post_data after parsing multipart is not allowed |
| 324 | Reading body after parsing multipart is not allowed |
323 | 325 | """ |
324 | 326 | # Because multipart is used for large amounts fo data i.e. file uploads, |
325 | 327 | # we don't want the data held in memory twice, and we don't want to |
326 | | # silence the error by setting raw_post_data = '' either. |
| 328 | # silence the error by setting body = '' either. |
327 | 329 | payload = "\r\n".join([ |
328 | 330 | '--boundary', |
329 | 331 | 'Content-Disposition: form-data; name="name"', |
… |
… |
def test_raw_post_data_after_POST_multipart(self):
|
336 | 338 | 'CONTENT_LENGTH': len(payload), |
337 | 339 | 'wsgi.input': StringIO(payload)}) |
338 | 340 | self.assertEqual(request.POST, {u'name': [u'value']}) |
339 | | self.assertRaises(Exception, lambda: request.raw_post_data) |
| 341 | self.assertRaises(Exception, lambda: request.body) |
340 | 342 | |
341 | 343 | def test_POST_multipart_with_content_length_zero(self): |
342 | 344 | """ |
… |
… |
def test_read_by_lines(self):
|
366 | 368 | 'wsgi.input': StringIO(payload)}) |
367 | 369 | self.assertEqual(list(request), ['name=value']) |
368 | 370 | |
369 | | def test_POST_after_raw_post_data_read(self): |
| 371 | def test_POST_after_body_read(self): |
370 | 372 | """ |
371 | | POST should be populated even if raw_post_data is read first |
| 373 | POST should be populated even if body is read first |
372 | 374 | """ |
373 | 375 | payload = 'name=value' |
374 | 376 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
375 | 377 | 'CONTENT_LENGTH': len(payload), |
376 | 378 | 'wsgi.input': StringIO(payload)}) |
377 | | raw_data = request.raw_post_data |
| 379 | raw_data = request.body |
378 | 380 | self.assertEqual(request.POST, {u'name': [u'value']}) |
379 | 381 | |
380 | | def test_POST_after_raw_post_data_read_and_stream_read(self): |
| 382 | def test_POST_after_body_read_and_stream_read(self): |
381 | 383 | """ |
382 | | POST should be populated even if raw_post_data is read first, and then |
| 384 | POST should be populated even if body is read first, and then |
383 | 385 | the stream is read second. |
384 | 386 | """ |
385 | 387 | payload = 'name=value' |
386 | 388 | request = WSGIRequest({'REQUEST_METHOD': 'POST', |
387 | 389 | 'CONTENT_LENGTH': len(payload), |
388 | 390 | 'wsgi.input': StringIO(payload)}) |
389 | | raw_data = request.raw_post_data |
| 391 | raw_data = request.body |
390 | 392 | self.assertEqual(request.read(1), u'n') |
391 | 393 | self.assertEqual(request.POST, {u'name': [u'value']}) |
392 | 394 | |
393 | | def test_POST_after_raw_post_data_read_and_stream_read_multipart(self): |
| 395 | def test_POST_after_body_read_and_stream_read_multipart(self): |
394 | 396 | """ |
395 | | POST should be populated even if raw_post_data is read first, and then |
| 397 | POST should be populated even if body is read first, and then |
396 | 398 | the stream is read second. Using multipart/form-data instead of urlencoded. |
397 | 399 | """ |
398 | 400 | payload = "\r\n".join([ |
… |
… |
def test_POST_after_raw_post_data_read_and_stream_read_multipart(self):
|
406 | 408 | 'CONTENT_TYPE': 'multipart/form-data; boundary=boundary', |
407 | 409 | 'CONTENT_LENGTH': len(payload), |
408 | 410 | 'wsgi.input': StringIO(payload)}) |
409 | | raw_data = request.raw_post_data |
| 411 | raw_data = request.body |
410 | 412 | # Consume enough data to mess up the parsing: |
411 | 413 | self.assertEqual(request.read(13), u'--boundary\r\nC') |
412 | 414 | self.assertEqual(request.POST, {u'name': [u'value']}) |
| 415 | |
| 416 | def test_raw_post_data_returns_body(self): |
| 417 | """ |
| 418 | HttpRequest.raw_post_body should be the same as HttpRequest.body |
| 419 | """ |
| 420 | payload = 'Hello There!' |
| 421 | request = WSGIRequest({ |
| 422 | 'REQUEST_METHOD': 'POST', |
| 423 | 'CONTENT_LENGTH': len(payload), |
| 424 | 'wsgi.input': StringIO(payload) |
| 425 | }) |
| 426 | |
| 427 | warnings_state = get_warnings_state() |
| 428 | warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.http') |
| 429 | self.assertEqual(request.body, request.raw_post_data) |
| 430 | restore_warnings_state(warnings_state) |
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 7d0b0e4..4ddd957 100644
a
|
b
|
def test_response_no_template(self):
|
975 | 975 | |
976 | 976 | class ReadLimitedStreamTest(TestCase): |
977 | 977 | """ |
978 | | Tests that ensure that HttpRequest.raw_post_data, HttpRequest.read() and |
| 978 | Tests that ensure that HttpRequest.body, HttpRequest.read() and |
979 | 979 | HttpRequest.read(BUFFER) have proper LimitedStream behaviour. |
980 | 980 | |
981 | 981 | Refs #14753, #15785 |
982 | 982 | """ |
983 | | def test_raw_post_data_from_empty_request(self): |
984 | | """HttpRequest.raw_post_data on a test client GET request should return |
| 983 | |
| 984 | def test_body_from_empty_request(self): |
| 985 | """HttpRequest.body on a test client GET request should return |
985 | 986 | the empty string.""" |
986 | | self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '') |
| 987 | self.assertEquals(self.client.get("/test_client_regress/body/").content, '') |
987 | 988 | |
988 | 989 | def test_read_from_empty_request(self): |
989 | 990 | """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 93f7a2e..d869c23 100644
a
|
b
|
|
31 | 31 | (r'^parse_unicode_json/$', views.return_json_file), |
32 | 32 | (r'^check_headers/$', views.check_headers), |
33 | 33 | (r'^check_headers_redirect/$', RedirectView.as_view(url='/test_client_regress/check_headers/')), |
34 | | (r'^raw_post_data/$', views.raw_post_data), |
| 34 | (r'^body/$', views.body), |
35 | 35 | (r'^read_all/$', views.read_all), |
36 | 36 | (r'^read_buffer/$', views.read_buffer), |
37 | 37 | (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 b398293..ebb68c4 100644
a
|
b
|
|
| 1 | import warnings |
| 2 | |
1 | 3 | from django.conf import settings |
2 | 4 | from django.contrib.auth.decorators import login_required |
3 | 5 | from django.http import HttpResponse, HttpResponseRedirect |
… |
… |
def return_json_file(request):
|
79 | 81 | charset = settings.DEFAULT_CHARSET |
80 | 82 | |
81 | 83 | # This just checks that the uploaded data is JSON |
82 | | obj_dict = simplejson.loads(request.raw_post_data.decode(charset)) |
| 84 | obj_dict = simplejson.loads(request.body.decode(charset)) |
83 | 85 | obj_json = simplejson.dumps(obj_dict, encoding=charset, |
84 | 86 | cls=DjangoJSONEncoder, |
85 | 87 | ensure_ascii=False) |
… |
… |
def check_headers(request):
|
92 | 94 | "A view that responds with value of the X-ARG-CHECK header" |
93 | 95 | return HttpResponse('HTTP_X_ARG_CHECK: %s' % request.META.get('HTTP_X_ARG_CHECK', 'Undefined')) |
94 | 96 | |
95 | | def raw_post_data(request): |
96 | | "A view that is requested with GET and accesses request.raw_post_data. Refs #14753." |
97 | | return HttpResponse(request.raw_post_data) |
| 97 | def body(request): |
| 98 | "A view that is requested with GET and accesses request.body. Refs #14753." |
| 99 | return HttpResponse(request.body) |
98 | 100 | |
99 | 101 | def read_all(request): |
100 | 102 | "A view that is requested with accesses request.read()." |