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,为什么不调用它?
可能重复 --- Django REST:上传和序列化多个图像。
从“ DRF可写嵌套序列化程序”文档中,
默认情况下,嵌套串行器是只读的。如果要支持对嵌套序列化器字段的写操作,则需要创建
create()和/或update()方法,以明确指定应如何保存子关系。
由此可见,除非明确调用,否则子序列化器(BinaryFileSerializer)不会调用其自己的create()方法。
您的HTTP POST请求的目的是创建新Submission实例(和BinaryFile实例)。创建过程将create()采用SubmissionCreateSerializer序列化器的方法,您将被覆盖。因此,它将按照您的代码执行/执行。
要记住的事情
1. AFAIK,我们不能发送嵌套的消息multipart/form-data
。2.在这里,我仅尝试实现最小写的情况
。3.我已经使用POSTMAN rest api测试工具测试了该解决方案。
4.此方法可能很复杂(直到找到更好的方法为止)。
5.假设你的视图类的子ModelViewSet类的
什么,我该怎么办?
1.由于我们无法以嵌套方式发送文件/数据,因此必须以平面模式发送。
图片1
2.覆盖序列化器的__init__()方法,SubmissionSerializer并FileField()根据request.FILES数据动态添加尽可能多的属性。
我们可以以某种方式使用ListSerializer或ListField在这里。不幸的是我找不到办法:(
# 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_1和file_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)
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)
| 归档时间: |
|
| 查看次数: |
857 次 |
| 最近记录: |