diff --git a/starfields_drf_generics/parsers.py b/starfields_drf_generics/parsers.py new file mode 100644 index 0000000..d1ad7e2 --- /dev/null +++ b/starfields_drf_generics/parsers.py @@ -0,0 +1,73 @@ +from rest_framework.parsers import BaseParser, DataAndFiles +from django.http.multipartparser import MultiPartParserError +from rest_framework.exceptions import ParseError +from django.http.multipartparser import \ + MultiPartParser as DjangoMultiPartParser +from django.conf import settings +import json + + +class NestedJsonMultiPartParser(BaseParser): + """ + Parser for multipart form data, which may include file data. + """ + media_type = 'multipart/form-data' + + def parse(self, stream, media_type=None, parser_context=None): + """ + Parses the incoming bytestream as a multipart encoded form, + and returns a DataAndFiles object. + The main difference with the parser from rest_framework.parsers + is that it attempts to parse nested strings as json to fit with + better with json payloads. I also ensure that a single file is + passed per field instead of a list which was erroring out + FieldFile.to_internal_value. + + `.data` will be a dict containing all the form parameters. + `.files` will be a dict containing all the form files. + """ + parser_context = parser_context or {} + request = parser_context['request'] + encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) + meta = request.META.copy() + meta['CONTENT_TYPE'] = media_type + upload_handlers = request.upload_handlers + + try: + parser = DjangoMultiPartParser(meta, + stream, + upload_handlers, + encoding) + data, files = parser.parse() + + # Attempt to parse the multipart fields as json, this is not + # demanding since multiparts exist exclusively for file uploads + # which is much more demanding + data_dict = {} + for key in data.keys(): + values = data[key] + try: + data_dict[key] = json.loads(values) + except Exception as e: + data_dict[key] = values + + # Make sure the filenames become file names + for filename in files.keys(): + uploaded_file = files[filename] + hasfilename = hasattr(uploaded_file, '_name') + hasname = hasattr(uploaded_file, 'name') + if hasfilename and not hasname: + uploaded_file.name = uploaded_file._name + + # Turn the monstrous MultiValueDict into a normal dict so that + # there is only a single uploaded file per field passed + files_dict = {} + for key in files.keys(): + uploaded_file = files[key] + if isinstance(uploaded_file, list): + uploaded_file = uploaded_file[0] + files_dict[key] = uploaded_file + + return DataAndFiles(data_dict, files_dict) + except MultiPartParserError as exc: + raise ParseError('Multipart form parse error - %s' % str(exc))