【问题标题】:Accessing Subclassed Django Models through Tastypie's ToManyField通过 Tastypie 的 ToManyField 访问子类化的 Django 模型
【发布时间】:2015-07-30 19:58:15
【问题描述】:

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

我似乎无法让 sweetpie 与有问题的模型之一很好地搭配。我已经设法让 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)

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

目前,我的美味派资源看起来像这样:

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 调整为几乎所有可能的东西(父类、容器、firstinheritor),并试图摆弄这两个 ToManyFields 的属性,但是因为没有 -direct- Container 和两个 Inheritor 模型之间的联系,他们找不到彼此。

我尝试将 toManyField 子类化并重写它的许多函数以查看更改事物或强制某些变量的效果,但我没有运气。

按照目前的配置,尝试使用第一或第二次发布数据会导致

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

【问题讨论】:

    标签: django django-models tastypie


    【解决方案1】:

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

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

    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 反映了这一点,并期望每个子对象都有一个“类型”字段。 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 方法中使用不同的转换方法。

    【讨论】:

      猜你喜欢
      • 2013-01-03
      • 1970-01-01
      • 2013-11-30
      • 2011-08-16
      • 1970-01-01
      • 2014-03-21
      • 1970-01-01
      • 2012-07-09
      • 1970-01-01
      相关资源
      最近更新 更多