Django REST:上传和序列化多张图片

17

我有两个模型TaskTaskImage,它们是属于Task对象的图像集合。

我的目标是能够向我的Task对象添加多张图像,但我只能使用两个模型来实现。目前,当我添加图像时,它不允许我上传它们并保存新的对象。

settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

serializers.py

class TaskImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = TaskImage
        fields = ('image',)


class TaskSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.ReadOnlyField(source='user.username')
    images = TaskImageSerializer(source='image_set', many=True, read_only=True)

    class Meta:
        model = Task
        fields = '__all__'

    def create(self, validated_data):
        images_data = validated_data.pop('images')
        task = Task.objects.create(**validated_data)
        for image_data in images_data:
            TaskImage.objects.create(task=task, **image_data)
        return task

models.py

class Task(models.Model):
    title = models.CharField(max_length=100, blank=False)
    user = models.ForeignKey(User)

    def save(self, *args, **kwargs):
        super(Task, self).save(*args, **kwargs)

class TaskImage(models.Model):
    task = models.ForeignKey(Task, on_delete=models.CASCADE)
    image = models.FileField(blank=True)

然而,当我进行POST请求时:

enter image description here

我得到了以下的回溯信息:

在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/django/core/handlers/exception.py"的第41行,发生异常;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/django/core/handlers/base.py"的第187行,处理异常;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/django/core/handlers/base.py"的第185行,对请求进行处理;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/django/views/decorators/csrf.py"的第58行,装饰器包装视图函数;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/rest_framework/viewsets.py"的第95行,调用视图;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/rest_framework/views.py"的第494行,处理异常;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/rest_framework/views.py"的第454行,抛出未捕获的异常;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/rest_framework/views.py"的第491行,处理请求;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/rest_framework/mixins.py"的第21行,创建对象;在"/Users/gr/Desktop/PycharmProjects/godo/api/views.py"的第152行,执行创建操作;在"/Applications/Anaconda/anaconda/envs/godo/lib/python3.6/site-packages/rest_framework/serializers.py"的第214行,保存对象;在"/Users/gr/Desktop/PycharmProjects/godo/api/serializers.py"的第67行,获取images数据时发生KeyError异常。
2个回答

23

问题描述

异常的起因是 KeyError,原因在于以下语句

images_data = validated_data.pop('images')

这是因为经过验证的数据中没有键为images的数据。这意味着来自Postman的图像输入无法通过验证。

Django Post请求将InMemoryUpload存储在request.FILES中,因此我们使用它来获取文件。同时,您希望一次上传多个图像。因此,在上传图像时,您必须使用不同的图像名称(在Postman中)。

将您的serializer更改为如下:

class TaskSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.ReadOnlyField(source='user.username')
    images = TaskImageSerializer(source='taskimage_set', many=True, read_only=True)

    class Meta:
        model = Task
        fields = ('id', 'title', 'user', 'images')

    def create(self, validated_data):
        images_data = self.context.get('view').request.FILES
        task = Task.objects.create(title=validated_data.get('title', 'no-title'),
                                   user_id=1)
        for image_data in images_data.values():
            TaskImage.objects.create(task=task, image=image_data)
        return task

我不知道你的看法,但我更喜欢使用ModelViewSet这个优先级较高的视图类。

class Upload(ModelViewSet):
    serializer_class = TaskSerializer
    queryset = Task.objects.all()

Postman控制台:

在此输入图片描述

DRF结果:

{
        "id": 12,
        "title": "This Is Task Title",
        "user": "admin",
        "images": [
            {
                "image": "http://127.0.0.1:8000/media/Screenshot_from_2017-12-20_07-18-43_tNIbUXV.png"
            },
            {
                "image": "http://127.0.0.1:8000/media/game-of-thrones-season-valar-morghulis-wallpaper-1366x768_3bkMk78.jpg"
            },
            {
                "image": "http://127.0.0.1:8000/media/IMG_212433_lZ2Mijj.jpg"
            }
        ]
    }

更新

这是对您评论的回答。

在 Django 中,reverse foreignKey 使用 _set 进行捕获。请参阅官方文档。在这里,TaskTaskImage 处于 OneToMany 关系,所以如果您有一个 Task 实例,您可以通过这个 反向查找 功能获取所有相关的 TaskImage 实例。

这是一个例子:

task_instance = Task.objects.get(id=1)
task_img_set_all = task_instance.taskimage_set.all()

在这里,task_img_set_all将等于TaskImage.objects.filter(task_id=1)


如果您需要,可以使用此git-repo https://bitbucket.org/jerinpetergeorge/django_1_11/src/97728beb7b7979464f98ce50d9c70e7525a948ff/?at=SO-48756249 - JPG
谢谢,这很完美。我注意到在serializers中你使用了source=taskimage_set,我不确定为什么会这样?更合理的应该是task_image_set或者更一般的model-field_set,这只是用于一对多关系的惯例吗? - GRS
1
请查看答案底部的更新部分。 - JPG
3
当您创建TaskImage(TaskImage.objects.create(task=task, image=image_data))时,是否应该使用序列化程序进行创建?如果不这样做,我会认为您正在绕过验证阶段,并且未经验证即保存对象。 - geekscrap
不完全是这样,因为图像是根据serializer.ImageField()字段进行验证的。 - JPG
我找到了一个解决方案,可以使用表单数据上传多个图像并发送嵌套的JSON。您可以查看我的答案 - Metehan Gülaç

1
您在TaskImageSerializer嵌套字段中将read_only设置为true。因此,那里将没有validated_data。

我已经将其删除,但错误和回溯仍然相同。 - GRS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接