diff --git a/AUTHORS b/AUTHORS
index bf10e80afd..4adfe2310b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -650,6 +650,7 @@ answer newbie questions, and generally made Django that much better:
Thomas Sorrel
Thomas Steinacher
Thomas Stromberg
+ Thomas Tanner
tibimicu@gmx.net
Tim Graham
Tim Heap
diff --git a/django/test/client.py b/django/test/client.py
index b2633bd7e7..62f7c5fb6c 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -195,20 +195,25 @@ def encode_multipart(boundary, data):
def encode_file(boundary, key, file):
to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
+ filename = os.path.basename(file.name) if hasattr(file, 'name') else ''
if hasattr(file, 'content_type'):
content_type = file.content_type
+ elif filename:
+ content_type = mimetypes.guess_type(filename)[0]
else:
- content_type = mimetypes.guess_type(file.name)[0]
+ content_type = None
if content_type is None:
content_type = 'application/octet-stream'
+ if not filename:
+ filename = key
return [
to_bytes('--%s' % boundary),
to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"'
- % (key, os.path.basename(file.name))),
+ % (key, filename)),
to_bytes('Content-Type: %s' % content_type),
b'',
- file.read()
+ to_bytes(file.read())
]
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index d95599f613..375a39920f 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -511,6 +511,8 @@ Tests
:meth:`TestCase.setUpTestData() `. Using
this technique can speed up the tests as compared to using ``setUp()``.
+* Added test client support for file uploads with file-like objects.
+
Validators
^^^^^^^^^^
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 454da239e0..69649d7a2e 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -238,6 +238,13 @@ Use the ``django.test.Client`` class to make requests.
(The name ``attachment`` here is not relevant; use whatever name your
file-processing code expects.)
+ You may also provide any file-like object (e.g., :class:`~io.StringIO` or
+ :class:`~io.BytesIO`) as a file handle.
+
+ .. versionadded:: 1.8
+
+ The ability to use a file-like object was added.
+
Note that if you wish to use the same file handle for multiple
``post()`` calls then you will need to manually reset the file
pointer between posts. The easiest way to do this is to
diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
index 85363744d8..37954a99ca 100644
--- a/tests/file_uploads/tests.py
+++ b/tests/file_uploads/tests.py
@@ -17,7 +17,7 @@ from django.test import TestCase, client
from django.test import override_settings
from django.utils.encoding import force_bytes
from django.utils.http import urlquote
-from django.utils.six import StringIO
+from django.utils.six import BytesIO, StringIO
from . import uploadhandler
from .models import FileModel
@@ -262,6 +262,34 @@ class FileUploadTests(TestCase):
self.assertLess(len(got), 256,
"Got a long file name (%s characters)." % len(got))
+ def test_file_content(self):
+ tdir = tempfile.gettempdir()
+
+ file = tempfile.NamedTemporaryFile
+ with file(suffix=".ctype_extra", dir=tdir) as no_content_type, \
+ file(suffix=".ctype_extra", dir=tdir) as simple_file:
+ no_content_type.write(b'no content')
+ no_content_type.seek(0)
+
+ simple_file.write(b'text content')
+ simple_file.seek(0)
+ simple_file.content_type = 'text/plain'
+
+ string_io = StringIO('string content')
+ bytes_io = BytesIO(b'binary content')
+
+ response = self.client.post('/echo_content/', {
+ 'no_content_type': no_content_type,
+ 'simple_file': simple_file,
+ 'string': string_io,
+ 'binary': bytes_io,
+ })
+ received = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(received['no_content_type'], 'no content')
+ self.assertEqual(received['simple_file'], 'text content')
+ self.assertEqual(received['string'], 'string content')
+ self.assertEqual(received['binary'], 'binary content')
+
def test_content_type_extra(self):
"""Uploaded files may have content type parameters available."""
tdir = tempfile.gettempdir()