diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py index 8bed5681cf..fc48aa9e7b 100644 --- a/django/http/multipartparser.py +++ b/django/http/multipartparser.py @@ -136,6 +136,7 @@ class MultiPartParser(object): # since we cannot be sure a file is complete until # we hit the next boundary/part of the multipart content. self.handle_file_complete(old_field_name, counters) + old_field_name = None try: disposition = meta_data['content-disposition'][1] diff --git a/django/test/client.py b/django/test/client.py index 87731043a7..47c12a4ca1 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -90,32 +90,34 @@ def encode_multipart(boundary, data): """ lines = [] to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET) + + # Not by any means perfect, but good enough for our purposes. + is_file = lambda thing: hasattr(thing, "read") and callable(thing.read) + + # Each bit of the multipart form data could be either a form value or a + # file, or a *list* of form values and/or files. Remember that HTTP field + # names can be duplicated! for (key, value) in data.items(): - if isinstance(value, file): - lines.extend([ - '--' + boundary, - 'Content-Disposition: form-data; name="%s"; filename="%s"' \ - % (to_str(key), to_str(os.path.basename(value.name))), - 'Content-Type: application/octet-stream', - '', - value.read() - ]) - else: - if not isinstance(value, basestring) and is_iterable(value): - for item in value: + if is_file(value): + lines.extend(encode_file(boundary, key, value)) + elif not isinstance(value, basestring) and is_iterable(value): + for item in value: + if is_file(item): + lines.extend(encode_file(boundary, key, item)) + else: lines.extend([ '--' + boundary, 'Content-Disposition: form-data; name="%s"' % to_str(key), '', to_str(item) ]) - else: - lines.extend([ - '--' + boundary, - 'Content-Disposition: form-data; name="%s"' % to_str(key), - '', - to_str(value) - ]) + else: + lines.extend([ + '--' + boundary, + 'Content-Disposition: form-data; name="%s"' % to_str(key), + '', + to_str(value) + ]) lines.extend([ '--' + boundary + '--', @@ -123,6 +125,17 @@ def encode_multipart(boundary, data): ]) return '\r\n'.join(lines) +def encode_file(boundary, key, file): + to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET) + return [ + '--' + boundary, + 'Content-Disposition: form-data; name="%s"; filename="%s"' \ + % (to_str(key), to_str(os.path.basename(file.name))), + 'Content-Type: application/octet-stream', + '', + file.read() + ] + class Client: """ A class that can act as a client for testing purposes. diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py index 8992298470..8a61966240 100644 --- a/tests/regressiontests/file_uploads/tests.py +++ b/tests/regressiontests/file_uploads/tests.py @@ -147,12 +147,35 @@ class FileUploadTests(TestCase): def test_broken_custom_upload_handler(self): f = tempfile.NamedTemporaryFile() f.write('a' * (2 ** 21)) - + # AttributeError: You cannot alter upload handlers after the upload has been processed. self.assertRaises( AttributeError, self.client.post, - '/file_uploads/quota/broken/', + '/file_uploads/quota/broken/', {'f': open(f.name)} - ) - \ No newline at end of file + ) + + def test_fileupload_getlist(self): + file1 = tempfile.NamedTemporaryFile() + file1.write('a' * (2 ** 23)) + + file2 = tempfile.NamedTemporaryFile() + file2.write('a' * (2 * 2 ** 18)) + + file2a = tempfile.NamedTemporaryFile() + file2a.write('a' * (5 * 2 ** 20)) + + response = self.client.post('/file_uploads/getlist_count/', { + 'file1': open(file1.name), + 'field1': u'test', + 'field2': u'test3', + 'field3': u'test5', + 'field4': u'test6', + 'field5': u'test7', + 'file2': (open(file2.name), open(file2a.name)) + }) + got = simplejson.loads(response.content) + + self.assertEqual(got.get('file1'), 1) + self.assertEqual(got.get('file2'), 2) diff --git a/tests/regressiontests/file_uploads/urls.py b/tests/regressiontests/file_uploads/urls.py index 529bee312d..cc4cc80fdd 100644 --- a/tests/regressiontests/file_uploads/urls.py +++ b/tests/regressiontests/file_uploads/urls.py @@ -7,4 +7,5 @@ urlpatterns = patterns('', (r'^echo/$', views.file_upload_echo), (r'^quota/$', views.file_upload_quota), (r'^quota/broken/$', views.file_upload_quota_broken), + (r'^getlist_count/$', views.file_upload_getlist_count), ) diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py index 833cf90531..dfa877da3a 100644 --- a/tests/regressiontests/file_uploads/views.py +++ b/tests/regressiontests/file_uploads/views.py @@ -67,4 +67,14 @@ def file_upload_quota_broken(request): """ response = file_upload_echo(request) request.upload_handlers.insert(0, QuotaUploadHandler()) - return response \ No newline at end of file + return response + +def file_upload_getlist_count(request): + """ + Check the .getlist() function to ensure we receive the correct number of files. + """ + file_counts = {} + + for key in request.FILES.keys(): + file_counts[key] = len(request.FILES.getlist(key)) + return HttpResponse(simplejson.dumps(file_counts))