This is a slightly simplified example of the filterset I'm using, which I'm using with the DjangoFilterBackend for Django Rest Framework. I'd like to be able to send a request to /api/bookmarks/?title__contains=word1&title__contains=word2
and have results returned that contain both words, but currently it ignores the first parameter and only filters for word2.
Any help would be very appreciated!
class BookmarkFilter(django_filters.FilterSet):
class Meta:
model = Bookmark
fields = {
'title': ['startswith', 'endswith', 'contains', 'exact', 'istartswith', 'iendswith', 'icontains', 'iexact'],
}
class BookmarkViewSet(viewsets.ModelViewSet):
serializer_class = BookmarkSerializer
permission_classes = (IsAuthenticated,)
filter_backends = (DjangoFilterBackend,)
filter_class = BookmarkFilter
ordering_fields = ('title', 'date', 'modified')
ordering = '-modified'
page_size = 10
The DjangoFilterBackend class is used to filter the queryset based on a specified set of fields. This backend class automatically creates a FilterSet (django_filters. rest_framework. FilterSet) class for the given fields.
The filter() method is used to filter you search, and allows you to return only the rows that matches the search term.
The simplest way to filter the queryset of any view that subclasses GenericAPIView is to override the . get_queryset() method. Overriding this method allows you to customize the queryset returned by the view in a number of different ways.
The main problem is that you need a filter that understands how to operate on multiple values. There are basically two options:
MultipleChoiceFilter
(not recommended for this instance)Using MultipleChoiceFilter
class BookmarkFilter(django_filters.FilterSet):
title__contains = django_filters.MultipleChoiceFilter(
name='title',
lookup_expr='contains',
conjoined=True, # uses AND instead of OR
choices=[???],
)
class Meta:
...
While this retains your desired syntax, the problem is that you have to construct a list of choices. I'm not sure if you can simplify/reduce the possible choices, but off the cuff it seems like you would need to fetch all titles from the database, split the titles into distinct words, then create a set to remove duplicates. This seems like it would be expensive/slow depending on how many records you have.
Custom Filter
Alternatively, you can create a custom filter class - something like the following:
class MultiValueCharFilter(filters.BaseCSVFilter, filters.CharFilter):
def filter(self, qs, value):
# value is either a list or an 'empty' value
values = value or []
for value in values:
qs = super(MultiValueCharFilter, self).filter(qs, value)
return qs
class BookmarkFilter(django_filters.FilterSet):
title__contains = MultiValueCharFilter(name='title', lookup_expr='contains')
class Meta:
...
Usage (notice that the values are comma-separated):
GET /api/bookmarks/?title__contains=word1,word2
Result:
qs.filter(title__contains='word1').filter(title__contains='word2')
The syntax is changed a bit, but the CSV-based filter doesn't need to construct an unnecessary set of choices.
Note that it isn't really possible to support the ?title__contains=word1&title__contains=word2
syntax as the widget can't render a suitable html input. You would either need to use SelectMultiple
(which again, requires choices), or use javascript on the client to add/remove additional text inputs with the same name
attribute.
Without going into too much detail, filters and filtersets are just an extension of Django's forms.
Filter
has a form Field
, which in turn has a Widget
.FilterSet
is composed of Filter
s. FilterSet
generates an inner form based on its filters' fields.Responsibilities of each filter component:
data
QueryDict
. filter()
call to the queryset, using the validated value. In order to apply multiple values for the same filter, you would need a filter, field, and widget that understand how to operate on multiple values.
The custom filter achieves this by mixing in BaseCSVFilter
, which in turn mixes in a "comma-separation => list" functionality into the composed field and widget classes.
I'd recommend looking at the source code for the CSV mixins, but in short:
CharField
or IntegerField
). The field also derives the mixed in widget. The CSV filter was intended to be used with in
and range
lookups, which accept a list of values. In this case, contains
expects a single value. The filter()
method fixes this by iterating over the values and chaining together individual filter calls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With