【问题标题】:RESTful API with model inheritance in Django在 Django 中具有模型继承的 RESTful API
【发布时间】:2015-11-02 14:30:05
【问题描述】:

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

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

示例要求:

  • 如果/animals/ 列出所有动物的一般属性(即年龄)就足够了。
  • 假设带有ID=1 的动物是一只鸟,/animals/1 应该给出如下内容:

{
    'age': '3',
    'class': 'Bird',
    'wing': 'big'
}
  • 假设带有ID=2 的动物是一只狗,/animals/2 应该给出如下信息:

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

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

【问题讨论】:

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


    【解决方案1】:

    tl;dr: 它并不像看起来那么复杂。该解决方案提出了一个完全可重用的类,该类以最少的代码实现所请求的用例,正如您可以从以下 示例用法 看到的那样。


    经过一番折腾,我想出了以下解决方案,我相信这非常令人满意。为此,我们需要一个辅助函数和两个类,没有其他依赖项。

    HyperlinkedModelSerializer 的扩展

    假设一个查询从Animal 类返回一个对象,实际上 是一个Bird。比get_actualAnimal 解析为对象形式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:

    class AnimalViewSet(viewsets.ModelViewSet):
        queryset = Animal.objects.all()
        serializer_class = AnimalSerializer
    

    【讨论】:

    • 请记住,如果您将类继承与类似的模型一起使用,那么任何查询都会有多个表和隐式连接。你最好使用abstract base classes
    • @dukebody: 是的,但是有一个抽象基类Animal 我不能像Animal.objects.all() 这样的查询,我也不能把Dog.objects.all()Bird.objects.all() 联合起来,这将使处理Animal 对象变得非常不方便。
    • 对!您可能需要考虑在 Animal 模型中为特定的动物属性使用单独的“额外”JSON 字段,如果您的动物与其他关系字段无关,甚至可以考虑使用 [django-rest-framework-mongoengine] (github.com/umutbozkurt/django-rest-framework-mongoengine) 之类的字段。
    • @dukebody:您能否更详细地解释一下Animal 模型中的“额外”JSON 字段是什么意思?
    猜你喜欢
    • 1970-01-01
    • 2015-09-29
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    相关资源
    最近更新 更多