Django-filter: (2.0) FilterView always returning empty QuerySet for unbound FilterSet.

Created on 29 Jun 2018  ·  19Comments  ·  Source: carltongibson/django-filter

I have the a view, AccountList, which is trying to render a django_table2 table. The view's source code:

class AccountList(SingleTableMixin, FilterView):
    model = Account
    table_class = AccountTable
    template_name = 'accounts/account_list.html'
    context_table_name = 'object_list'
    ordering = ['vps']

    filterset_class = AccountFilter

This view is currently using this filterset (from django_filters):

import django_filters
from accounts.models import Account

class AccountFilter(django_filters.FilterSet):
    class Meta:
        model = Account
        fields = ['is_suspended', 'is_abandoned']

    is_suspended = django_filters.BooleanFilter(name='is_suspended', initial='False')
    is_abandoned = django_filters.BooleanFilter(name='is_abandoned', initial='False')

    def __init__(self, data=None, *args, **kwargs):
        # if filterset is bound, use initial values as defaults
        if data is not None:
            # get a mutable copy of the QueryDict
            data = data.copy()

            for name, f in self.base_filters.items():
                initial = f.extra.get('initial')

                # filter param is either missing or empty, use initial as default
                if not data.get(name) and initial:
                    data[name] = initial

        super().__init__(data, *args, **kwargs)

Using this template:

{% if filter %}
    <form action="" method="get" class="form form-inline">
        {{ filter.form.as_p }}
        <input type="submit" />
    </form>
{% endif %}

{% render_table object_list %}

{% endblock %}

This is my from my urls.py

path('', login_required(AccountList.as_view())),

When I visit my page, 127.0.0.1:8000, I see that the filters are not set:
enter image description here

But then if i do 127.0.0.1:8000?page=1, I see the filters are initialized properly:

enter image description here

What is causing my filters to not have default value when I don't have page=1 appended to my url?

Bug

Most helpful comment

Yep - started the PR but had to put my open source work on hold. I should be able to pick up where I left off fairly soon.

All 19 comments

Try using an actual False boolean value instead of the string value 'False'.

So when I tried the following:

is_suspended = django_filters.BooleanFilter(name='is_suspended', initial=False)
is_abandoned = django_filters.BooleanFilter(name='is_abandoned', initial=False)

This is how the filters are rendered on 127.0.0.1:8000/

image

Even though the filter was rendered with the right values, the filter had no effect as I can still see accounts that are suspended or abandoned.

Moreover, when I visit page 2 by pressing the 2 button at the bottom, I get to http://127.0.0.1:8000/?page=2 with the filters resetting to Unknown.

image

Do you see any reason why?

Sorry - nothing obvious comes to mind.

For the first issue, I'd recommend looking at the resulting SQL query. Make sure the suspended and abandoned filters are actually being applied.
For the second issue, I'd check the incoming data. It's possible that an invalid value is being provided to the suspended and abandoned filters when changing pages.

I inserted a few print statements and I found the following:

def __init__(self, data=None, *args, **kwargs):
    if data is not None:        # 1
        data = data.copy()

        for name, f in self.base_filters.items():
            initial = f.extra.get('initial')   

            # filter param is either missing or empty, use initial as default
            if not data.get(name) and initial:
                data[name] = initial

    super(BaseFilterSet, self).__init__(data, *args, **kwargs)

So it seems that data is None. Will you happen to know why my class based view is not passing data into AccountFilter?

Hm. The data argument should always be provided by FilterView.

https://github.com/carltongibson/django-filter/blob/b1f1c6592ef64a2172ec39040607a9c0b0714140/django_filters/views.py#L39-L46

Is data empty for both the first and second pages?

This isn't addressable as is. Closing pending enough info to identify an issue.

Same problem here after upgrade to 2.0. Either default page or empty filter should be present in the URL to get the results. Is this at least documented somewhere?

Still need more info to be able to reproduce...

page? Isn't this to do with your pagination, rather than Django Filter?

What info is needed exactly? Upgrading to 2.0 leads to the described behavior. Downgrading to 1.1.0 gets everything back to normal. I tried simplifying the view and the filterset to as minimalistic as possible, the behavior persists, so it seems like this should be fairly easy to reproduce.

@moorchegue, a minimal example test case or test project that demonstrates the issue would be helpful.

It also looks like the issue is with whatever is providing your pagination logic. Yes, there may be some incompatibility with the new version, but debugging issues in other packages is out of scope. You need to demonstrate a bug in Django Filter for there to be anything we can do here.

OK, so the problem here is with the is_valid() method introduced as part of #788.

https://github.com/carltongibson/django-filter/blob/1dde11c70eedeac32f6de92c426deb289e1aebd8/django_filters/filterset.py#L202-L206

This automatically returns False when data is None.

In the view we're then setting object_list to qs.none().

https://github.com/carltongibson/django-filter/blob/1dde11c70eedeac32f6de92c426deb289e1aebd8/django_filters/views.py#L80-L83

Hence the observed behaviour.

The test for this isn't failing because the test template is using filter.qs rather than object_list.

https://github.com/carltongibson/django-filter/blob/1dde11c70eedeac32f6de92c426deb289e1aebd8/tests/templates/tests/book_filter.html#L3-L5

The immediate work around is to set strict = False on your filterset. This has consequence that actual invalid filter parameters will lead to partial filtering rather than showing empty results, but it should be workable for the now. (Better is to override the view get logic of course...)

We'll have a think and improve the handling for the unbound case.

Hm. The primary deficiency here is the disparity between the View's object_list and the FilterSet's .qs. Moving the strict behavior back to the FilterSet.qs would fix that.

Additionally, there is no strict/non-strict handling for the DRF backend.

I'll WIP this up.

@rpkilby Is there any progress on this issue?

Yep - started the PR but had to put my open source work on hold. I should be able to pick up where I left off fairly soon.

Note that the workaround should be to set strict = False on your FilterMixin descendant (probably the View.

Can't you just change the condition to return the qs in case of unbounded form?

e.g.

-        if self.filterset.is_valid() or not self.get_strict():
+        if not self.filterset.is_bound or self.filterset.is_valid() or not self.get_strict():

https://github.com/carltongibson/django-filter/pull/1007

Thanks.

Was this page helpful?
0 / 5 - 0 ratings