django rest framework file upload with nested writable serializers
class AnnotationSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Annotation class ImageSerializer(serializers.HyperlinkedModelSerializer): annotations = AnnotationSerializer(many=True, required=False) class Meta: depth = 1 model = Image exclude = ('owner‘,)
An annotation has an image foreign key attribute and so images have potentially multiple annotations. I’d like to create an image with nested annotations via a post request to an images endpoint including the list of annotations for this image. Posting my data json encoded to the images endpoint does work and creates an image with appropriate annotations.
But when I try to upload an actual image, I have to use a multipart/form-encoded post request instead of a json one to make fileupload possible. And now I’m having a hard time getting my nested list of image annotations included in this request. Maybe I could put a json encoded string in some form-field and manually parse it in the view, overwriting request.DATA, but this seems to be really ugly.
I wonder whether there’s a better way to achieve what I’m trying to do :).
The neatest way I’ve found to solve this problem is to write a custom parser which parses the incoming multipart request. I’ve been using formencode to do the actual parsing but you could use any nested formdata library. This takes surprisingly little code:
from rest_framework import parsers from formencode.variabledecode import variable_decode class MultipartFormencodeParser(parsers.MultiPartParser): def parse(self, stream, media_type=None, parser_context=None): result = super().parse( stream, media_type=media_type, parser_context=parser_context ) data = variable_decode(result.data) return parsers.DataAndFiles(data, result.files)
And then in your
class ImageViewSet(viewsets.ModelViewSet): ... parsers = (MultipartFormencodeParser,) ...
Formencode represents lists as
<key>-<index> entries in the encoded form data and nested properties as
<item>.<proprety>. So in your example the client would need to encode annotations as something like
"annotation-1.name" in the request. Obviously you will still need to handle the update of nested data manually in the serializer as mentioned in the rest framework documentation here