DRF nested params and multipart

Enabling nested params with file uploads in django rest framework

While DRF supports multipart file uploads, it does not support combining them with nested params, for example say you have a user profile that has a profile picture and you want to send an updated profile picture along with the user’s updated detail. Your profile model has an image field for the picture and a related user model. Say the user also changed his/her first name along with the portrait. Normally you would have two options.

  • Send two separate requests, one to update the user.first_name field (which is a nested field) which a json request and another for the picture which is a multipart request.
  • Flatten them out in the serializer instead of having user = UserSerializer() in your profile serializer have first_name = serializers.CharField(source='user.first_name') and use one multipart request.

This problem appears when your client code sets the field name to user[first_name] which will be stored in request.data under the key 'user[first_name]' instead of a nested dictionary request.data['user']['first_name'].

Thankfully it’s really straightforward to define a new parser for DRF.

I have published a pip package that remedies this problem which can be found here. For those interested stick around to see how it works.

I decided I was going to write my own variation of DRF’s MultiPartParser that’s going to add a processing step that unflattens the nested keys in the request.data dict as explained above.

from rest_framework import parsers
class NestedMultipartParser(parsers.MultiPartParser):
"""
Parser for processing nested field values as well as multipart files.
Author: Ahmed H. Ismail.
"""
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(stream=stream, media_type=media_type, parser_context=parser_context)
data = {}
for key, value in result.data.items():
if '[' in key and ']' in key:
# nested
index_left_bracket = key.index('[')
index_right_bracket = key.index(']')
nested_dict_key = key[:index_left_bracket]
nested_value_key = key[index_left_bracket + 1:index_right_bracket]
if nested_dict_key not in data:
data[nested_dict_key] = {}
data[nested_dict_key][nested_value_key] = value
else:
data[key] = value
return parsers.DataAndFiles(data, result.files)

As for using it

from drf_nested_field_multipart import NestedMultipartParser
from rest_framework.parsers import JSONParser
from rest_framework import viewsets
class MyViewSet(viewsets.ViewSet):
parser_classes = (JSONParser, NestedMultipartParser)

Now I can use ngFileUpload to process file uploads as well as nested data from my angular clients.