简体   繁体   English

Django中具有模型继承的RESTful API

[英]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: 让我们坚持一个简单的示例场景:

  • Class Animal has attribute age . Class Animal有属性age
  • Class Bird extends Animal by an attribute wing , eg its size. Class Bird通过属性wing扩展Animal ,例如它的大小。
  • Class Dog extends Animal by an attribute tail , eg its length. Class Dog通过属性tail扩展Animal ,例如它的长度。

Exemplary requirements: 示例要求:

  • It would be sufficient if /animals/ listed all animals with their general attributes, ie their age. 如果/animals/列出所有动物的一般属性,即它们的年龄就足够了。
  • Assuming that the animal with ID=1 was a bird, /animals/1 should give something like: 假设ID=1的动物是鸟, /animals/1应该给出类似的东西:

{
    'age': '3',
    'class': 'Bird',
    'wing': 'big'
}
  • Assuming that the animal with 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. 我们需要一个辅助函数和两个类,没有进一步的依赖。

An extension to the 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)

Exemplary Usage 示例用法

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM