我有一个包含模型的设置,该模型具有一组外键控制的对象,这些对象都从父类继承。围绕此设计的所有代码都运行良好,但我目前正在集成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."}
答案 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方法中使用不同的方法。