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

我想提供两种不同的序列化器,并且能够受益于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?

< p > 编辑: < br > 下面是如何使用自定义基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.
}
144692 次浏览

重写你的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.

您可能会发现这个mixin很有用,它覆盖了get_serializer_class方法,并允许您声明一个dict,该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 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,
}

关于提供不同的序列化器,为什么没有人选择检查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

这个答案和公认的答案是一样的,但我更喜欢这样做。

通用视图 .

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)

虽然以某种方式预先定义多个序列化器似乎是最明显的记录方式,但FWIW还有另一种方法,它利用其他文档代码,并允许在实例化序列化器时将参数传递给它。我认为,如果需要基于各种因素生成逻辑,比如用户管理级别、被调用的操作,甚至实例的属性,那么可能更有价值。

第一块拼图是关于在实例化时动态修改序列化器的文档。该文档没有解释如何从视图集调用这段代码,也没有解释如何在字段被初始化后修改字段的只读状态——但这并不难。

第二部分——get_serializer方法也被记录了——(就在get_serializer_class页面的'other methods'下面),所以它应该是安全的(源代码非常简单,希望这意味着由于修改而导致的意外副作用的可能性更小)。检查GenericAPIView下的源代码(ModelViewSet和所有其他内建的视图集类似乎都继承自GenericAPIView,它定义了get_serializer。

把两者放在一起,你可以这样做:

在一个序列化器文件中(对我来说是base_serialzers .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上设置了我的字段定义为“所有”。这意味着实例化序列化器时没有未传递的字段意外地幸存下来,而且我也不必将序列化器调用与继承的序列化器类定义进行比较,以知道包含了什么……g在DynamicFieldsModelSerializer的初始化中:

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

如果我只想要两个或三个类映射到不同的操作和/或我不想要任何特别动态的序列化程序行为,我可能会使用其他人在这里提到的方法之一,但我认为这值得作为一种替代方案,特别是考虑到它的其他用途。

与所有其他解决方案提到,我无法找到如何实例化类使用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

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

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

您可以使用类中的字典将所有序列化器与操作映射,然后从“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()