[英]RESTful API with model inheritance in Django
I need to implement a RESTful API that pays attention to the inheritance of models with Django's REST Framwork. 我需要实现一个RESTful API,它关注Django的REST Framwork模型的继承。 I think this is a neat approach for structuring the API. 我认为这是构建API的一种巧妙方法。 The underlying idea is to only differ the object classes by the set of attributes presented. 基本思想是仅通过所呈现的属性集来区分对象类。 But how can this be done with Django? 但是如何用Django完成呢? Lets stick to a simple exemplary scenario: 让我们坚持一个简单的示例场景:
Animal
has attribute age
. Class Animal
有属性age
。 Bird
extends Animal
by an attribute wing
, eg its size. Class Bird
通过属性wing
扩展Animal
,例如它的大小。 Dog
extends Animal
by an attribute tail
, eg its length. Class Dog
通过属性tail
扩展Animal
,例如它的长度。 Exemplary requirements: 示例要求:
/animals/
listed all animals with their general attributes, ie their age. 如果/animals/
列出所有动物的一般属性,即它们的年龄就足够了。 ID=1
was a bird, /animals/1
should give something like: 假设ID=1
的动物是鸟, /animals/1
应该给出类似的东西:
{
'age': '3',
'class': 'Bird',
'wing': 'big'
}
ID=2
was a dog, /animals/2
should give something like: 假设ID=2
的动物是狗, /animals/2
应该给出类似的东西:
{
'age': '8',
'class': 'Dog',
'tail': 'long'
}
I had no luck implementing that Django's REST framework, mainly because I haven't succeeded in adding/removing class-specific fields. 我没有运气实现Django的REST框架,主要是因为我没有成功添加/删除特定于类的字段。 Especially I'm wondering, how the create operation is to be implemented in such scenario? 特别是我想知道,在这种情况下如何实现创建操作?
tl;dr: It isn't as complicated as it looks. tl; dr: 它看起来并不复杂。 This solution proposes a fully reusable class that realizes the requested usecase with a minimum of code, as one can see from the exemplary usage below. 该解决方案提出了一个完全可重用的类,它使用最少的代码实现所请求的用例,如 下面 的 示例性用法 所示。
After some fiddling, I've come up with following solution, that, I believe, is quite satisfying. 经过一番摆弄,我想出了以下解决方案,我相信,这是非常令人满意的。 We will need one helper function and two classes for this, there are no further dependencies. 我们需要一个辅助函数和两个类,没有进一步的依赖。
HyperlinkedModelSerializer
HyperlinkedModelSerializer
的扩展 Suppose that a query returned an object from Animal
class, that actually is a Bird
. 假设一个查询从Animal
类返回一个对象, 实际上是一个Bird
。 Than get_actual
would resolve that Animal
to an object form Bird
class: 比get_actual
会将Animal
解析为对象形式Bird
类:
def get_actual(obj):
"""Expands `obj` to the actual object type.
"""
for name in dir(obj):
try:
attr = getattr(obj, name)
if isinstance(attr, obj.__class__):
return attr
except:
pass
return obj
The ModelField
defines a field that names the model that underlies a serializer: ModelField
定义了一个字段,该字段命名了序列化程序基础的模型:
class ModelField(serializers.ChoiceField):
"""Defines field that names the model that underlies a serializer.
"""
def __init__(self, *args, **kwargs):
super(ModelField, self).__init__(*args, allow_null=True, **kwargs)
def get_attribute(self, obj):
return get_actual(obj)
def to_representation(self, obj):
return obj.__class__.__name__
The HyperlinkedModelHierarchySerializer
does the magic: HyperlinkedModelHierarchySerializer
实现了神奇:
class HyperlinkedModelHierarchySerializer(serializers.HyperlinkedModelSerializer):
"""Extends the `HyperlinkedModelSerializer` to properly handle class hierearchies.
For an hypothetical model `BaseModel`, serializers from this
class are capable of also handling those models that are derived
from `BaseModel`.
The `Meta` class must whitelist the derived `models` to be
allowed. It also must declare the `model_dependent_fields`
attribute and those fields must also be added to its `fields`
attribute, for example:
wing = serializers.CharField(allow_null=True)
tail = serializers.CharField(allow_null=True)
class Meta:
model = Animal
models = (Bird, Dog)
model_dependent_fields = ('wing', 'tail')
fields = ('model', 'id', 'name') + model_dependent_fields
read_only_fields = ('id',)
The `model` field is defined by this class.
"""
model = ModelField(choices=[])
def __init__(self, *args, **kwargs):
"""Instantiates and filters fields.
Keeps all fields if this serializer is processing a CREATE
request. Retains only those fields that are independent of
the particular model implementation otherwise.
"""
super(HyperlinkedModelHierarchySerializer, self).__init__(*args, **kwargs)
# complete the meta data
self.Meta.models_by_name = {model.__name__: model for model in self.Meta.models}
self.Meta.model_names = self.Meta.models_by_name.keys()
# update valid model choices,
# mark the model as writable if this is a CREATE request
self.fields['model'] = ModelField(choices=self.Meta.model_names, read_only=bool(self.instance))
def remove_missing_fields(obj):
# drop those fields model-dependent fields that `obj` misses
unused_field_keys = set()
for field_key in self.Meta.model_dependent_fields:
if not hasattr(obj, field_key):
unused_field_keys |= {field_key}
for unused_field_key in unused_field_keys:
self.fields.pop(unused_field_key)
if not self.instance is None:
# processing an UPDATE, LIST, RETRIEVE or DELETE request
if not isinstance(self.instance, QuerySet):
# this is an UPDATE, RETRIEVE or DELETE request,
# retain only those fields that are present on the processed instance
self.instance = get_actual(self.instance)
remove_missing_fields(self.instance)
else:
# this is a LIST request, retain only those fields
# that are independent of the particular model implementation
for field_key in self.Meta.model_dependent_fields:
self.fields.pop(field_key)
def validate_model(self, value):
"""Validates the `model` field.
"""
if self.instance is None:
# validate for CREATE
if value not in self.Meta.model_names:
raise serializers.ValidationError('Must be one of: ' + (', '.join(self.Meta.model_names)))
else:
return value
else:
# model cannot be changed
return get_actual(self.instance).__class__.__name__
def create(self, validated_data):
"""Creates instance w.r.t. the value of the `model` field.
"""
model = self.Meta.models_by_name[validated_data.pop('model')]
for field_key in self.Meta.model_dependent_fields:
if not field_key in model._meta.get_all_field_names():
validated_data.pop(field_key)
self.fields.pop(field_key)
return model.objects.create(**validated_data)
And this is how we can use it. 这就是我们如何使用它。 In serializers.py
: 在serializers.py
:
class AnimalSerializer(HyperlinkedModelHierarchySerializer):
wing = serializers.CharField(allow_null=True)
tail = serializers.CharField(allow_null=True)
class Meta:
model = Animal
models = (Bird, Dog)
model_dependent_fields = ('wing', 'tail')
fields = ('model', 'id', 'name') + model_dependent_fields
read_only_fields = ('id',)
And in views.py
: 在views.py
:
class AnimalViewSet(viewsets.ModelViewSet):
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.