Изменить поле в 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, несколько сериализаторов будут только создавать все больше и больше путаницы.

скорее я предпочел бы ниже Решение:

  1. не меняйте набор представлений (оставьте его по умолчанию)
  2. добавить .метод 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 для получения связанного объекта.