From d6d51c9546a8734683c75bddc2cab93e05247f1e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 17 Feb 2007 00:23:09 +0000 Subject: [PATCH] Fixed #3160 -- Added the ability to control the content type in a test client POST request. This is to allow easier testing of json-rpc/xml-rpc/soap etc interfaces. Thanks to Mikeal Rogers for the suggestion, and Ben for the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@4529 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/client.py | 18 ++++++++++------ docs/testing.txt | 20 +++++++++++------ tests/modeltests/test_client/models.py | 12 +++++++++-- tests/modeltests/test_client/urls.py | 1 + tests/modeltests/test_client/views.py | 30 +++++++++++++++++++++----- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index ca1a04e659..95900154c5 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -9,6 +9,9 @@ from django.http import urlencode, SimpleCookie from django.test import signals from django.utils.functional import curry +BOUNDARY = 'BoUnDaRyStRiNg' +MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY + class ClientHandler(BaseHandler): """ A HTTP Handler that can be used for testing purposes. @@ -184,19 +187,20 @@ class Client: return self.request(**r) - def post(self, path, data={}, **extra): + def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): "Request a response from the server using POST." - BOUNDARY = 'BoUnDaRyStRiNg' + if content_type is MULTIPART_CONTENT: + post_data = encode_multipart(BOUNDARY, data) + else: + post_data = data - encoded = encode_multipart(BOUNDARY, data) - stream = StringIO(encoded) r = { - 'CONTENT_LENGTH': len(encoded), - 'CONTENT_TYPE': 'multipart/form-data; boundary=%s' % BOUNDARY, + 'CONTENT_LENGTH': len(post_data), + 'CONTENT_TYPE': content_type, 'PATH_INFO': path, 'REQUEST_METHOD': 'POST', - 'wsgi.input': stream, + 'wsgi.input': StringIO(post_data), } r.update(extra) diff --git a/docs/testing.txt b/docs/testing.txt index 6b6aecba38..33014723cd 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -217,15 +217,21 @@ can be invoked on the ``Client`` instance. http://yoursite.com/customers/details/?name=fred&age=7 -``post(path, data={})`` - Make a POST request on the provided ``path``. The key-value pairs in the - data dictionary will be used to create the POST data payload. This payload - will be transmitted with the mimetype ``multipart/form-data``. - - However submitting files is a special case. To POST a file, you need only +``post(path, data={}, content_type=MULTIPART_CONTENT)`` + Make a POST request on the provided ``path``. If you provide a content type + (e.g., ``text/xml`` for an XML payload), the contents of ``data`` will be + sent as-is in the POST request, using the content type in the HTTP + ``Content-Type`` header. + + If you do not provide a value for ``content_type``, the values in + ``data`` will be transmitted with a content type of ``multipart/form-data``. + The key-value pairs in the data dictionary will be encoded as a multipart + message and used to create the POST data payload. + + Submitting files is a special case. To POST a file, you need only provide the file field name as a key, and a file handle to the file you wish to upload as a value. The Test Client will populate the two POST fields (i.e., - ``field`` and ``field_file``) required by FileField. For example:: + ``field`` and ``field_file``) required by Django's FileField. For example:: c = Client() f = open('wishlist.doc') diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 74f5e9700c..c3febfb99f 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -43,7 +43,7 @@ class ClientTest(unittest.TestCase): # Check some response details self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, 'Empty POST Template') + self.assertEqual(response.template.name, 'Empty GET Template') def test_empty_post(self): "POST an empty dictionary to a view" @@ -53,7 +53,7 @@ class ClientTest(unittest.TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty POST Template') - def test_post_view(self): + def test_post(self): "POST some data to a view" post_data = { 'value': 37 @@ -66,6 +66,14 @@ class ClientTest(unittest.TestCase): self.assertEqual(response.template.name, 'POST Template') self.failUnless('Data received' in response.content) + def test_raw_post(self): + test_doc = """BlinkMalcolm Gladwell""" + response = self.client.post("/test_client/raw_post_view/", test_doc, + content_type="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/') diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 96da4ec34e..0bef1e9b71 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -4,6 +4,7 @@ import views 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), (r'^session_view/$', views.session_view), diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index b1f2739845..653e9a10e9 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -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 @@ -13,15 +14,34 @@ def post_view(request): """A view that expects a POST, and returns a different template depending on whether any POST data is available """ - if request.POST: - t = Template('Data received: {{ data }} is the value.', name='POST Template') - c = Context({'data': request.POST['value']}) + if request.method == 'POST': + if request.POST: + t = Template('Data received: {{ data }} is the value.', name='POST Template') + c = Context({'data': request.POST['value']}) + else: + t = Template('Viewing POST page.', name='Empty POST Template') + c = Context() else: - t = Template('Viewing POST page.', name='Empty POST Template') + t = Template('Viewing GET page.', name='Empty GET Template') c = Context() - + 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.method == '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/')