通过Tastypie的ToManyField访问Subclassed Django模型

时间:2015-07-30 19:58:16

标签: django django-models tastypie

我有一个包含模型的设置,该模型具有一组外键控制的对象,这些对象都从父类继承。围绕此设计的所有代码都运行良好,但我目前正在集成tastypie以进行API访问。

我似乎无法让tastypie与其中一个模型很好地配合。我已经设法让GET工作了,但是所有尝试为创建或编辑工作制作POST都会导致GET中断,或者根本没有任何工作。我已经列出了示例代码和当前在下面修复的尝试。

简化型号:

class Container(models.Model):
    variable = models.CharField(max_length=100)


class ParentClass(models.Model):
    container = models.ForeignKey(Container)
    order = models.IntegerField()


class FirstInheritor(ParentClass):
    important_data = models.IntegerField(default=0)


class SecondInheritor(ParentClass):
    other_data = models.IntegerField(default=0)

完整代码中的实际继承者模型还有很多,但该示例具有相同的概念:来自父模型的多个继承者,它具有我正在尝试启用的模型的外键(容器)的适用性。

目前,我的tastypie资源看起来像这样:

class FirstInheritorResource(resources.ModelResource):
    container = fields.ForeignKey('api.ContainerResource', 'container')

    class Meta:
        queryset = FirstInheritor.objects.all()
        authorization = Authorization()
        allowed_methods = ['get', 'post']


class SecondInheritorResource(resources.ModelResource):
    container = fields.ForeignKey('api.ContainerResource', 'container')

    class Meta:
        queryset = SecondInheritor.objects.all()
        authorization = Authorization()
        allowed_methods = ['get', 'post']


class ContainerResource(resources.ModelResource):
    firsts = fields.ToManyField(FirstInheritorResource, attribute=lambda bundle: FirstInheritor.objects.filter(container=bundle.obj), related_name="parentclass", full=True, null=True)
    seconds = fields.ToManyField(SecondInheritorResource, attribute=lambda bundle: SecondInheritor.objects.filter(container=bundle.obj), related_name="parentclass", full=True, null=True)

    class Meta:
        queryset = Container.objects.all()
        authorization = Authorization()
        allowed_methods = ['get', 'post']

使用此代码,GET请求显然有效。我已经尝试将“firsts”和“seconds”的related_name调整为几乎所有可能的东西(parentclass,container,firstinheritor),并试图摆弄这两个ToManyFields的属性,但由于没有-direct-在Container和两个继承者模型之间的连接,他们找不到彼此。

我已经尝试了对toManyField进行子类化并重写其中的许多函数,以查看更改内容或强制某些变量的效果,但我没有运气。

由于目前已配置,尝试以第一或第二次发布数据会导致

{"error": "The 'container' field has no data and doesn't allow a default or null value."}

1 个答案:

答案 0 :(得分:0)

所以,我想我已经在这里解决了这个问题。

首先,我将父模型子类化为以下模型:

class InheritableOrderedModel(models.Model):
real_type = models.ForeignKey(ContentType, editable=False,)

def save(self, *args, **kwargs):
    if not self.id:
        self.real_type = self._get_real_type()
    super(InheritableOrderedModel, self).save(*args, **kwargs)

def _get_real_type(self):
    return ContentType.objects.get_for_model(type(self))

def cast(self):
    return self.real_type.get_object_for_this_type(pk=self.pk)

class Meta:
    abstract = True

我不确定这是多么有效,但它目前正在努力让我得到各种孩子的真正课程。

然后我创建了一个新的字段类型。我专门为我的孩子修改了get_related_resources和build_related资源,因此目前这不是一个完全可扩展的解决方案:

class PolymorphicRelatedField(fields.ToManyField):
def get_related_resource(self, related_instance):
    """
    Instaniates the related resource.
    """
    to = {
        FirstInheritor: FirstInheritorResource,
        SecondInheritor: SecondInheritorResource,

    }

    # Try to cast this item to it's real component
    try:
        related_model = related_instance.cast().__class__
        related_instance = related_instance.cast()
        related_resource = to[related_model]()
        print(related_model)
    except:
        related_resource = self.to_class()

    print "RELATED INSTANCE POSTFIX", related_instance
    # Fix the ``api_name`` if it's not present.
    if related_resource._meta.api_name is None:
        if self._resource and not self._resource._meta.api_name is None:
            related_resource._meta.api_name = self._resource._meta.api_name

    # Try to be efficient about DB queries.
    related_resource.instance = related_instance
    return related_resource

def build_related_resource(self, value, request=None, related_obj=None, related_name=None):
        """
        Returns a bundle of data built by the related resource, usually via
        ``hydrate`` with the data provided.
        Accepts either a URI, a data dictionary (or dictionary-like structure)
        or an object with a ``pk``.
        """
        # Override the resource type based on the "type"
        if 'type' in value:
            thistype = value.pop('type')
            if thistype == "FirstInheritor":
                self.fk_resource = FirstInheritorResource()
            elif thistype == "SecondInheritor":
                self.fk_resource = SecondInheritorResource()
        else:
            self.fk_resource = self.to_class()

        kwargs = {
            'request': request,
            'related_obj': related_obj,
            'related_name': related_name,
        }

        if isinstance(value, Bundle):
            # Already hydrated, probably nested bundles. Just return.
            return value
        elif isinstance(value, six.string_types):
            # We got a URI. Load the object and assign it.
            return self.resource_from_uri(self.fk_resource, value, **kwargs)
        elif hasattr(value, 'items'):
            # We've got a data dictionary.
            # Since this leads to creation, this is the only one of these
            # methods that might care about "parent" data.
            return self.resource_from_data(self.fk_resource, value, **kwargs)
        elif hasattr(value, 'pk'):
            # We've got an object with a primary key.
            return self.resource_from_pk(self.fk_resource, value, **kwargs)
        else:
            raise ApiFieldError("The '%s' field was given data that was not a URI, not a dictionary-alike and does not have a 'pk' attribute: %s." % (self.instance_name, value))

我还使脱水功能儿童资源返回“类型”字段。 POST镜像了这一点,并期望每个子对象都有一个“type”字段。 build_related_resources查找此字段并将对象强制转换为其预期的类。

然后我需要为父对象添加另一个资源

class ParentResource(resources.ModelResource):
container = fields.ForeignKey('api.ContainerResource', 'container')

class Meta:
    queryset = ParentClass.objects.all()
    authorization = Authorization()
    allowed_methods = ['get', 'post']

将容器资源更改为:

class ContainerResource(resources.ModelResource):
    children = PolymorphicRelatedField(ParentResource, attribute=parentclass_set, related_name='container', null=True, full=True)

class Meta:
    queryset = Container.objects.all()
    authorization = Authorization()
    allowed_methods = ['get', 'post']

结果是api允许创建相关的子字段,以及在GET请求中正确显示相关字段。

我还没有通过编辑字段对此进行测试,可能需要解决一些我还没有找到的错误。

我应该注意到在解决这个问题的过程中,我发现了Django-Polymorphic。这个解决方案也可以轻松地使用django-polymorphic而不是“IngeritableOrderedModel”类,并在get_related_fields方法中使用不同的方法。