Django中带有模型继承的RESTful API
我需要实现一个RESTful API,它关注Django的REST框架对模型的继承。我认为这是构建API的一种简洁方法。其基本思想是仅通过所呈现的属性集来区分对象类。但Django如何做到这一点?让我们坚持一个简单的示例场景:Django中带有模型继承的RESTful API,django,rest,django-views,django-rest-framework,Django,Rest,Django Views,Django Rest Framework,我需要实现一个RESTful API,它关注Django的REST框架对模型的继承。我认为这是构建API的一种简洁方法。其基本思想是仅通过所呈现的属性集来区分对象类。但Django如何做到这一点?让我们坚持一个简单的示例场景: 类Animal具有属性age 类鸟通过属性翅膀扩展动物,例如其大小 类狗通过属性尾巴扩展动物,例如其长度 示范性要求: 如果/animals/列出所有动物的一般属性,即年龄,就足够了 假设ID=1的动物是一只鸟,/anists/1应该给出如下信息: 假设ID=
- 类
具有属性Animal
age
- 类
通过属性鸟
扩展翅膀
,例如其大小动物
- 类
通过属性狗
扩展尾巴
,例如其长度动物
- 如果
列出所有动物的一般属性,即年龄,就足够了/animals/
- 假设ID=1的动物是一只鸟,
应该给出如下信息:/anists/1
- 假设ID=2的动物是狗,
应该给出如下信息:/animates/2
经过一番努力,我想出了下面的解决方案,我相信这是相当令人满意的。我们需要一个helper函数和两个类,没有进一步的依赖关系 对超链接ModelSerializer的扩展 假设一个查询从
Animal
类返回一个对象,它实际上是一只Bird
。然后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
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__
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)
示范用法
这就是我们如何使用它。在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',)
class AnimalViewSet(viewsets.ModelViewSet):
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
在views.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',)
class AnimalViewSet(viewsets.ModelViewSet):
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
请记住,如果对这样的模型使用类继承,则任何查询都会有多个表和隐式连接。您最好使用。@dukebody:是的,但是对于抽象基类
Animal
,我不能像Animal.objects.all()
这样进行查询,而且我也不能将Dog.objects.all()
和Bird.objects.all()
,这将使处理Animal
对象变得非常不方便。对!你可能想考虑在动物模型中使用一个单独的“额外”JSON字段,用于特定的动物属性,或者甚至类似于[Django REST框架MungEngEng](),如果你的动物与其他关系领域无关,那么DukByth:你能更详细地解释一下“额外”的意思吗?Animal
模型中的JSON字段?请参阅。Django有json字段实现: