diff --git a/django/http/__init__.py b/django/http/__init__.py
index 476a625..bb1c3b6 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -4,6 +4,8 @@
 import os
 import re
 import time
+import warnings
+
 from pprint import pformat
 from urllib import urlencode, quote
 from urlparse import urljoin
@@ -300,14 +302,21 @@ def parse_file_upload(self, META, post_data):
         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(
+        'The raw_post_data attribute has been renamed to body and the original '
+        'name has been deprecated.', PendingDeprecationWarning)
+        return self.body
 
     def _mark_post_parse_error(self):
         self._post = QueryDict('')
@@ -319,14 +328,14 @@ def _load_post_and_files(self):
         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:
@@ -342,14 +351,14 @@ def _load_post_and_files(self):
                 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/ref/request-response.txt b/docs/ref/request-response.txt
index 64d0e10..b6fffb2 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -30,6 +30,19 @@ Attributes
 
 All attributes except ``session`` should be considered read-only.
 
+.. attribute:: HttpRequest.body
+
+    .. versionadded:: 1.4
+
+    The raw HTTP request body 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.path
 
     A string representing the full path to the requested page, not including
@@ -172,6 +185,8 @@ All attributes except ``session`` should be considered read-only.
 
 .. attribute:: HttpRequest.raw_post_data
 
+    .. deprecated:: 1.4
+
     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``.
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index a86064e..6ea7213 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 e96f312..a3bfea0 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
 
@@ -291,39 +292,98 @@ def test_stream(self):
                                'wsgi.input': StringIO(payload)})
         self.assertEqual(request.read(), 'name=value')
 
-    def test_read_after_value(self):
+    def test_read_after_value_raw_post_data(self):
         """
         Reading from request is allowed after accessing request contents as
         POST or raw_post_data.
         """
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            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.read(), 'name=value')
+
+    def test_read_after_value(self):
+        """
+        Reading from request is allowed after accessing request contents as
+        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):
+    def test_value_after_read_raw_post_data(self):
         """
         Construction of POST or raw_post_data is not allowed after reading
         from request.
         """
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            payload = 'name=value'
+            request = WSGIRequest({'REQUEST_METHOD': 'POST',
+                                   'CONTENT_LENGTH': len(payload),
+                                   'wsgi.input': StringIO(payload)})
+            self.assertEqual(request.read(2), 'na')
+            self.assertRaises(Exception, lambda: request.raw_post_data)
+            self.assertEqual(request.POST, {})
+
+    def test_value_after_read(self):
+        """
+        Construction of POST or body is not allowed after reading
+        from request.
+        """
         payload = 'name=value'
         request = WSGIRequest({'REQUEST_METHOD': 'POST',
                                '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):
         """
         Reading raw_post_data after parsing multipart is not allowed
         """
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            # 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.
+            payload = "\r\n".join([
+                    '--boundary',
+                    'Content-Disposition: form-data; name="name"',
+                    '',
+                    'value',
+                    '--boundary--'
+                    ''])
+            request = WSGIRequest({'REQUEST_METHOD': 'POST',
+                                   'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
+                                   'CONTENT_LENGTH': len(payload),
+                                   'wsgi.input': StringIO(payload)})
+            self.assertEqual(request.POST, {u'name': [u'value']})
+            self.assertRaises(Exception, lambda: request.raw_post_data)
+
+    def test_body_after_POST_multipart(self):
+        """
+        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 +396,7 @@ def test_raw_post_data_after_POST_multipart(self):
                                '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):
         """
@@ -370,11 +430,26 @@ def test_POST_after_raw_post_data_read(self):
         """
         POST should be populated even if raw_post_data is read first
         """
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            payload = 'name=value'
+            request = WSGIRequest({'REQUEST_METHOD': 'POST',
+                                   'CONTENT_LENGTH': len(payload),
+                                   'wsgi.input': StringIO(payload)})
+            raw_data = request.raw_post_data
+            self.assertEqual(request.POST, {u'name': [u'value']})
+
+    def test_POST_after_body_read(self):
+        """
+        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):
@@ -382,11 +457,28 @@ def test_POST_after_raw_post_data_read_and_stream_read(self):
         POST should be populated even if raw_post_data is read first, and then
         the stream is read second.
         """
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            payload = 'name=value'
+            request = WSGIRequest({'REQUEST_METHOD': 'POST',
+                                   'CONTENT_LENGTH': len(payload),
+                                   'wsgi.input': StringIO(payload)})
+            raw_data = request.raw_post_data
+            self.assertEqual(request.read(1), u'n')
+            self.assertEqual(request.POST, {u'name': [u'value']})
+
+    def test_POST_after_body_read_and_stream_read(self):
+        """
+        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']})
 
@@ -395,6 +487,31 @@ def test_POST_after_raw_post_data_read_and_stream_read_multipart(self):
         POST should be populated even if raw_post_data is read first, and then
         the stream is read second. Using multipart/form-data instead of urlencoded.
         """
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            payload = "\r\n".join([
+                    '--boundary',
+                    'Content-Disposition: form-data; name="name"',
+                    '',
+                    'value',
+                    '--boundary--'
+                    ''])
+            request = WSGIRequest({'REQUEST_METHOD': 'POST',
+                                   'CONTENT_TYPE': 'multipart/form-data; boundary=boundary',
+                                   'CONTENT_LENGTH': len(payload),
+                                   'wsgi.input': StringIO(payload)})
+            raw_data = request.raw_post_data
+            # 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_POST_after_body_read_and_stream_read_multipart(self):
+        """
+        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([
                 '--boundary',
                 'Content-Disposition: form-data; name="name"',
@@ -406,7 +523,7 @@ def test_POST_after_raw_post_data_read_and_stream_read_multipart(self):
                                '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']})
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 7d0b0e4..160cfa6 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -975,15 +975,25 @@ def test_response_no_template(self):
 
 class ReadLimitedStreamTest(TestCase):
     """
-    Tests that ensure that HttpRequest.raw_post_data, HttpRequest.read() and
+    Tests that ensure that HttpRequest.body, HttpRequest.raw_post_data, 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
         the empty string."""
-        self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '')
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+            warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+            self.assertEquals(self.client.get("/test_client_regress/raw_post_data/").content, '')
+
+    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/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 93f7a2e..db3c9de 100644
--- a/tests/regressiontests/test_client_regress/urls.py
+++ b/tests/regressiontests/test_client_regress/urls.py
@@ -32,6 +32,7 @@
     (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 b398293..21c700c 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)
@@ -94,7 +96,15 @@ def check_headers(request):
 
 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)
+    with warnings.catch_warnings():
+        warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+        warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+        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()."
