Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF - is it possible to combine multiple filter parameters in the URL with some kind of OR logical symbol

I built up a REST endpoint using Django REST Framework.

class PersonFilter(django_filters.FilterSet):
    id = django_filters.NumberFilter(name="id", lookup_type="gt")
    first_name = django_filters.CharFilter(name="first_name", lookup_type="icontains")
    last_name = django_filters.CharFilter(name="last_name", lookup_type="icontains")

class Meta:
    model = Person
    fields = ('id', 'first_name', 'last_name', 'last_mod')

class PersonModelViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Person.objects.none()
    filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
    pagination_class = StandardResultsSetPagination
    ordering_fields = ('id', 'first_name', 'last_name', 'last_mod')
    ordering = ('last_mod', 'id')
    filter_class = PersonFilter

Now if I make a request like this:

/api/rest/v1/Person?first_name=foo&last_name=foo&page_size=10

This returns only those objects where both the first name and the last name contains "foo". I want to return those objects where first name contains "foo" OR last name contains "foo".

I wonder if there's a symbol usable in the URL parameters, which will mean logical or relationship between the filters.

One workaround can be to issue two separate AJAX queries to the endpoint, but that requires extra work to unify the result.

like image 382
Csaba Toth Avatar asked Jan 06 '16 05:01

Csaba Toth


2 Answers

Unfortunately, it's not possible with the current django_filter implementation. Every single filter modifies the queryset in-place instead of returning the Q object, which could be joined to your taste. You could try overriding the FilterSet.qs() method and doing some black magic on self._qs.query.where to recombine clauses using OR. See also a question on editing the queryset filters.

Update: As long as Django handles SQL injection attempts really well, you could just use something like:

qs.filter(map(operators.or_, [Q(k=v) for k, v in request.GET.items()]))

, but surely it needs some validation before putting it to production.

like image 165
Alex Morozov Avatar answered Sep 22 '22 09:09

Alex Morozov


I also wanted to do something similar to this and ended up creating a custom filter using django-filter to do it, hope this helps:

class NameFilter(django_filters.CharFilter):
  def filter(self, qs, value):
    if value:
      return qs.filter(Q(**{first_name+'__'+self.lookup_expr: value}) |
                       Q(**{last_name+'__'+self.lookup_expr: value}))
    return qs


class PersonFilter(django_filters.rest_framework.FilterSet):
  name = NameFilter(lookup_expr='icontains')

/api/rest/v1/Person?name=foo&page_size=10

not a very generic solution but it is an example of how to create your own filters, how generic it is depends on your code implementation.

like image 42
ArkD Avatar answered Sep 23 '22 09:09

ArkD