在Django REST Framework中使用multipart/form-data上传多个图像和嵌套的json [英] Uploading multiple images and nested json using multipart/form-data in Django REST Framework

查看:154
本文介绍了在Django REST Framework中使用multipart/form-data上传多个图像和嵌套的json的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在解析视图集中的 request.data 时遇到问题.我有一个模型,可以根据产品添加多个图像.

I have a problem with parsing request.data in viewset. I have a model that can add multiple images depending on a product.

我要从传入数据中拆分 图像 ,将 产品数据 发送到ProductSerializer,然后将 图像 产品数据 一起发送到其序列化器并保存.

I want to split the image from the incoming data, send product data to ProductSerializer, and after that send image to its serializer with product data and save it.

我有两个模型,就像这样:

I have two model, simply like this:

def Product(models.Model):
    name = models.CharField(max_length=20)
    color = models.ForeignKey(Color, on_delete=models.CASCADE)

def Color(models.Model):
    name = models.CharField(max_length=15)

def ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='product_pics/')

我要发送到 产品 的请求(127.0.0.1:8000/products/)就像这样:

The request I want to send to the Product (127.0.0.1:8000/products/) is simply like:

{
    "name": "strawberry",
    "color": {
        "name": "red"
    },
    "productimage_set": [
        {"image": "<some_encode_image_data>"}
    ]
}

序列化器没有什么特别的,它只是提取标签链接,所以我没有写它.如何发送 multipart/form-data ,如何在视图集中进行解析?或解决方案是什么?

There is nothing special in the serializer, it just extracts the tags link, so I did not write it. How do I send multipart/form-data and how can I parse it in the viewset? or what is the solution?

推荐答案

我开发了一个解决方案.使用邮递员,我发送了包含多个图像,单个数据和嵌套数据的multipart/form-data.

I developed a solution. Using Postman, I sent multipart/form-data containing multiple images, single and nested data.

在我的模型文件中,我将Tags模型添加为ManyToManyField作为示例,并添加了django-taggit. 表单数据 将如图所示.

In my model file, I added the Tags model as ManyToManyField to be an example, and also django-taggit. form-data will be like in the picture.

models.py

class Product(models.Model):
    name = models.CharField(max_length=20, blank=True)
    tags = models.ManyToManyField(Tags)
    taggit = TaggableManager(blank=True)

class ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='image_path/', null=True, blank=True)

class Tags(models.Model):
    name = models.CharField(max_length=15, blank=True)

第一件事首先;第一个数据未正确解析.作为对此的一种解决方案,并在 answer 的帮助下,我创建了此自定义 解析器 :

First things first; the first data was not parsed correctly. As a solution to this and with the help of that answer, I created this custom parser:

