Search code examples
pythondjangodjango-admintastypie

How do I construct an AND query on the same field in the URL of TastyPie?


I want to filter results in the tastypie to get results that conform to both of two filters on the same field.

So if I have a simple model like this...

class Item(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField()

With a ModelResource...

class ItemResource(ModelResource):
    ...
    class Meta():
        queryset = Item.objects.all()
        resource_name = 'item'
        filtering = {'name': ALL, 'description': ALL}

I can easily construct 'AND' queries in the url of tastypie:

/api/v1/item/?name__contains=hello&description__contains=foo

But if I want to construct an AND operator on the same field, it only takes the second argument and ignores the first. That is,

/api/v1/item/?name__contains=hello&name__contains=world

returns resources whose name field contains 'world' but not those whose name field contains BOTH 'hello' and 'world'.

I understand how to do this directly in django:

Item.objects.filter(name__contains='hello').filter(name__contains='world')

But how do I construct this kind of a query in the URL of the tastypie?


Solution

  • I'm using the below. It will give you support for name__contains=hello,world. And you could also do negations name__contains!=foo.

    def build_filters(self, filters=None):
    
        """
        Adds support for negation filtering
        """
        if not filters:
            return filters
    
        applicable_filters = {}
        self.filters = filters
    
        # Normal filtering
        filter_params = dict([(x, filters[x]) for x in filter(lambda x: not x.endswith('!'), filters)])
        applicable_filters['filter'] = super(MainBaseResource, self).build_filters(filter_params)
    
        # Exclude filtering
        exclude_params = dict([(x[:-1], filters[x]) for x in filter(lambda x: x.endswith('!'), filters)])
        applicable_filters['exclude'] = super(MainBaseResource, self).build_filters(exclude_params)
    
        return applicable_filters
    
    def apply_filters(self, request, applicable_filters):
    
        """
        Adds support for:
        1. negation filtering: value_date__year!=2013
        2. multiple filtering value_date__year=2013,2012
        """
    
        from django.db.models import Q
        import operator
        from types import *
    
        objects = self.get_object_list(request)
    
        f = applicable_filters.get('filter')
    
        if f:
            # Q Filters for multiple values (1,2,3 etc)
            q_filters = []
            for key, val in f.iteritems():
                string = str(val)
                if ',' in string:
                    for excl_filter in string.split(','):
                        q_filters.append((key, excl_filter))
    
            q_list = [Q(x) for x in q_filters]
            for x in q_filters:
                try:
                    del f[x[0]]
                except:
                    pass
            if q_list:
                objects = objects.filter(reduce(operator.or_, q_list), **f)
            else:
                objects = objects.filter(**f)
    
        e = applicable_filters.get('exclude')
        if e:
            objects = objects.exclude(**e)
        return objects