2
votes

I'm having trouble applying filters to a ManyToMany relationship with tastypie. Here are my models

class Post(models.Model):
    user = models.ForeignKey(User, related_name='posts')
    title = models.TextField()

    class Meta:
        """ Meta """
        db_table = "post"

class Category(models.Model):
    posts = models.ManyToManyField(Post, through='PostCategory', null = True, blank = True)
    name = models.TextField()

    class Meta:
        """ Meta """
        db_table = "category"

class PostCategory(models.Model):
    post = models.ForeignKey(Post, related_name='posts')
    category = models.ForeignKey(Category, related_name='categories')

    class Meta:
        """ Meta """
        db_table = "post_category"

Here is api.py

class CategoryResource(ModelResource):
    class Meta:
        queryset = Category.objects.all()
        resource_name = 'category'
        filtering = {
            'id': ['exact'],
        }

class PostCategoryResource(ModelResource):
    category = fields.ToOneField(CategoryResource, 'category', full=True)
    class Meta:
        queryset = PostCategory.objects.all()
        resource_name = 'postcategory'
        filtering = {
            'category': ALL_WITH_RELATIONS,
            'id': ['exact'],
        }

class PostResource(ModelResource):
    user = fields.ToOneField(ProfileResource, 'user', full=True)
    categories = fields.ToManyField(PostCategoryResource,
            attribute=lambda bundle: bundle.obj.categories.through.objects.filter(
                post=bundle.obj,)
            or bundle.obj.categories, full=True)

    class Meta:
        queryset = Post.objects.all()
        resource_name = 'post'
        filtering = {
            'user': ALL_WITH_RELATIONS,
            'categories': ALL_WITH_RELATIONS,
            'id': ['exact'],
        }


My goal is to be able to get all of the posts by user_id or by category_id. The following works just as I want it to:
http://127.0.0.1/api/v1/post/?format=json&user__id=2

However, I am failing to get something like the this working as well:
http://127.0.0.1/api/v1/post/?format=json&categories__category__id=3

Here's the error I'm getting:

{"error_message": "sequence item 0: expected string, function found", "traceback": "Traceback >(most recent call last):\n\n File \"/usr/lib/python2.6/site-packages/django_tastypie-0.11.0->py2.6.egg/tastypie/resources.py\", line 195, in wrapper\n response = callback(request, >*args, **kwargs)\n\n File \"/usr/lib/python2.6/site-packages/django_tastypie-0.11.0->py2.6.egg/tastypie/resources.py\", line 426, in dispatch_list\n return >self.dispatch('list', request, **kwargs)\n\n File \"/usr/lib/python2.6/site->packages/django_tastypie-0.11.0-py2.6.egg/tastypie/resources.py\", line 458, in dispatch\n >response = method(request, **kwargs)\n\n File \"/usr/lib/python2.6/site->packages/django_tastypie-0.11.0-py2.6.egg/tastypie/resources.py\", line 1266, in get_list\n >objects = self.obj_get_list(bundle=base_bundle, **self.remove_api_resource_names(kwargs))\n\n >File \"/usr/lib/python2.6/site-packages/django_tastypie-0.11.0->py2.6.egg/tastypie/resources.py\", line 2044, in obj_get_list\n applicable_filters = >self.build_filters(filters=filters)\n\n File \"/usr/lib/python2.6/site->packages/django_tastypie-0.11.0-py2.6.egg/tastypie/resources.py\", line 1949, in >build_filters\n db_field_name = LOOKUP_SEP.join(lookup_bits)\n\nTypeError: sequence item >0: expected string, function found\n"}

3

3 Answers

1
votes

Seems like tasty doesn't allow you to filter by virtual field, when the field attribute is represented by lambda.

You can make it work with some modifications:

class PostCategory(models.Model):
    post = models.ForeignKey(Post, related_name='categories')
    category = models.ForeignKey(Category)


class PostResource(ModelResource):
    user = fields.ToOneField(ProfileResource, 'user', full=True)
    categories = fields.ToManyField(PostCategoryResource, 'categories', full=True)

    class Meta:
        queryset = Post.objects.all()
        resource_name = 'post'
        filtering = {
            # filter by user
            'user': ALL_WITH_RELATIONS,

            #filter by category id
            'categories': ALL_WITH_RELATIONS,
            'category': ALL,
            'id': ['exact'],
        }
0
votes
    change the resource fields as:

    categories= fields.ManyToManyField(PostResource, attribute=lambda bundle: bundle.obj.categories.all().order_by('name'),
                                       null=True, blank=True, full=True)

also you can use :
lambda bundle: categories.objects.filter(
        categories=bundle.obj, 
        user__id=2
    )
0
votes

I stumbled on this thread today when I received a tastypie error trying to filter on field where attribite is a callable: TypeError: sequence item 1: expected string or Unicode, function found.

It appears that tastypie requires attribute to be a string in order to be able to filter by it, but a callable when you want to filter nested objects. To compromise this, I implemented a CallableString which is a string that you can call:

class CallableString(unicode):
    def __init__(self, *args, **kwargs):
        super(CallableString, self).__init__(*args, **kwargs)

    def __call__(self, bundle):
        return InstitutionGroup.objects.published().filter(institutions=bundle.obj)

Replace implementation of __call__ with your own custom logic and add to your resource field:

groups = fields.ToManyField(
    InstitutionGroupResource,
    attribute=CallableString('institution_groups'),
    null=True,
    full=True,
)

I'd say this is a very hacky solution and might have some side effects, but seems to work for me for now - tastypie version 0.13.3