我想提供两种不同的序列化器,并且能够从ModelViewSet的所有功能中受益:

当查看对象列表时,我希望每个对象都有一个url,该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?

编辑: 下面是如何使用一个自定义的基础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.
    }

当前回答

对于所有提到的其他解决方案,我无法找到如何使用get_serializer_class函数实例化类,也无法找到自定义验证函数。对于那些像我一样仍然迷失并希望完全实现的人,请检查下面的答案。

views.py

from rest_framework.response import Response

from project.models import Project
from project.serializers import ProjectCreateSerializer, ProjectIDGeneratorSerializer


class ProjectViewSet(viewsets.ModelViewSet):
    action_serializers = {
        'generate_id': ProjectIDGeneratorSerializer,
        'create': ProjectCreateSerializer,
    }
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if hasattr(self, 'action_serializers'):
            return self.action_serializers.get(self.action, self.serializer_class)

        return super(ProjectViewSet, self).get_serializer_class()

    # You can create custom function
    def generate_id(self, request):
        serializer = self.get_serializer_class()(data=request.GET)
        serializer.context['user'] = request.user
        serializer.is_valid(raise_exception=True)
        return Response(serializer.validated_data, status=status.HTTP_200_OK)

    def create(self, request, **kwargs):
        serializer = self.get_serializer_class()(data=request.data)
        serializer.context['user'] = request.user
        serializer.is_valid(raise_exception=True)
        return Response(serializer.validated_data, status=status.HTTP_200_OK)

serializers.py

import random
from rest_framework import serializers
from project.models import Project


class ProjectIDGeneratorSerializer(serializers.Serializer):
    def update(self, instance, validated_data):
        pass

    def create(self, validated_data):
        pass

    projectName = serializers.CharField(write_only=True)

    class Meta:
        fields = ['projectName']

    def validate(self, attrs):
        project_name = attrs.get('projectName')
        project_id = project_name.replace(' ', '-')

        return {'projectID': project_id}


class ProjectCreateSerializer(serializers.Serializer):
    def update(self, instance, validated_data):
        pass

    def create(self, validated_data):
        pass

    projectName = serializers.CharField(write_only=True)
    projectID = serializers.CharField(write_only=True)

    class Meta:
        model = Project
        fields = ['projectName', 'projectID']

    def to_representation(self, instance: Project):
        data = dict()
        data['projectName'] = instance.name
        data['projectID'] = instance.projectID
        data['createdAt'] = instance.createdAt
        data['updatedAt'] = instance.updatedAt

        representation = {
            'message': f'Project {instance.name} has been created.',
        }

        return representation

    def validate(self, attrs):
        print('attrs', dict(attrs))
        project_name = attrs.get('projectName')
        project_id = attrs.get('projectID')

        if Project.objects.filter(projectID=project_id).first():
            raise serializers.ValidationError(f'Project with ID {project_id} already exist')

        project = Project.objects.create(projectID=project_id,
                                         name=project_name)

        print('user', self.context['user'])
        project.user.add(self.context["user"])

        project.save()

        return self.to_representation(project)

urls . py

from django.urls import path

from .views import ProjectViewSet

urlpatterns = [
    path('project/generateID', ProjectViewSet.as_view({'get': 'generate_id'})),
    path('project/create', ProjectViewSet.as_view({'post': 'create'})),
]

models.py

# Create your models here.
from django.db import models

from authentication.models import User


class Project(models.Model):
    id = models.AutoField(primary_key=True)
    projectID = models.CharField(max_length=255, blank=False, db_index=True, null=False)
    user = models.ManyToManyField(User)
    name = models.CharField(max_length=255, blank=False)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

其他回答

基于@gonz和@user2734679 answers,我创建了这个小的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,
    }

对于所有提到的其他解决方案,我无法找到如何使用get_serializer_class函数实例化类,也无法找到自定义验证函数。对于那些像我一样仍然迷失并希望完全实现的人,请检查下面的答案。

views.py

from rest_framework.response import Response

from project.models import Project
from project.serializers import ProjectCreateSerializer, ProjectIDGeneratorSerializer


