Изменить поле в Django Rest Framework ModelSerializer на основе типа запроса?
рассмотрим этот случай, когда у меня есть Book
и Author
модель.
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
fields = ('id', 'name')
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
viewsets.py
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
это отлично работает, если я отправляю GET
запрос на книгу. Я получаю вывод с вложенным сериализатором, содержащим сведения о книге и вложенные сведения об авторе, что я и хочу.
однако, когда я хочу создать/обновить книгу, я должен послать POST
/PUT
/PATCH
с вложенными деталями автор вместо того, чтобы просто их id. Я хочу иметь возможность создавать / обновлять объект книги, указывая идентификатор автора, а не весь объект автора.
Итак, что-то, где мой сериализатор выглядит так для GET
запрос
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
и мой сериализатор выглядит следующим образом POST
, PUT
, PATCH
запрос
class BookSerializer(serializers.ModelSerializer):
author = PrimaryKeyRelatedField(queryset=Author.objects.all())
class Meta:
model = models.Book
fields = ('id', 'title', 'author')
я также не хочу создавать два совершенно отдельных сериализатора для каждого типа запроса. Я хотел бы просто изменить в BookSerializer
.
4 ответов
IMHO, несколько сериализаторов будут только создавать все больше и больше путаницы.
скорее я предпочел бы ниже Решение:
- не меняйте набор представлений (оставьте его по умолчанию)
- добавить .метод validate () в сериализаторе; наряду с другими необходимыми .создать или. обновление() и т. д. Здесь действительная логика войдет метод validate. Где на основе типа запроса мы будем создавать validated_data дикт, как требуется нашим сериализатором.
I думаю, это самый чистый подход.
см. мою аналогичную проблему и решение в DRF: разрешить все поля в GET request, но ограничить POST только одним полем
существует функция DRF, в которой вы можете динамически изменять поля сериализатора http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
мой вариант использования: используйте поле slug на GET, чтобы мы могли видеть хороший представитель отношения, но на POST/PUT переключитесь на классическое обновление первичного ключа. Настройте сериализатор на что-то вроде этого:
class FooSerializer(serializers.ModelSerializer):
bar = serializers.SlugRelatedField(slug_field='baz', queryset=models.Bar.objects.all())
class Meta:
model = models.Foo
fields = '__all__'
def __init__(self, *args, **kwargs):
super(FooSerializer, self).__init__(*args, **kwargs)
try:
if self.context['request'].method in ['POST', 'PUT']:
self.fields['bar'] = serializers.PrimaryKeyRelatedField(queryset=models.Bar.objects.all())
except KeyError:
pass
KeyError иногда выбрасывается при инициализации кода без запрос, возможно, модульные тесты.
наслаждайтесь и используйте ответственно.
вы ищете get_serializer_class
метод ViewSet
. Это позволяет переключать тип запроса, для которого требуется использовать сериализатор.
from rest_framework import viewsets
class MyModelViewSet(viewsets.ModelViewSet):
model = MyModel
queryset = MyModel.objects.all()
def get_serializer_class(self):
if self.action in ('create', 'update', 'partial_update'):
return MySerializerWithPrimaryKeysForCreatingOrUpdating
else:
return MySerializerWithNestedData
в итоге я столкнулся с этой проблемой, имея другой сериализатор, когда это связанное поле.
class HumanSerializer(PersonSerializer):
class Meta:
model = Human
fields = PersonSerializer.Meta.fields + (
'firstname',
'middlename',
'lastname',
'sex',
'date_of_birth',
'balance'
)
read_only_fields = ('name',)
class HumanRelatedSerializer(HumanSerializer):
def to_internal_value(self, data):
return self.Meta.model.objects.get(id=data['id'])
class PhoneNumberSerializer(serializers.ModelSerializer):
contact = HumanRelatedSerializer()
class Meta:
model = PhoneNumber
fields = (
'id',
'contact',
'phone',
'extension'
)
вы можете сделать что-то подобное, но для RelatedSerializer сделайте:
def to_internal_value(self, data):
return self.Meta.model.objects.get(id=data)
таким образом, при сериализации вы сериализуете Связанный объект, а при де-сериализации вам нужен только id для получения связанного объекта.