class MultipartJsonParser(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 = {}

        for key, value in result.data.items():
            if type(value) != str:
                data[key] = value
                continue
            if '{' in value or "[" in value:
                try:
                    data[key] = json.loads(value)
                except ValueError:
                    data[key] = value
            else:
                data[key] = value
        return parsers.DataAndFiles(data, result.files)

现在,我们可以使用此解析器和Django REST内置JSONParser解析数据.现在该建立我们的 视图集 .

Now we can parse our data with this parser and Django REST built-in JSONParser. Now it's time to build our viewsets.

class ProductViewSet(ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    parser_classes = [MultipartJsonParser, JSONParser]

    def get_serializer_context(self):
        context = super(ProductViewSet, self).get_serializer_context()

        # appending extra data to context
        if len(self.request.FILES) > 0:
            context.update({
                'included_images': self.request.FILES
            })

        return context

    def create(self, request, *args, **kwargs):
        # Validating images with its own serializer, but not creating.
        # The adding process must be through Serializer.
        try:
            image_serializer = ProductImageSerializer(data=request.FILES)
            image_serializer.is_valid(raise_exception=True)
        except Exception:
            raise NotAcceptable(
                detail={
                    'message': 'Upload a valid image. The file you uploaded was either not '
                               'an image or a corrupted image.'}, code=406)

        # the rest of method is about the product serialization(with extra context), 
        # validation and creation.
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

class ProductImageViewSet(ModelViewSet):
    queryset = ProductImage.objects.all()
    serializer_class = ProductImageSerializer

class TagsViewSet(ModelViewSet):
    queryset = Tags.objects.all()
    serializer_class = TagsSerializer

让我们在这里进行检查.正如我在评论中提到的,图像文件将包含在request.FILES中.因此,我首先将数据发送到 ProductImageSerializer 并进行了验证.如果发生验证错误,该过程将停止并且API将发送一条错误消息作为响应.然后,我将数据和图片信息一起通过 get_serializer_context 方法发送到ProductSerializer.

Let's examine here. As I mentioned in the comments, the image files will be included in request.FILES. For this reason, I first sent the data to the ProductImageSerializer and validated it. If a validation error occurs, the process will stop and the API will send an error message as a response. Then I sent the data to the ProductSerializer with the picture information I appended to the context in the get_serializer_context method.

我们已经完成了 create 方法,其他细节都写在了代码上.

We are done with the create method, other details are written on the code.

最后, serializer.py

Finally, serializer.py

from django.forms import ImageField as DjangoImageField

class TagsSerializer(HyperlinkedModelSerializer):
    class Meta:
    model = Tags
    fields = ['url', 'pk', 'name']

class ProductImageSerializer(HyperlinkedModelSerializer):
    class Meta:
        model = ProductImage
        fields = ['url', 'pk', 'product', 'image']
        # attention!!! if you not use this bottom line,
        # it will show error like "product required" and
        # indirectly our validation at ProductViewSet will raise error.
        extra_kwargs = {
            'product': {'required': False}
        }
    # we created Object-level custom validation because validation not working correctly.
    # when ProductImageSerializer get single image, everything just fine but
    # when it get multiple image, serializer is just passing all the files.
    def validate(self, attrs):
        default_error_messages = {
            'invalid_image':
                'Upload a valid image. The file you uploaded was either not an image or a corrupted image.',
        }
        # in here we're verifying image with using django.forms; Pillow not necessary !!
        for i in self.initial_data.getlist('image'):
            django_field = DjangoImageField()
            django_field.error_messages = default_error_messages
            django_field.clean(i)
        return attrs

class ProductSerializer(HyperlinkedModelSerializer, TaggitSerializer):
    tags = TagsSerializer(allow_null=True, many=True, required=False)
    # you can delete this line. If you delete it, it will appear as url in response.
    productimage_set = ProductImageSerializer(allow_null=True, many=True, required=False)
    taggit = TagListSerializerField(allow_null=True, required=False)

    class Meta:
        model = Product
        fields = ['url', 'pk', 'name', 'tags', 'taggit', 'productimage_set']

    def create(self, validated_data):
        # create product
        try:
            product_obj = Product.objects.create(
                name=validated_data['name']
            )
        except Exception:
            raise NotAcceptable(detail={'message': 'The request is not acceptable.'}, code=406)

        if 'included_images' in self.context:  # checking if key is in context
            images_data = self.context['included_images']
            for i in images_data.getlist('image'):
                ProductImage.objects.create(
                    product=product_obj,
                    image=i
                )

        # pop taggit and create
        if 'taggit' in validated_data:
            taggit_data = validated_data.pop('taggit')
            for taggit_data in taggit_data:
                taggit_obj, created = Tag.objects.get_or_create(name=taggit_data)
                product_obj.taggit.add(taggit_obj)

        # pop tags and create
        if 'tags' in validated_data:
            tags_data = validated_data.pop('tags')
            for tags_data in tags_data:
                for i in tags_data.items():
                    tags_obj, created = Tags.objects.get_or_create(name=i[1])
                    product_obj.tags.add(tags_obj)

        return product_obj

那么这里发生了什么?为什么我们要为图像创建额外的验证?尽管我不知道为什么,但是ImageSerializer仅对单个文件进行正确的验证.如果您尝试上传两个文件,甚至可以在图片旁边放一个电影,验证将无法进行.为了防止这种情况,我们使用django的内置形式依次验证了图片;更改.mp3的格式并将其设置为.jpg,尝试上传大文件,但它们均无法正常工作.进行验证的是纯django.其他详细信息在代码中.

So what happened here? Why did we create an extra validation for the image? Although I don't know why, ImageSerializer only makes the right validation for a single file. If you try to upload two files, you can even put a movie next to the picture, validation will not work. To prevent this, we validate the pictures in order using the built-in form of django; Change the format of .mp3 and make it .jpg, try to upload files of high size, none of them will work. The thing that makes the verification is pure django. Other details are in the code.

如果您按照我说的做所有事情,响应将是这样的:

If you do everything as I stated, the response will be like this:

我认为这会使大多数 邮递员 用户满意.希望对您有所帮助.如果有什么引起您的注意,让我们见面吧.

I think this will make most Postman users happy. I hope it helps. If anything catches your attention, let's meet in comments.

这篇关于在Django REST Framework中使用multipart/form-data上传多个图像和嵌套的json的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