При использовании multipart/form-data с вложенными сериализаторами и файлами, DRF выдает ошибку "Представленные данные не были файлом. Проверьте тип кодировки в форме".
У меня возникла проблема с использованием multipart/form-data, вложенных сериализаторов и загрузкой изображений/файлов. Я думаю, что мои модели и сериализаторы работают хорошо, но в какой-то момент во время разбора данных формы происходит сбой.
Я только что расширил BaseParser для разбора строк на списки и массивы.
parsers.py
class MultiPartJSONParser(BaseParser):
media_type = 'multipart/form-data'
def parse(self, stream, media_type=None, parser_context=None):
parser_context = parser_context or {}
request = parser_context['request']
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
meta = request.META.copy()
meta['CONTENT_TYPE'] = media_type
upload_handlers = request.upload_handlers
try:
parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding)
data, files = parser.parse()
data = data.dict()
for key in data:
if data[key]:
try:
data[key] = json.loads(data[key])
except ValueError as e:
pass
return DataAndFiles(data, files)
except MultiPartParserError as exc:
raise ParseError('Multipart form parse error - %s' % exc)
Почему я так делаю? Потому что мое намерение состоит в том, чтобы передать форму-данные следующим образом:
key | Value |
---|---|
user | 6 |
address | {"street": "Main St.", "number": "1"} |
first_name | Alice |
last_name | Bob |
image [file] | ...\image_directory |
language | [1,2] |
Если я не отправляю POST или PATCH с изображением, все работает хорошо, но когда я отправляю изображение:
The submitted data was not a file. Check the encoding type on the form.
"Сумасшедшая" вещь в этом заключается в том, что если я использую MultipartParser из DRF, который очень похож на вышеупомянутый, он работает с изображением, но адрес и язык не работает.
Вот мои сериализаторы:
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
user = PrimaryKeyRelatedField(many = False, queryset = User.objects.all())
address = AddressSerializer()
language = PrimaryKeyRelatedField(many = True, queryset = Language.objects.all())
class Meta:
model = RenterProfile
fields = '__all__'
def create(self, validated_data):
address_data = validated_data.pop('address')
address = Address.objects.create(**address_data)
language_data = validated_data.pop('language')
renter_profile = RenterProfile.objects.create(address=address, **validated_data)
renter_profile.language.set(language_data)
def update(self, instance, validated_data)
if 'address' in validated_data:
address_data = validated_data.pop('address')
Address.objects.filter(id=instance.address.id).update(**address_data)
if 'language' in validated_data:
language_data = validated_data.pop('language')
instance.language.set(language_data)
for (key, value) in validated_data.items():
setattr(instance, key, value)
instance.save()
return instance
class AddressSerializer(serializers.ModelSerializer):
class Meta:
models = Address
fields = '__all__'
Вот еще контекст, если он вам нужен.
ПРИМЕЧАНИЕ: Я сократил код, чтобы избежать ненужной информации.
Models.py
class User(models.Model):
email = models.EmailField()
password = models.CharField()
class UserProfile(models.Model):
user = models.OneToOneField(User)
image = models.ImageField()
first_name = models.CharField()
last_name = models.Charfield()
address = models.ForeignKey(Address)
language = models.ManyToMany(Language)
class Address(models.Model):
street = models.Charfield()
number = models.Charfield()
class Language(models.Model):
name = models.CharField()
Мои взгляды просты:
views.py
class CreateProfileView(CreateAPIView):
serializer_class = ProfileSerializer
parser_classes = [MultiPartJSONParser]
class RUDProfileView(RetrieveUpdateDestroyAPIView):
serializer_class = ProfileSerializer
queryset = UserProfile.objects.all()
lookup_field = 'user'
parser_classes = [MultiPartJSONParser]
Я пришел к решению, которое не является желаемым для меня, но оно работает.
Я обнаружил, что по неизвестной причине поле file/image вставляется в список, если я использую MultiPartJSONParser.
Чтобы решить эту проблему, я должен извлечь эти поля из списка в файле views.py перед вызовом super().create()
или super().update()
views.py
class CreateProfileView(CreateAPIView):
serializer_class = ProfileSerializer
parser_classes = [MultiPartJSONParser]
def create(self, request, *args, **kwargs):
for key in request.FILES:
request.data[key] = request.data[key][0]
return super().update(request, *args, **kwargs)
class RUDProfileView(RetrieveUpdateDestroyAPIView):
serializer_class = ProfileSerializer
queryset = UserProfile.objects.all()
lookup_field = 'user'
parser_classes = [MultiPartJSONParser]
def update(self, request, *args, **kwargs):
for key in request.FILES:
request.data[key] = request.data[key][0]
return super().update(request, *args, **kwargs)