class ProjectViewSet(viewsets.ModelViewSet):
    action_serializers = {
        'generate_id': ProjectIDGeneratorSerializer,
        'create': ProjectCreateSerializer,
    }
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if hasattr(self, 'action_serializers'):
            return self.action_serializers.get(self.action, self.serializer_class)

        return super(ProjectViewSet, self).get_serializer_class()

    # You can create custom function
    def generate_id(self, request):
        serializer = self.get_serializer_class()(data=request.GET)
        serializer.context['user'] = request.user
        serializer.is_valid(raise_exception=True)
        return Response(serializer.validated_data, status=status.HTTP_200_OK)

    def create(self, request, **kwargs):
        serializer = self.get_serializer_class()(data=request.data)
        serializer.context['user'] = request.user
        serializer.is_valid(raise_exception=True)
        return Response(serializer.validated_data, status=status.HTTP_200_OK)

serializers.py

import random
from rest_framework import serializers
from project.models import Project


class ProjectIDGeneratorSerializer(serializers.Serializer):
    def update(self, instance, validated_data):
        pass

    def create(self, validated_data):
        pass

    projectName = serializers.CharField(write_only=True)

    class Meta:
        fields = ['projectName']

    def validate(self, attrs):
        project_name = attrs.get('projectName')
        project_id = project_name.replace(' ', '-')

        return {'projectID': project_id}


class ProjectCreateSerializer(serializers.Serializer):
    def update(self, instance, validated_data):
        pass

    def create(self, validated_data):
        pass

    projectName = serializers.CharField(write_only=True)
    projectID = serializers.CharField(write_only=True)

    class Meta:
        model = Project
        fields = ['projectName', 'projectID']

    def to_representation(self, instance: Project):
        data = dict()
        data['projectName'] = instance.name
        data['projectID'] = instance.projectID
        data['createdAt'] = instance.createdAt
        data['updatedAt'] = instance.updatedAt

        representation = {
            'message': f'Project {instance.name} has been created.',
        }

        return representation

    def validate(self, attrs):
        print('attrs', dict(attrs))
        project_name = attrs.get('projectName')
        project_id = attrs.get('projectID')

        if Project.objects.filter(projectID=project_id).first():
            raise serializers.ValidationError(f'Project with ID {project_id} already exist')

        project = Project.objects.create(projectID=project_id,
                                         name=project_name)

        print('user', self.context['user'])
        project.user.add(self.context["user"])

        project.save()

        return self.to_representation(project)

urls . py

from django.urls import path

from .views import ProjectViewSet

urlpatterns = [
    path('project/generateID', ProjectViewSet.as_view({'get': 'generate_id'})),
    path('project/create', ProjectViewSet.as_view({'post': 'create'})),
]

models.py

# Create your models here.
from django.db import models

from authentication.models import User


class Project(models.Model):
    id = models.AutoField(primary_key=True)
    projectID = models.CharField(max_length=255, blank=False, db_index=True, null=False)
    user = models.ManyToManyField(User)
    name = models.CharField(max_length=255, blank=False)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

关于提供不同的序列化器,为什么没有人选择检查HTTP方法的方法?在我看来,这样更清楚,也不需要额外的检查。

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

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

您可以使用类中的字典将所有序列化器与操作映射,然后从“get_serializer_class”方法获取它们。下面是我在不同情况下使用的不同序列化器。

class RushesViewSet(viewsets.ModelViewSet):

    serializer_class = DetailedRushesSerializer
    queryset = Rushes.objects.all().order_by('ingested_on')
    permission_classes = (IsAuthenticated,)
    filter_backends = (filters.SearchFilter, 
       django_filters.rest_framework.DjangoFilterBackend, filters.OrderingFilter)
    pagination_class = ShortResultsSetPagination
    search_fields = ('title', 'asset_version__title', 
      'asset_version__video__title')

    filter_class = RushesFilter
    action_serializer_classes = {
      "create": RushesSerializer,
      "update": RushesSerializer,
      "retrieve": DetailedRushesSerializer,
      "list": DetailedRushesSerializer,
      "partial_update": RushesSerializer,
     }

    def get_serializer_context(self):
        return {'request': self.request}

    def get_serializer_class(self):
        try:
            return self.action_serializer_classes[self.action]
        except (KeyError, AttributeError):
            error_logger.error("---Exception occurred---")
            return super(RushesViewSet, self).get_serializer_class()

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

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