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

Bla*_*ear 165 django serialization django-rest-framework

我想提供两种不同的序列化器,但能够从以下所有设施中受益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"
  ]
}
Run Code Online (Sandbox Code Playgroud)
  • 查看对象的详细信息时,我想使用默认值 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/"
  ]
}
Run Code Online (Sandbox Code Playgroud)

我设法按照以下方式完成所有这些工作:

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
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

基本上我检测用户何时请求列表视图或详细视图并serializer_class根据我的需要进行更改.我对这段代码并不是很满意,它看起来像是一个肮脏的黑客,最重要的是,如果两个用户同时请求列表和细节怎么办?

是否有更好的方法来实现这一目标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.
    }
Run Code Online (Sandbox Code Playgroud)

use*_*688 251

覆盖您的get_serializer_class方法.在模型mixins中使用此方法来检索正确的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.                
Run Code Online (Sandbox Code Playgroud)

  • 警告:django rest swagger没有放置self.action参数,因此该函数将抛出异常.您可以使用gonz的答案,或者您可以使用`if hasattr(self,'action')和self.action =='list' (12认同)
  • `return serializers.Default` - 或者只是返回 super().get_serializer_class() (2认同)

gon*_*onz 81

您可能会发现这个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()
Run Code Online (Sandbox Code Playgroud)

  • 谢谢Gonz.我已经更新了你的例子,因为`MultiSerializerViewSetMixin`需要'混入'到MyViewSet (2认同)

小智 11

只是想添加到现有的解决方案中。如果您想要为视图集的额外操作使用不同的序列化器(即使用@action装饰器),您可以在装饰器中添加 kwargs,如下所示:

@action(methods=['POST'], serializer_class=YourSpecialSerializer)
def your_extra_action(self, request):
    serializer = self.get_serializer(data=request.data)
    ...
Run Code Online (Sandbox Code Playgroud)


Adi*_*lik 7

基于@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,
    }
Run Code Online (Sandbox Code Playgroud)

  • 最好使用mixin,这是非常通用的. (6认同)

Luc*_*rra 7

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

def get_serializer_class(self):
    if self.request.method == 'POST':
        return NewRackItemSerializer
    return RackItemSerializer
Run Code Online (Sandbox Code Playgroud)

积分/来源:https : //github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718

  • 对于有问题的情况,这是关于对list和retrieve动作使用不同的序列化程序,您会遇到一个问题,即两者都使用GET方法。这就是django rest框架ViewSets使用_actions_的概念的原因,该概念相似但与相应的http方法略有不同。 (7认同)

Moh*_*umi 5

This answer is the same as the accepted answer but I prefer to do in this way.

Generic views

get_serializer_class(self):

Returns the class that should be used for the serializer. Defaults to returning the serializer_class attribute.

May be overridden to provide dynamic behavior, such as using different serializers for reading and write operations or providing different serializers to the different types of users. the serializer_class attribute.

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)
Run Code Online (Sandbox Code Playgroud)