多文件上传DRF

gee*_*rap 9 python django validation upload django-rest-framework

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

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/')
Run Code Online (Sandbox Code Playgroud)

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

Run Code Online (Sandbox Code Playgroud)

主要问题:尽管上述方法可行,但直到我在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/
Run Code Online (Sandbox Code Playgroud)

TIA!

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

JPG*_*JPG 6

可能重复 --- Django REST:上传和序列化多个图像



从“ DRF可写嵌套序列化程序”文档中

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

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

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


更新1

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


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

图片1
img-1

2.覆盖序列化器的__init__()方法,SubmissionSerializerFileField()根据request.FILES数据动态添加尽可能多的属性。
我们可以以某种方式使用ListSerializerListField在这里。不幸的是我找不到办法:(

# 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)
Run Code Online (Sandbox Code Playgroud)

那么,file_fields这里的ID是什么?
由于表单数据键值对,因此每个文件数据都必须与键相关联。在图1中,您可以看到file_1file_2

3.现在我们需要传递file_fields来自的值view。由于此操作正在创建新实例,因此我们需要重写API类create()方法。

# 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)
Run Code Online (Sandbox Code Playgroud)


4.现在,所有值都将正确序列化。是时候重写映射关系的create()方法了SubmissionSerializer()

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
Run Code Online (Sandbox Code Playgroud)



5.就这样!


完整的代码片段

# 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)
Run Code Online (Sandbox Code Playgroud)

Screenhots和其他东西

1. POSTMAN控制台
POSTMAN控制台
2. Django Shell

In [2]: Submission.objects.all()                                                                                                                                                                                   
Out[2]: <QuerySet [<Submission: Submission object>]>

In [3]: sub_obj = Submission.objects.all()[0]                                                                                                                                                                      

In [4]: sub_obj                                                                                                                                                                                                    
Out[4]: <Submission: Submission object>

In [5]: sub_obj.__dict__                                                                                                                                                                                           
Out[5]: 
{'_state': <django.db.models.base.ModelState at 0x7f529a7ea240>,
 'id': 5,
 'issued_at': datetime.datetime(2019, 3, 27, 8, 45, 42, 193943, tzinfo=<UTC>),
 'completed': False,
 'atomic_id': 1}

In [6]: sub_obj.binary_files.all()                                                                                                                                                                                 
Out[6]: <QuerySet [<BinaryFile: uploads/binary/logo-800.png>, <BinaryFile: uploads/binary/Doc.pdf>, <BinaryFile: uploads/binary/invoice_2018_11_29_04_57_53.pdf>, <BinaryFile: uploads/binary/Screenshot_from_2019-02-13_16-22-53.png>]>

In [7]: for _ in sub_obj.binary_files.all(): 
   ...:     print(_) 
   ...:                                                                                                                                                                                                            
uploads/binary/logo-800.png
uploads/binary/Doc.pdf
uploads/binary/invoice_2018_11_29_04_57_53.pdf
uploads/binary/Screenshot_from_2019-02-13_16-22-53.png
Run Code Online (Sandbox Code Playgroud)


3. Django Admin Screenhot 在此处输入图片说明