多文件上传DRF [英] Multiple file upload DRF
问题描述
我有一个要求,我希望允许在同一发布请求中上传多个文件以创建对象。我目前有一种方法可以执行此操作,但是在查看其他示例后,似乎并没有达到预期的目的。
models.py
class Analyzer(models.Model):
名称= models.CharField(max_length = 100,editable = False,unique = True )
类Atomic(models.Model):
名称= models.CharField(max_length = 20,unique = True)
class Submission(models.Model) :
类Meta:
排序= ['-updated_at']
issue_at = models.DateTimeField(auto_now_add = True,editable = False)
完成=模型。 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 ='二进制文件'
verbose_name_plural ='二进制文件'
def __str __(self):
返回self.file.name
提交=模型.ForeignKey(提交,on_delete = models.CASCADE,related_name ='binary_files')
file = models.FileField(upload_to ='uploads / binary /')
serializers.py
class BinaryFileSerializer(serializers.ModelSerializer):
类Meta:
model = models.BinaryFile
字段='__all__'
类SubmissionCreateSerializer(serializers.ModelSerializer):
类元:
模型= models.Submission
字段= ['id', 'completed','atomic','analyzers','binary_files']
id = serializers.ReadOnlyField()
completed = serializers.ReadOnlyField()
原子= serializers.PrimaryKeyRelatedField(许多=假,queryset = models.Atomic.objects.all()
分析器= serializers.PrimaryKeyRelatedField(许多=真实,queryset = models.Analyzer.objects.all()
binary_files = BinaryFileSerializer(必填=真,很多=真)
def validate(self,data):
##我真的不喜欢手动获取无效的输入!
data ['binary_files'] = self.initial_data.getlist('binary_files')
返回数据
def create(self,validated_data):
提交= models.Submission.objects.create(
atomic = validated_data ['atomic']
)
Submit.analyzers.set(validated_data ['analyzers'])
##序列化文件-这似乎为时已晚!
for validated_data ['binary_files']中的文件:
binary_file = BinaryFileSerializer(
data = {'file':file,'submission':submitting.id}
)
如果binary_file.is_valid():
binary_file.save()
返回提交
主要问题:尽管上述工作有效,但直到我在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,为什么不调用它?
可能重复 ---
2。覆盖 SubmissionSerializer
序列化器的 __ init __()
方法并根据 request.FILES
FileField()
属性> 数据。
我们可以以某种方式使用
2。 Django Shell
在[2]中:Submission.objects.all()
输出[2]:< QuerySet [<提交:提交对象>]>
在[3]中:sub_obj = Submission.objects.all()[0]
在[4]:sub_obj
Out [4]:<提交:提交对象>
输入[5]:sub_obj .__ dict__
输出[5]:
{'_state':< django.db.models.base.ModelState at 0x7f529a7ea240> ;,
'id':5,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:上载/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>]>
在[7]中:sub_obj.binary_files.all()中的_:
...:打印(_)
...:
上传/ binary / logo-800.png
上传/binary/Doc.pdf
上传/binary/invoice_2018_11_29_04_57_53.pdf
上传/binary/Screenshot_from_2019-02-13_16-22-53.png
3。 Django Admin Screenhot
I have a requirement which I would like to allow multiple files to be uploaded within the same post request to create an object. I currently have a method of doing this, but after looking at some other examples it doesn't appear to be intended way to do it.
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
Main question: While the above works, the child serializer (BinaryFileSerializer) doesn't get called until I explicitly call it in create(), which is after the validation should have occurred. Why does this never get called?
I also don't like the fact I have to manually do a self.initial_data.getlist('binary_files')
and manually add it to data
- this should have already been added and validated, no?
My thought is that as I defined binary_files = BinaryFileSerializer
, this serializer should be called to validate that particular fields input?
FYI, I'm using the following to test POST uploads:
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!
Update: The question is now, if a validate() funciton is added to the BinaryFileSerializer, why does it not get called?
Possible duplicate --- Django REST: Uploading and serializing multiple images.
From the DRF Writable Nested Serializer doc,
By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create
create()
and/orupdate()
methods in order to explicitly specify how the child relationships should be saved.
From this, it's clear that the child serializer (BinaryFileSerializer
) won't call its own create()
method unless explicitly called.
The aim of your HTTP POST
request is to create new Submission
instance (and BinaryFile
instance). The creation process undergoes in the create()
method of the SubmissionCreateSerializer
serializer, which is you'd overridden. So, it will act/execute as per your code.
UPDATE-1
Things to remember
1. AFAIK, we can't send nested multipart/form-data
2. Here I'm only trying to implementing the least case scenario
3. I'm tested this solution with POSTMAN rest api test tool.
4. This method may be complex (until we found a better one).
5. Assuming your view class is subclass of ModelViewSet
class
What I'm going to do?
1. Since we can't send the files/data in a nested fashion, we have to send it flat mode.
image-1
2. Override the __init__()
method of the SubmissionSerializer
serializer and dynamically add as much FileField()
attribute according to the request.FILES
data.
We could somehow use ListSerializer
or ListField
here. Unfortunately I couldn't find out a way :(
# 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)
So, what id file_fields
here?
Since the form-data is a key-value pair, every file data must be associated with a key. Here in image-1, you could see file_1
and file_2
.
3. Now we need to pass the file_fields
values from the view
. Since this operation is creating new instance, we need to override the create()
method of the API class.
# 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)
4. Now, all values will be serialized properly. It's time to override the create()
method of the SubmissionSerializer()
to map the relations
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
5. That's it!!!
Complete Code Snippet
# 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)
Screenhots and other stuffs
1. POSTMAN console
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
3. Django Admin Screenhot
这篇关于多文件上传DRF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!