Django Tastypie throws a 'maximum recursion depth exceeded' when full=True on reverse relation.

[亡魂溺海] 提交于 2019-12-21 12:33:08

问题


I get a maximum recursion depth exceeded if a run the code below:

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

If a set full=False on one of the relations it works. I do understand why this is happening but I need both relations to bring data, not just the "resource_uri". Is there a Tastypie way to do it? I managed to solve the problem creating a serialization method on my Project Model, but it is far from elegant. Thanks.


回答1:


You would have to override full_dehydrate method on at least one resource to skip dehydrating related resource that is causing the recursion.

Alternatively you can define two types of resources that use the same model one with full=Trueand another with full=False.




回答2:


Thanks @astevanovic pointing the right direction.

I found that overriding dehydrate method to process only some specified fields is a bit less tedious than overriding full_hydrate method to skip fields.

In the pursuit of reusability, I came up with the following code snippets. Hope it would be useful to some:

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']

With @sigmus' example, the resources will need 3 modifications:

  1. both resource will use BeeModuleResource as its super class (or, add dehydrate_partial to one resource and dehydrate_selected to the other.)
  2. unset full=True on either of the resource
  3. add partial_fields into the resource Meta the unset resource

```

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'



回答3:


Dead simple solution: set the use_in = 'list' kwarg on both relationship fields!

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



来源:https://stackoverflow.com/questions/11570443/django-tastypie-throws-a-maximum-recursion-depth-exceeded-when-full-true-on-re

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!