我想提供两种不同的序列化器,并且能够从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.
}
尽管以某种方式预先定义多个序列化器似乎是最明显的文档化方法,但FWIW还有另一种方法,它利用其他文档化的代码,并允许在实例化序列化器时将参数传递给它。我认为,如果需要基于各种因素生成逻辑,比如用户管理级别、被调用的操作,甚至实例的属性,那么可能更有价值。
这个难题的第一部分是关于在实例化时动态修改序列化器的文档。该文档没有解释如何从视图集调用这段代码,也没有解释如何在字段被初始化后修改字段的只读状态——但这并不难。
第二部分——get_serializer方法也被记录了——(在get_serializer_class的“其他方法”下面),因此它应该是安全的(源代码非常简单,希望这意味着由于修改而导致的意外副作用的可能性更小)。检查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,它应该知道该做什么。
Perhaps its worth mentioning that it can makes special sense if you want to adapt the serializer in some other ways …e.g. to do things like take in a read_only_exceptions list and use it to whitelist rather than blacklist fields (which I tend to do). I also find it useful to set the fields to an empty tuple if its not passed and then just remove the check for None ... and I set my fields definitions on my inheriting Serializers to 'all'. This means no fields that aren't passed when instantiating the serializer survive by accident and I also don't have to compare the serializer invocation with the inheriting serializer class definition to know what's been included...e.g within the init of the DynamicFieldsModelSerializer:
# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....
注意:如果我只是想要两个或三个类映射到不同的操作和/或我不想要任何特别的动态序列化器行为,我可能会使用其他人在这里提到的方法之一,但我认为这值得作为一种替代方案,特别是考虑到它的其他用途。