我有一个要求,我希望允许在同一发布请求中上传多个文件以创建对象。我目前有一种方法可以执行此操作,但是在查看其他一些示例后,看来这并不是实现此目的的方法。
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,为什么不调用它?
答案 0 :(得分:8)
可能重复 --- Django REST: Uploading and serializing multiple images。
从DRF Writable Nested Serializer doc,
默认情况下,嵌套的序列化器是只读的。如果要支持对嵌套序列化器字段的写操作,则需要创建
create()
和/或update()
方法,以便明确指定应如何保存子关系。
由此可见,除非明确调用,子序列化器(BinaryFileSerializer
)不会调用其自己的create()
方法。
您的 HTTP POST
请求的目的是创建新的 Submission
实例(和 { {1}} 实例)。创建过程将使用 BinaryFile
序列化器的 create()
方法进行,您将对其进行覆盖。因此,它将按照您的代码执行/执行。
要记住的事情
1. AFAIK,我们无法发送 嵌套SubmissionCreateSerializer
2.在这里,我仅尝试实现最少情况的方案
3.我已经使用POSTMAN rest api测试工具测试了该解决方案。
4.此方法可能很复杂(直到找到更好的方法为止)。
5.假设您的视图类是 multipart/form-data
类的子类
我要做什么?
1.由于我们无法以嵌套方式发送文件/数据,因此必须以平面模式发送它。
图片1
2。覆盖 ModelViewSet
序列化器的 __init__()
方法,并根据需要动态添加尽可能多的 SubmissionSerializer
属性 FileField()
数据。
我们可以在此处使用ListSerializer
或ListField
。不幸的是我找不到办法:(
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_fields
和file_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
# 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)