多文件上传DRF

时间:2019-03-23 15:11:08

标签: python django validation django-rest-framework upload

我有一个要求,我希望允许在同一发布请求中上传多个文件以创建对象。我目前有一种方法可以执行此操作,但是在查看其他一些示例后,看来这并不是实现此目的的方法。

models.py

class Analyzer(models.Model):
    name = models.CharField(max_length=100, editable=False, unique=True)

class Atomic(models.Model):
    name = models.CharField(max_length=20, unique=True)

class Submission(models.Model):
    class Meta:
        ordering = ['-updated_at']

    issued_at = models.DateTimeField(auto_now_add=True, editable=False)
    completed = models.BooleanField(default=False)
    analyzers = models.ManyToManyField(Analyzer, related_name='submissions')
    atomic = models.ForeignKey(Atomic, verbose_name='Atomic datatype', related_name='submission', on_delete=models.CASCADE)

class BinaryFile(models.Model):
    class Meta:
        verbose_name = 'Binary file'
        verbose_name_plural = 'Binary files'
    def __str__(self):
        return self.file.name

    submission = models.ForeignKey(Submission, on_delete=models.CASCADE, related_name='binary_files')
    file = models.FileField(upload_to='uploads/binary/')

serializers.py

class BinaryFileSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.BinaryFile
        fields = '__all__'

class SubmissionCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Submission
        fields = ['id', 'completed', 'atomic', 'analyzers', 'binary_files']

    id = serializers.ReadOnlyField()
    completed = serializers.ReadOnlyField()

    atomic = serializers.PrimaryKeyRelatedField(many=False, queryset=models.Atomic.objects.all()
    analyzers = serializers.PrimaryKeyRelatedField(many=True, queryset=models.Analyzer.objects.all()

    binary_files = BinaryFileSerializer(required=True, many=True)

    def validate(self, data):
        # # I dont really like manually taking invalidated input!!
        data['binary_files'] = self.initial_data.getlist('binary_files')
        return data

    def create(self, validated_data):

        submission = models.Submission.objects.create(
            atomic=validated_data['atomic']
        )
        submission.analyzers.set(validated_data['analyzers'])

        # # Serialize the files - this seems too late to be doing this!
        for file in validated_data['binary_files']:
            binary_file = BinaryFileSerializer(
                data={'file': file, 'submission': submission.id}
            )

            if binary_file.is_valid():
                binary_file.save()

        return submission

主要问题:尽管上述方法可行,但直到我在create()中显式调用子序列化程序(BinaryFileSerializer)时,子序列化程序(BinaryFileSerializer)才被调用,这是在进行验证之后。为什么从来没有打电话给这个人?

我也不喜欢必须手动执行self.initial_data.getlist('binary_files')并将其手动添加到data的事实-这应该已经添加并验证了,不是吗?

我的想法是,按照我定义的binary_files = BinaryFileSerializer,应该调用此序列化程序来验证输入的特定字段吗?

仅供参考,我正在使用以下工具测试POST上传:

curl -F "binary_files=@file2.txt" -F "binary_files=@file2.txt" -F "atomic=7" -F "analyzers=12" -H "Accept: application/json; indent=4"  http://127.0.0.1:8000/api/submit/

TIA!

更新:现在的问题是,如果将validate()函数添加到BinaryFileSerializer,为什么不调用它?

1 个答案:

答案 0 :(得分:8)

可能重复 --- Django REST: Uploading and serializing multiple images



DRF Writable Nested Serializer doc

  

默认情况下,嵌套的序列化器是只读的。如果要支持对嵌套序列化器字段的写操作,则需要创建 create() 和/或 update() 方法,以便明确指定应如何保存子关系。

由此可见,除非明确调用,子序列化器(BinaryFileSerializer)不会调用其自己的create()方法。

您的 HTTP POST 请求的目的是创建新的 Submission 实例(和 { {1}} 实例)。创建过程将使用 BinaryFile 序列化器的 create() 方法进行,您将对其进行覆盖。因此,它将按照您的代码执行/执行。


UPDATE-1

要记住的事情
1. AFAIK,我们无法发送 嵌套SubmissionCreateSerializer
2.在这里,我仅尝试实现最少情况的方案
3.我已经使用POSTMAN rest api测试工具测试了该解决方案。
4.此方法可能很复杂(直到找到更好的方法为止)。
5.假设您的视图类是 multipart/form-data 类的子类


我要做什么?
1.由于我们无法以嵌套方式发送文件/数据,因此必须以平面模式发送它。

图片1
img-1

2。覆盖 ModelViewSet 序列化器的 __init__() 方法,并根据需要动态添加尽可能多的 SubmissionSerializer 属性 FileField() 数据。
我们可以在此处使用ListSerializerListField。不幸的是我找不到办法:(

request.FILES

那么,# init method of "SubmissionSerializer" def __init__(self, *args, **kwargs): file_fields = kwargs.pop('file_fields', None) super().__init__(*args, **kwargs) if file_fields: field_update_dict = {field: serializers.FileField(required=False, write_only=True) for field in file_fields} self.fields.update(**field_update_dict) 是什么ID?
因为 表单数据 键-value 对,每个文件数据必须与一个键相关联。在图像1 中,您可以看到file_fieldsfile_1

3.现在我们需要传递file_2中的file_fields值。由于此操作正在创建新实例,因此我们需要覆盖 API类 view 方法。

create()


4.现在,所有值都将正确序列化。是时候重写 # complete view code from rest_framework import status from rest_framework import viewsets class SubmissionAPI(viewsets.ModelViewSet): queryset = Submission.objects.all() serializer_class = SubmissionSerializer def create(self, request, *args, **kwargs): # main thing starts file_fields = list(request.FILES.keys()) # list to be passed to the serializer serializer = self.get_serializer(data=request.data, file_fields=file_fields) # main thing ends 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) create() 方法以映射关系了

SubmissionSerializer()



5.就是这样!


完整代码段

def create(self, validated_data):
    from django.core.files.uploadedfile import InMemoryUploadedFile
    validated_data_copy = validated_data.copy()
    validated_files = []
    for key, value in validated_data_copy.items():
        if isinstance(value, InMemoryUploadedFile):
            validated_files.append(value)
            validated_data.pop(key)
    submission_instance = super().create(validated_data)
    for file in validated_files:
        BinaryFile.objects.create(submission=submission_instance, file=file)
    return submission_instance

屏幕截图和其他内容

1。 POSTMAN控制台
POSTMAN console
2。 Django Shell

# serializers.py
from rest_framework import serializers
from django.core.files.uploadedfile import InMemoryUploadedFile


class SubmissionSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        file_fields = kwargs.pop('file_fields', None)
        super().__init__(*args, **kwargs)
        if file_fields:
            field_update_dict = {field: serializers.FileField(required=False, write_only=True) for field in file_fields}
            self.fields.update(**field_update_dict)

    def create(self, validated_data):
        validated_data_copy = validated_data.copy()
        validated_files = []
        for key, value in validated_data_copy.items():
            if isinstance(value, InMemoryUploadedFile):
                validated_files.append(value)
                validated_data.pop(key)
        submission_instance = super().create(validated_data)
        for file in validated_files:
            BinaryFile.objects.create(submission=submission_instance, file=file)
        return submission_instance

    class Meta:
        model = Submission
        fields = '__all__'


# views.py
from rest_framework import status
from rest_framework import viewsets


class SubmissionAPI(viewsets.ModelViewSet):
    queryset = Submission.objects.all()
    serializer_class = SubmissionSerializer

    def create(self, request, *args, **kwargs):
        # main thing starts
        file_fields = list(request.FILES.keys())  # list to be passed to the serializer
        serializer = self.get_serializer(data=request.data, file_fields=file_fields)
        # main thing ends

        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)


3。 Django Admin Screenhot enter image description here

相关问题