django rest framework file upload with nested writ

2019-06-17 04:09发布

问题:

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 :).

回答1:

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 ViewSet

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



回答2:

What do you mean that having a hard time gettting the nested list of image annotations include in the request? When you send the multpart/form-data post request, is the nested notation list data included in the request.data? (Please use request.data instead of request.DATA and request.FILES). Please use some debug tools like pdb to check your request.data.

For supporting writable nested serializer, I think you should overrite the create() function for POST method, and you can find more details from here. Sorry for not giving the answer directly, I need more details about your models.

And if you want to post JSON instead of multipart/form-data, you can use base64 for representing the figure (But the size of the file will grow almost 33% larger).



回答3:

you can use formencode.variabledecode.variable_decode()

example:

class ImageSerializer(serializers.HyperlinkedModelSerializer):

    ...

    def to_internal_value(self, value):
        return super(ImageSerializer, self).to_internal_value(variable_decode(value))