Codebase improvements on FileUploadParser

* Added docstrings.
* Added `FileUploadParser.get_filename` to make it easier to override.
* Added url kwargs filename detection step.
* Updated tests corresponding to these changes.
This commit is contained in:
Michael Elovskikh 2013-05-04 14:58:21 +06:00
parent 318fdaabe5
commit e36e4f48ad
2 changed files with 40 additions and 15 deletions

View File

@ -215,16 +215,19 @@ class FileUploadParser(BaseParser):
media_type = '*/*' media_type = '*/*'
def parse(self, stream, media_type=None, parser_context=None): def parse(self, stream, media_type=None, parser_context=None):
"""
Returns a DataAndFiles object.
`.data` will be None (we expect request body to be a file content).
`.files` will be a `QueryDict` containing one 'file' elemnt - a parsed file.
"""
parser_context = parser_context or {} parser_context = parser_context or {}
request = parser_context['request'] request = parser_context['request']
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
meta = request.META meta = request.META
upload_handlers = request.upload_handlers
try: filename = self.get_filename(stream, media_type, parser_context)
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'])
filename = disposition[1]['filename']
except KeyError:
filename = None
content_type = meta.get('HTTP_CONTENT_TYPE', meta.get('CONTENT_TYPE', '')) content_type = meta.get('HTTP_CONTENT_TYPE', meta.get('CONTENT_TYPE', ''))
try: try:
@ -233,28 +236,28 @@ class FileUploadParser(BaseParser):
content_length = None content_length = None
# See if the handler will want to take care of the parsing. # See if the handler will want to take care of the parsing.
for handler in request.upload_handlers: for handler in upload_handlers:
result = handler.handle_raw_input(None, result = handler.handle_raw_input(None,
meta, meta,
content_length, content_length,
None, None,
encoding) encoding)
if result is not None: if result is not None:
return DataAndFiles(result[0], {'file': result[1]}) return DataAndFiles(None, {'file': result[1]})
possible_sizes = [x.chunk_size for x in request.upload_handlers if x.chunk_size] possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
chunk_size = min([2**31-4] + possible_sizes) chunk_size = min([2**31-4] + possible_sizes)
chunks = ChunkIter(stream, chunk_size) chunks = ChunkIter(stream, chunk_size)
counters = [0] * len(request.upload_handlers) counters = [0] * len(upload_handlers)
for handler in request.upload_handlers: for handler in upload_handlers:
try: try:
handler.new_file(None, filename, content_type, content_length, encoding) handler.new_file(None, filename, content_type, content_length, encoding)
except StopFutureHandlers: except StopFutureHandlers:
break break
for chunk in chunks: for chunk in chunks:
for i, handler in enumerate(request.upload_handlers): for i, handler in enumerate(upload_handlers):
chunk_length = len(chunk) chunk_length = len(chunk)
chunk = handler.receive_data_chunk(chunk, counters[i]) chunk = handler.receive_data_chunk(chunk, counters[i])
counters[i] += chunk_length counters[i] += chunk_length
@ -262,7 +265,23 @@ class FileUploadParser(BaseParser):
# If the chunk received by the handler is None, then don't continue. # If the chunk received by the handler is None, then don't continue.
break break
for i, handler in enumerate(request.upload_handlers): for i, handler in enumerate(upload_handlers):
file_obj = handler.file_complete(counters[i]) file_obj = handler.file_complete(counters[i])
if file_obj: if file_obj:
return DataAndFiles(None, {'file': file_obj}) return DataAndFiles(None, {'file': file_obj})
def get_filename(self, stream, media_type, parser_context):
"""
Detects the uploaded file name. First searches a 'filename' url kwarg.
Then tries to parse Content-Disposition header.
"""
try:
return parser_context['kwargs']['filename']
except KeyError:
pass
try:
meta = parser_context['request'].META
disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'])
return disposition[1]['filename']
except (AttributeError, KeyError):
pass

View File

@ -99,11 +99,17 @@ class TestFileUploadParser(TestCase):
'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt'.encode('utf-8'), 'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt'.encode('utf-8'),
'HTTP_CONTENT_LENGTH': 14, 'HTTP_CONTENT_LENGTH': 14,
} }
self.parser_context = {'request': request} self.parser_context = {'request': request, 'kwargs': {}}
def test_parse(self): def test_parse(self):
""" Make sure the `QueryDict` works OK """ """ Make sure the `QueryDict` works OK """
parser = FileUploadParser() parser = FileUploadParser()
data_and_files = parser.parse(self.stream, parser_context=self.parser_context) self.stream.seek(0)
data_and_files = parser.parse(self.stream, None, self.parser_context)
file_obj = data_and_files.files['file'] file_obj = data_and_files.files['file']
self.assertEqual(file_obj._size, 14) self.assertEqual(file_obj._size, 14)
def test_get_filename(self):
parser = FileUploadParser()
filename = parser.get_filename(self.stream, None, self.parser_context)
self.assertEqual(filename, 'file.txt'.encode('utf-8'))