Django rest框架,在同一ModelViewSet中使用不同的序列化器


195

我想提供两个不同的序列化器,但仍然能够从以下所有功能中受益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以满足我的需求。我对这段代码并不真正满意,它看起来像是一个肮脏的hack,最重要的是,如果两个用户同时请求一个列表和一个细节怎么办?

有没有更好的方法可以实现此目的,ModelViewSets还是我必须退后一步GenericAPIView

编辑:
这是使用自定义库的方法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.
    }

您是如何最终实现的?使用user2734679提出的方法还是使用GenericAPIView?
andilabs

如user2734679所建议;我创建了一个通用的ViewSet,添加了一个字典来为每个操作指定序列化程序,并在未指定时默认为序列化程序
BlackBear

我有类似的问题(stackoverflow.com/questions/24809737/…),目前已经结束了(gist.github.com/andilab/a23a6370bd118bf5e858),但我对此并不满意。
andilabs

1
为此创建了这个小包装。github.com/Darwesh27/drf-custom-viewsets
Adil Malik

1
覆盖检索方法可以。
gzerone

Answers:


287

覆盖您的get_serializer_class方法。您的模型mixin中使用此方法来检索适当的Serializer类。

请注意,还有一种get_serializer方法可以返回正确的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.                

1
太好了,谢谢!我已重写get_serializer_class虽然
BlackBear

15
警告:django rest swagger不会放置self.action参数,因此此函数将引发异常。您可以使用gonz的答案,也可以使用if hasattr(self, 'action') and self.action == 'list'
Tom Leys

为此创建一个小的pypi包。github.com/Darwesh27/drf-custom-viewsets
Adil Malik

pk如果操作为,我们如何获得请求的对象retrieve
Pranjal Mittal

我的自我行动是无。有人可以告诉我为什么吗?
卡卡吉

86

您可能会发现此mixin有用,它覆盖了get_serializer_class方法,并允许您声明一个将操作和序列化程序类或后备映射到常规行为的字典。

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()

为此创建了这个小包装。github.com/Darwesh27/drf-custom-viewsets
Adil Malik

15

此答案与接受的答案相同,但我更喜欢这样做。

通用视图

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,
        # ... other actions
    }
    default_serializer_class = DefaultSerializer # Your default serializer

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

无法使用它,因为它告诉我我的视图没有属性“动作”。看起来像ProductIndex(generics.ListCreateAPIView)。这是否意味着您绝对需要将视图集作为参数传递,还是可以使用泛型API视图来实现?
勒布

1
对@Seb评论的最新回复-也许有人可以从中受益:)该示例使用ViewSets,而不是Views :)
fanny

因此,与这个职位相结合stackoverflow.com/questions/32589087/...,ViewSets似乎是去有过不同的意见更多的控制权,并生成URL自动有一个一致的API的方式吗?最初认为,generics.ListeCreateAPIView是最有效的,但太基本了吧?
的Seb

10

关于提供不同的序列化器,为什么没人去检查HTTP方法呢?IMO更清晰,不需要额外的检查。

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


12
对于有问题的情况,即有关对listretrieve动作使用不同的序列化器,您都遇到了同时使用GET方法的问题。这就是django rest框架ViewSets使用action概念的原因,该概念与之相似,但与相应的http方法略有不同。
哈肯·利德

8

基于@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,
    }

6
最好使用具有通用性的mixin。
iamsk

1

尽管以某种方式预定义多个串行器似乎是最明显的记录方式,但是FWIW还是有一种替代方法可以借鉴其他记录的代码,并且可以在实例化串行器时将参数传递给串行器。我认为,如果您需要根据各种因素(例如用户管理员级别,调用的动作,甚至实例的属性)来生成逻辑,则可能会更值得。

难题的第一步是有关在实例化点动态修改序列化程序的文档。该文档没有说明如何在启动视图后从视图集中调用此代码或如何修改字段的只读状态-但这并不难。

第二部分- 还记录了get_serializer方法 -(在“其他方法”下,距get_serializer_class的页面稍远处),因此应该可以安全地依赖(并且源很简单,这希望减少意外的机会)修饰引起的副作用)。检查GenericAPIView下的源代码(ModelViewSet-以及所有其他似乎内置的视图集类-继承自定义get_serializer的GenericAPIView。

将两者放在一起,您可以执行以下操作:

在序列化器文件中(对我来说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列表之类的操作并将其用于白名单而非黑名单字段(我倾向于这样做)。我还发现,将字段设置为空元组(如果未通过),然后删除对None的检查非常有用,然后将继承的Serializers上的字段定义设置为“ 全部 ”。这意味着实例化序列化程序时没有传递的字段不会偶然幸免,而且我也不必将序列化程序调用与继承的序列化程序类定义进行比较,以了解其中包含了什么...例如在DynamicFieldsModelSerializer 的初始化中:

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

注意:如果我只想将两到三个类映射到不同的操作,并且/或者我不想要任何特别的动态序列化程序行为,那么我很可能会使用这里其他人提到的一种方法,但是我认为值得一提,尤其是考虑到其其他用途。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.