Django rest framework, используйте разные сериализаторы в том же ModelViewSet - программирование
Подтвердить что ты не робот

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.
    }
4b9b3361

Ответ 1

Переопределите метод get_serializer_class. Этот метод используется в ваших модельных миксах для получения соответствующего класса Serializer.

Обратите внимание, что существует также метод 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.                

Ответ 2

Вы можете найти этот миксин полезным, он переопределяет метод 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 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()

Ответ 3

Что касается предоставления различных сериализаторов, почему никто не собирается использовать подход, который проверяет метод 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

Ответ 4

На основе ответов @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,
    }

Ответ 5

Этот ответ такой же, как принятый ответ, но я предпочитаю делать таким образом.

Общие взгляды

get_serializer_class(self):

Возвращает класс, который должен использоваться для сериализатора. По умолчанию возвращается атрибут serializer_class.

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

class DualSerializerViewSet(viewsets.ModelViewSet):
    # mapping serializer into the action
    serializer_classes = {
        'list': serializers.ListaGruppi,
        'retrieve': serializers.DettaglioGruppi,
        # ...
    }
    default_serializer_class = DefaultSerializer # Your default serializer

    def get_serializer_class(self):
        return self.serializer_classes.get(self.action, self.default_serializer_class)

Ответ 6

Хотя предварительное определение нескольких сериализаторов тем или иным способом представляется наиболее очевидным документированным способом, в FWIW существует альтернативный подход, основанный на другом документированном коде и позволяющий передавать аргументы сериализатору в том виде, как он есть. инстанцирован. Я думаю, что было бы более целесообразно, если бы вам нужно было генерировать логику на основе различных факторов, таких как уровни администратора пользователя, вызываемое действие, возможно, даже атрибуты экземпляра.

Первая часть головоломки - это документация по динамическому изменению сериализатора в момент его создания. Эта документация не объясняет, как вызывать этот код из набора представлений или как изменять статус полей "только для чтения" после того, как они были инициированы, но это не очень сложно.

Вторая часть - метод get_serializer также задокументирована - (чуть дальше по странице от get_serializer_class в разделе "другие методы"), поэтому на него можно было бы безопасно положиться (а источник очень прост, что надеюсь, означает меньшую вероятность непреднамеренных побочных эффектов в результате модификации). Проверьте источник в GenericAPIView (ModelViewSet - и все остальные классы встроенных представлений, которые, по-видимому, - наследуются от GenericAPIView, который определяет get_serializer.

Соединяя их вместе, вы можете сделать что-то вроде этого:

В файле сериализатора (для меня base_serializers.py):

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional 'fields' argument that
controls which fields should be displayed.
"""

def __init__(self, *args, **kwargs):
    # Don't pass the 'fields' arg up to the superclass
    fields = kwargs.pop('fields', None)

    # Adding this next line to the documented example
    read_only_fields = kwargs.pop('read_only_fields', None)

    # Instantiate the superclass normally
    super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        # Drop any fields that are not specified in the 'fields' argument.
        allowed = set(fields)
        existing = set(self.fields)
        for field_name in existing - allowed:
            self.fields.pop(field_name)

    # another bit we're adding to documented example, to take care of readonly fields 
    if read_only_fields is not None:
        for f in read_only_fields:
            try:
                self.fields[f].read_only = True
            exceptKeyError:
                #not in fields anyway
                pass

Тогда в вашем наборе вы можете сделать что-то вроде этого:

class MyViewSet(viewsets.ModelViewSet):
    # ...permissions and all that stuff

    def get_serializer(self, *args, **kwargs):

        # the next line is taken from the source
        kwargs['context'] = self.get_serializer_context()

        # ... then whatever logic you want for this class e.g:
        if self.action == "list":
            rofs = ('field_a', 'field_b')
            fs = ('field_a', 'field_c')
        if self.action == 'retrieve':
            rofs = ('field_a', 'field_c, ‘field_d)
            fs = ('field_a', 'field_b)
        #  add all your further elses, elifs, drawing on info re the actions, 
        # the user, the instance, anything passed to the method to define your read only fields and fields ...
        #  and finally instantiate the specific class you want (or you could just
        # use get_serializer_class if you've defined it).  
        # Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
        kwargs['read_only_fields'] = rofs
        kwargs['fields'] = fs
        return MyDynamicSerializer(*args, **kwargs)

И это должно быть! Использование MyViewSet теперь должно создать экземпляр MyDynamicSerializer с нужными вам аргументами - и, предполагая, что ваш сериализатор наследуется от DynamicFieldsModelSerializer, он должен просто знать, что делать.

Возможно, стоит упомянуть, что это может иметь особый смысл, если вы хотите адаптировать сериализатор другими способами... например. чтобы делать такие вещи, как взять список read_only_exceptions и использовать его для внесения в белый, а не в черный список (что я обычно и делаю). Я также считаю полезным установить поля равными пустому кортежу, если он не прошел, а затем просто убрать проверку "Нет"... и я установил определения полей в своих наследующих сериализаторах на "all". Это означает, что никакие поля, которые не передаются при создании экземпляра сериализатора, не выживают случайно, и мне также не нужно сравнивать вызов сериализатора с определением класса наследующего сериализатора, чтобы узнать, что было включено... например, в init из DynamicFieldsModelSerializer:

# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....

NB Если я просто хотел, чтобы два или три класса отображались на отдельные действия, и/или я не хотел какого-либо специально динамического поведения сериализатора, я вполне мог бы использовать один из подходов, упомянутых здесь другими, но я подумал это стоит представить в качестве альтернативы, особенно учитывая его другое использование.