При использовании 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)

Вернуться на верх