Django Rest framework, используйте разные сериализаторы в одном ModelViewSet

я хотел бы предоставить два разных сериализатора и все же иметь возможность воспользоваться всеми возможностями ModelViewSet:

  • при просмотре списка объектов я хотел бы, чтобы каждый объект имел url-адрес, который перенаправляет на его детали, и каждое другое отношение появляется с помощью __unicode __ целевой модели;

пример:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "emilio",
  "accesso": "CHI",
  "membri": [
    "emilio",
    "michele",
    "luisa",
    "ivan",
    "saverio"
  ]
}
  • при просмотре сведений об объекте я хотел бы использовать значение по умолчанию HyperlinkedModelSerializer

пример:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "http://127.0.0.1:8000/database/utenti/3/",
  "accesso": "CHI",
  "membri": [
    "http://127.0.0.1:8000/database/utenti/3/",
    "http://127.0.0.1:8000/database/utenti/4/",
    "http://127.0.0.1:8000/database/utenti/5/",
    "http://127.0.0.1:8000/database/utenti/6/",
    "http://127.0.0.1:8000/database/utenti/7/"
  ]
}

мне удалось сделать всю эту работу, как я хочу, следующим образом:

serializers.py

# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
    membri = serializers.RelatedField(many = True)
    creatore = serializers.RelatedField(many = False)

    class Meta:
        model = models.Gruppi

# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Gruppi

views.py

class DualSerializerViewSet(viewsets.ModelViewSet):
    """
    ViewSet providing different serializers for list and detail views.

    Use list_serializer and detail_serializer to provide them
    """
    def list(self, *args, **kwargs):
        self.serializer_class = self.list_serializer
        return viewsets.ModelViewSet.list(self, *args, **kwargs)

    def retrieve(self, *args, **kwargs):
        self.serializer_class = self.detail_serializer
        return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)

class GruppiViewSet(DualSerializerViewSet):
    model = models.Gruppi
    list_serializer = serializers.ListaGruppi
    detail_serializer = serializers.DettaglioGruppi

    # etc.

в основном я обнаруживаю, когда пользователь запрашивает представление списка или подробный вид и изменение serializer_class в соответствии с моими потребностями. Я не очень доволен этим кодом, хотя он выглядит как грязный хак и, самое главное, что делать, если два пользователя запрашивают список и деталь одновременно?

есть ли лучший способ достичь этого с помощью ModelViewSets или я должен отступить, используя GenericAPIView?

EDIT:
Вот как это сделать с помощью пользовательской базы ModelViewSet:

class MultiSerializerViewSet(viewsets.ModelViewSet):
    serializers = { 
        'default': None,
    }

    def get_serializer_class(self):
            return self.serializers.get(self.action,
                        self.serializers['default'])

class GruppiViewSet(MultiSerializerViewSet):
    model = models.Gruppi

    serializers = {
        'list':    serializers.ListaGruppi,
        'detail':  serializers.DettaglioGruppi,
        # etc.
    }

4 ответов


отменить get_serializer_class метод. Этот метод используется в mixins модели для получения надлежащего класса сериализатора.

обратите внимание, что есть также get_serializer метод, который возвращает экземпляр правильный сериализатор

class DualSerializerViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.action == 'list':
            return serializers.ListaGruppi
        if self.action == 'retrieve':
            return serializers.DettaglioGruppi
        return serializers.Default # I dont' know what you want for create/destroy/update.                

вы можете найти этот mixin полезным, он переопределяет метод get_serializer_class и позволяет объявить dict, который сопоставляет действие и класс сериализатора или резервный вариант обычного поведения.

class MultiSerializerViewSetMixin(object):
    def get_serializer_class(self):
        """
        Look for serializer class in self.serializer_action_classes, which
        should be a dict mapping action name (key) to serializer class (value),
        i.e.:

        class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
            serializer_class = MyDefaultSerializer
            serializer_action_classes = {
               'list': MyListSerializer,
               'my_action': MyActionSerializer,
            }

            @action
            def my_action:
                ...

        If there's no entry for that action then just fallback to the regular
        get_serializer_class lookup: self.serializer_class, DefaultSerializer.

        """
        try:
            return self.serializer_action_classes[self.action]
        except (KeyError, AttributeError):
            return super(MultiSerializerViewSetMixin, self).get_serializer_class()

на основе ответов @gonz и @user2734679, которые я создал этот небольшой пакет python это дает эту функциональность в виде дочернего класса ModelViewset. Вот как это работает.

from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2

class MyViewSet(CustomSerializerViewSet):
    serializer_class = DefaultSerializer
    custom_serializer_classes = {
        'create':  CustomSerializer1,
        'update': CustomSerializer2,
    }

Что касается предоставления различных сериализаторов, почему никто не собирается использовать подход, который проверяет метод HTTP? Это более четкое ИМО и не требует дополнительных проверок.

def get_serializer_class(self):
    if self.request.method == 'POST':
        return NewRackItemSerializer
    return RackItemSerializer

кредиты/источник: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718