Решение представляет собой смесь или ответы @Neil и @ mon.Однако я исправлюсь немного подробнее.
Анализ
Прямо сейчас Почтальон отправляет данные формы, которые содержат 2 пары ключ-значение (пожалуйста, обратитесь к фотографии, которую я загрузил в моем исходном вопросе),Одно - это ключевое поле «photos», связанное с несколькими файлами фотографий, а другое - ключевое поле «data», связанное с одним большим фрагментом 'JSON-подобной строки' .Хотя это справедливый метод POSTing или PUTting данных вместе с файлами, DRF MultiPartParser или JSONParser не будут анализировать их должным образом.
Причина, по которой я получил сообщение об ошибке, была проста.self.get_serializer(instance, data=request.data, partial=partial
метод внутри ModelViewSet
(особенно UpdateModelMixin) не может понять request.data
part.
В настоящее время request.data
из отправленных данных формы выглядит следующим образом.
<QueryDict: { "photos": [PhotoObject1, PhotoObject2, ... ],
"request": ["{'\n 'title': 'title test', \n 'content': 'content test'}",]
}>
Смотреть"запросить" часть тщательно.Значение представляет собой простой string
объект.
Однако мой PostSerializer ожидает, что request.data
будет выглядеть примерно так, как показано ниже.
{ "photos": [{"image": ImageObject1, "post":1}, {"image": ImageObject2, "post":2}, ... ],
"title": "test title",
"content": "test content"
}
Поэтому давайте проведем некоторый эксперимент и поместим некоторые данные вв соответствии с вышеуказанной формой JSON.то есть
{ "photos": [{"image": "http://tny.im/gMU", "post": 1}],
"title" : "test title",
"content": "test content"
}
Вы получите следующее сообщение об ошибке:
"photos": [{"image": ["отправленные данные не являются файлом."]}]
Это означает, что все данные представлены правильно, но url http://tny.im/gMU изображения - это не файл, а строка.
Теперь причина всей этой проблемы стала ясна,Это парсер, который нужно исправить, чтобы сериализатор мог понимать отправленные данные формы.
Решение
1.Создать новый синтаксический анализатор
Новый синтаксический анализатор должен проанализировать 'JSON-подобную' строку для правильных данных JSON.Я заимствовал MultipartJSONParser из здесь.
То, что делает этот анализатор, просто.Если мы передаем 'JSON-подобную' строку с ключом 'data', вызовите json
из rest_framework и проанализируйте ее.После этого верните проанализированный JSON с запрошенными файлами.
class MultipartJsonParser(parsers.MultiPartParser):
# https://stackoverflow.com/a/50514022/8897256
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
data = json.loads(result.data["data"])
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
2.Редизайн сериализатора
Официальный документ DRF предлагает использовать вложенные сериализаторы для обновления или создания связанных объектов.Однако у нас есть существенный недостаток, заключающийся в том, что InMemoryFileObject не может быть переведен в надлежащую форму, которую ожидает сериализатор.Для этого нам нужно
- Переопределить
update
метод ModelViewSet - Выскочить пару ключ-значение 'photos' из
request.data
- Translate popped 'пары 'photos' в список словарей, содержащих ключи 'image' и 'post'.
- Добавьте результат к
request.data
с именем ключа 'photos'.Это потому, что наш PostSerializer ожидает, что имя ключа будет 'photos'.
Однако в основном request.data
- это QuerySet, который является неизменяемым по умолчанию.И я весьма скептически отношусь к тому, что нам нужно принудительно изменить QuerySet.Поэтому я скорее назначу процесс создания PostImage для update()
метода ModelViewSet
.В этом случае нам больше не нужно определять nested serializer
.
Просто сделайте это:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
class PostImageSerializer(serializer.ModelSerializer):
class Meta:
model = PostImage
fields = '__all__'
3.Переопределить метод update()
из ModelViewSet
Чтобы использовать наш класс Parser, нам нужно явно указать его.Мы объединим поведение PATCH и PUT, поэтому установите partial=True
.Как мы видели ранее, файлы изображений переносятся с ключом 'photos', поэтому выведите значения и создайте каждый экземпляр Photo.
Наконец, благодаря нашему недавно разработанному Parser, простая 'JSON-like' строка будетпревращается в обычные данные JSON.Так что просто поместите все в serializer_class
и perform_update
.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
# New Parser
parser_classes = (MultipartJsonParser,)
def update(self, request, *args, **kwargs):
# Unify PATCH and PUT
partial = True
instance = self.get_object()
# Create each PostImage
for photo in request.data.pop("photos"):
PostImage.objects.create(post=instance, image=photo)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
# Do ViewSet work.
self.perform_update(serializer)
return Response(serializer.data)
Заключение
Решение работает, но я не уверен, что это самый чистый способ сохранения внешнего ключа, связанного смоделей.У меня возникает сильное ощущение, что именно сериализатор должен сохранить соответствующую модель.Как указывалось в документе, данные, кроме файлов, сохраняются таким образом.Если бы кто-нибудь сказал мне более тонкий способ сделать это, я был бы очень признателен.