Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 4223)
+++ django/test/client.py	(working copy)
@@ -176,6 +187,22 @@
 
         return self.request(**r)
 
+    def raw_post(self, path, data, type, **extra):
+        """
+        Request a response from the server using POST. Raw post data and a
+        content type argument are supplied and passed unmodified.
+        """
+        r = {
+            'CONTENT_LENGTH': len(data),
+            'CONTENT_TYPE':   type,
+            'PATH_INFO':      path,
+            'REQUEST_METHOD': 'POST',
+            'wsgi.input':     StringIO(data),
+        }
+        r.update(extra)
+
+        return self.request(**r)
+
     def login(self, path, username, password, **extra):
         """
         A specialized sequence of GET and POST to log into a view that
Index: tests/modeltests/test_client/views.py
===================================================================
--- tests/modeltests/test_client/views.py	(revision 4223)
+++ tests/modeltests/test_client/views.py	(working copy)
@@ -1,3 +1,4 @@
+from xml.dom.minidom import parseString
 from django.template import Context, Template
 from django.http import HttpResponse, HttpResponseRedirect
 from django.contrib.auth.decorators import login_required
@@ -22,6 +23,21 @@
         
     return HttpResponse(t.render(c))
     
+def raw_post_view(request):
+    """A view which expects raw XML to be posted and returns content extracted
+    from the XML"""
+    if request.POST:
+        root = parseString(request.raw_post_data)
+        first_book = root.firstChild.firstChild
+        title, author = [n.firstChild.nodeValue for n in first_book.childNodes]
+        t = Template("{{ title }} - {{ author }}", name="Book template")
+        c = Context({"title": title, "author": author})
+    else:
+        t = Template("GET request.", name="Book GET template")
+        c = Context()
+
+    return HttpResponse(t.render(c))
+
 def redirect_view(request):
     "A view that redirects all requests to the GET view"
     return HttpResponseRedirect('/test_client/get_view/')
Index: tests/modeltests/test_client/models.py
===================================================================
--- tests/modeltests/test_client/models.py	(revision 4223)
+++ tests/modeltests/test_client/models.py	(working copy)
@@ -66,6 +66,14 @@
         self.assertEqual(response.template.name, 'POST Template')
         self.failUnless('Data received' in response.content)
         
+    def test_raw_post_view(self):
+        test_doc = """<?xml version="1.0" encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm Gladwell</author></book></library>"""
+        response = self.client.raw_post("/test_client/raw_post_view/", test_doc,
+                "text/xml")
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.template.name, "Book template")
+        self.assertEqual(response.content, "Blink - Malcolm Gladwell")
+
     def test_redirect(self):
         "GET a URL that redirects elsewhere"
         response = self.client.get('/test_client/redirect_view/')
Index: tests/modeltests/test_client/urls.py
===================================================================
--- tests/modeltests/test_client/urls.py	(revision 4223)
+++ tests/modeltests/test_client/urls.py	(working copy)
@@ -4,6 +4,7 @@
 urlpatterns = patterns('',
     (r'^get_view/$', views.get_view),
     (r'^post_view/$', views.post_view),
+    (r'^raw_post_view/$', views.raw_post_view),
     (r'^redirect_view/$', views.redirect_view),
     (r'^login_protected_view/$', views.login_protected_view),
 )
Index: docs/testing.txt
===================================================================
--- docs/testing.txt	(revision 4223)
+++ docs/testing.txt	(working copy)
@@ -242,6 +242,16 @@
     file name), and `attachment_file` (containing the file data). Note that you
     need to manually close the file after it has been provided to the POST.
 
+``raw_post(path, data, type)``
+    Make a POST request on the provided ``path``. The supplied data is sent
+    unmodified. Type is used as the value of the HTTP ``Content-Type`` header.
+    This can be used to send non-standard requests to a view, such as posting
+    XML to test SOAP views, or JSON, etc. For example::
+
+        c = Client()
+        book_data = '<book><title>Blink</title><author>Malcolm Gladwell</author></book>'
+        c.raw_post('/api/book/create/', book_data, "text/xml")
+
 ``login(path, username, password)``
     In a production site, it is likely that some views will be protected with
     the @login_required URL provided by ``django.contrib.auth``. Interacting
