Django中具有模型继承的RESTful API

时间:2015-08-10 15:31:26

标签: django rest django-views django-rest-framework

我需要实现一个RESTful API,它关注Django的REST Framwork模型的继承。我认为this是构建API的简洁方法。基本思想是仅通过所呈现的属性集来区分对象类。但是如何用Django完成呢?让我们坚持一个简单的示例场景:

  • 班级Animal具有属性age
  • Bird通过属性Animal扩展wing,例如它的大小。
  • Dog通过属性Animal扩展tail,例如它的长度。

示例性要求:

  • 如果/animals/列出所有具有一般属性的动物,即年龄,就足够了。
  • 假设ID=1的动物是一只鸟,/animals/1应该给出类似的东西:

{
    'age': '3',
    'class': 'Bird',
    'wing': 'big'
}
  • 假设ID=2的动物是狗,/animals/2应该给出类似的东西:

{
    'age': '8',
    'class': 'Dog',
    'tail': 'long'
}

我没有运气实现Django的REST框架,主要是因为我没有成功添加/删除特定于类的字段。特别是我想知道,如何在这种情况下实现创建操作?

1 个答案:

答案 0 :(得分:3)

tl; dr: 它看起来并不复杂。此解决方案提出了一个完全可重用的类,它使用最少的代码实现所请求的用例,如下面的 示例用法 所示。

经过一番摆弄,我提出了以下解决方案,我相信,这是非常令人满意的。我们需要一个辅助函数和两个类,没有进一步的依赖。

var Client = new HttpClient(); var request = new HttpRequestMessage( new HttpMethod("PATCH"), String.Format("/drive/items/{0}", folderId) ); var renameInstruction = new StringContent("{\"name\":\"NewFolder\"}"); request.Content = renameInstruction; var opResult = await Client.SendAsync(request);

的扩展名

假设查询从HyperlinkedModelSerializer类返回了一个对象,实际上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',)

views.py