[Solved]-Django-filter messing around with empty field

4👍

I finally figured out the issue.

Apparently django-filters does not handle correctly the lookup in for foreign keys. The default filter for source__in for instance, is ModelChoiceFilter. So I had to explicitely define it as a ModelMultipleChoiceFilter.

However I faced another issue which is that source__in=10&source__in=7 roughly translates into Q(source__in=10) | Q(source__in=7). Which is raises an exception as 10 and 7 are not iterables. So I changed my code to use the exact lookup instead of in but still use the ModelMultipleChoiceFilter. Which, in the end, gives the following:

class BookingListFiltersForm(forms.Form):

    state__in = forms.MultipleChoiceField(
        choices=Booking.STATE_CHOICES, required=False,
        label=_("État"), widget=forms.CheckboxSelectMultiple)
    source = forms.ModelMultipleChoiceField(
        queryset=Platform.objects.all(), required=False,
        label=_("Source"), widget=ModelSelect2Multiple(
            url='autocomplete:platform'))


class BookingManagerFilter(filters.FilterSet):

    source = filters.ModelMultipleChoiceFilter(
        queryset=Platform.objects.all())
    payments__date = filters.DateFilter(method='payments__date_filter')
    payments__method = filters.ChoiceFilter(
        method='payments__method_filter',
        choices=BookingPayment.METHOD_CHOICES,
    )

    class Meta:
        model = Booking
        fields = {
            'period': [
                'endswith', 'endswith__gte', 'endswith__lte',
                'startswith', 'startswith__gte', 'startswith__lte',
            ],
            'state': ['in'],
            'source': ['exact'],
            'booking_date': ['date', 'date__lte', 'date__gte'],
            'accommodation': ['exact'],
            'guest': ['exact']
        }

    def get_form_class(self):
        return BookingListFiltersForm

7👍

I think documentation answers your question:

Filtering by an empty string

It’s not currently possible to filter by an empty string, since empty
values are interpreted as a skipped filter.

GET http://localhost/api/my-model?myfield=

Further in the docs you have examples of possible solutions. Im putting here one of them

Solution 1: Magic values

You can override the filter() method of a filter class to specifically
check for magic values. This is similar to the ChoiceFilter’s null
value handling.

GET http://localhost/api/my-model?myfield=EMPTY

class MyCharFilter(filters.CharFilter):
    empty_value = 'EMPTY'

    def filter(self, qs, value):
        if value != self.empty_value:
            return super(MyCharFilter, self).filter(qs, value)

        qs = self.get_method(qs)(**{'%s__%s' % (self.name, self.lookup_expr): ""})
        return qs.distinct() if self.distinct else qs

Right now i feel there is not enough information to solve your problem. I left a comment under your question. If you can provide that extra information it would greatly help understand what is going on.

For here are some tips that can help you track this bug:

  • Install ipdb. it will help you execute code step by step and inspect each variables.
  • Drop breakpoint import ipdb;ipdb.set_trace() before line

    File "/home/tony/.venvs/cocoonr/lib/python3.6/site-packages/django/views/generic/list.py" in get
      142.         self.object_list = self.get_queryset()
    

I suspect you should find the culprit in https://github.com/carltongibson/django-filter/blob/82a47fb7bbddedf179f110723003f3b28682d7fe/django_filters/filterset.py#L215

You can do something like this

class BookingManagerFilter(filters.FilterSet):
    # your previous code here

    def filter_queryset(self, queryset):
        import ipdb;ipdb.set_trace()
        return super(BookingManagerFilter, self)filter_queryset(queryset):

And run your endpoint, ipdb will stop the app and you will be able to step into the code and inspect it.

Leave a comment