当反向关系为full = True时,Django Tastypie抛出'超出最大递归深度'。

时间:2012-07-19 22:47:48

标签: python django tastypie

如果运行以下代码,我会超出最大递归深度:

from tastypie import fields, utils
from tastypie.resources import ModelResource
from core.models import Project, Client


class ClientResource(ModelResource):
    projects = fields.ToManyField(
        'api.resources.ProjectResource', 'project_set', full=True
    )
    class Meta:
        queryset = Client.objects.all()
        resource_name = 'client'


class ProjectResource(ModelResource):
    client = fields.ForeignKey(ClientResource, 'client', full=True)
    class Meta:
        queryset = Project.objects.all()
        resource_name = 'project'

# curl http://localhost:8000/api/client/?format=json
# or
# curl http://localhost:8000/api/project/?format=json

如果其中一个关系有效,则设置为full = False。我确实理解为什么会发生这种情况,但我需要两种关系才能带来数据,而不仅仅是“resource_uri”。是否有Tastypie方法来做到这一点?我设法解决了在我的项目模型上创建序列化方法的问题,但它远非优雅。感谢。

3 个答案:

答案 0 :(得分:13)

您必须至少在一个资源上覆盖full_dehydrate方法,以跳过导致递归的脱水相关资源。

或者,您可以定义两种类型的资源,这些资源使用与full=True相同的模型,另一种使用full=False

答案 1 :(得分:3)

感谢@astevanovic指出正确的方向。

我发现重写dehydrate方法只处理某些指定的字段比覆盖full_hydrate方法跳过字段要简单一些。

为了追求可重用性,我想出了以下代码片段。希望它对某些人有用:

class BeeModelResource(ModelResource):

    def dehydrate(self, bundle):
        bundle = super(BeeModelResource, self).dehydrate(bundle)
        bundle = self.dehydrate_partial(bundle)        
        return bundle

    def dehydrate_partial(self, bundle):
        for field_name, resource_field in self.fields.items():
            if not isinstance(resource_field, RelatedField):
                continue

            if resource_field.full: # already dehydrated
                continue

            if not field_name in self._meta.partial_fields:
                continue

            if isinstance(resource_field, ToOneField):
                fk_object = getattr(bundle.obj, resource_field.attribute)
                fk_bundle = Bundle(obj=fk_object, request=bundle.request)
                fk_resource = resource_field.get_related_resource(fk_object)

                bundle.data[field_name] = fk_resource.dehydrate_selected( 
                        fk_bundle, self._meta.partial_fields[field_name]).data
            elif isinstance(resource_field, ToManyField):
                data = []

                fk_objects = getattr(bundle.obj, resource_field.attribute)
                for fk_object in fk_objects.all():
                    fk_bundle = Bundle(obj=fk_object, request=bundle.request)
                    fk_resource = resource_field.get_related_resource(fk_object)
                    fk_bundle = fk_resource.dehydrate_selected_fields( 
                            fk_bundle, self._meta.partial_fields[field_name])
                    data.append(fk_bundle.data)
                bundle.data[field_name] = data

        return bundle

    def dehydrate_selected_fields(self, bundle, selected_field_names):
        # Dehydrate each field.
        for field_name, field_object in self.fields.items():
            # A touch leaky but it makes URI resolution work. 
            # (borrowed from tastypie.resources.full_dehydrate)
            if field_name in selected_field_names and not self.is_special_fields(field_name):
                if getattr(field_object, 'dehydrated_type', None) == 'related':
                    field_object.api_name = self._meta.api_name
                    field_object.resource_name = self._meta.resource_name

                bundle.data[field_name] = field_object.dehydrate(bundle)

        bundle.data['resource_uri'] = self.get_resource_uri(bundle.obj)
        bundle.data['id'] = bundle.obj.pk

       return bundle

    @staticmethod
    def is_special_fields(field_name):
        return field_name in ['resource_uri']

以@sigmus为例,资源需要进行3次修改:

  1. 两个资源都将使用BeeModuleResource作为其超类(或者,将dehydrate_partial添加到一个资源,将dehydrate_selected添加到另一个资源。)
  2. 在任一资源上取消full=True
  3. partial_fields添加到资源Meta未设置资源
  4. ```

    class ClientResource(BeeModelResource): # make BeeModelResource a super class
        projects = fields.ToManyField(
            'api.resources.ProjectResource', 'project_set'
        ) # remove full=True
        class Meta:
            queryset = Client.objects.all()
            resource_name = 'client'
            partial_fields = {'projects': ['memo', 'title']} # add partial_fields
    
    class ProjectResource(BeeModelResource): # make BeeModelResource a super class
        client = fields.ForeignKey(ClientResource, 'client', full=True)
        class Meta:
            queryset = Project.objects.all()
            resource_name = 'project'
    

答案 2 :(得分:3)

死亡简单解决方案:在两个关系字段上设置use_in = 'list' kwarg!

文档:http://django-tastypie.readthedocs.org/en/latest/fields.html#use-in