While field_name and lookup_expr are optional, it is recommended that you specify
them. By default, if field_name is not specified, the filter’s name on the
filterset class will be used. Additionally, lookup_expr defaults to
exact. The following is an example of a misconfigured price filter:

It’s quite common to forget to set the lookup expression for CharField
and TextField and wonder why a search for “foo” does not return results
for “foobar”. This is because the default lookup type is exact, but you
probably want to perform an icontains lookup.

It’s not always appropriate to directly match a filter to its model field’s
type, as some lookups expect different types of values. This is a commonly
found issue with in, range, and isnull lookups. Let’s look
at the following product model:

So what’s the issue? While the underlying column type for category is an
integer, isnull lookups expect a boolean value. A NumberFilter however
only validates numbers. Filters are not ‘expression aware’ and won’t change
behavior based on their lookup_expr. You should use filters that match the
data type of the lookup expression instead of the data type underlying the
model field. The following would correctly allow you to search for both
uncategorized products and products for a set of categories:

In pre-1.0 versions of django-filter, a filter field’s initial value was used as a
default when no value was submitted. This behavior was not officially supported and has
since been removed.

Warning

It is recommended that you do NOT implement the below as it adversely
affects usability. Django forms don’t provide this behavior for a reason.

Using initial values as defaults is inconsistent with the behavior of Django forms.

Default values prevent users from filtering by empty values.

Default values prevent users from skipping that filter.

If defaults are necessary though, the following should mimic the pre-1.0 behavior:

classBaseFilterSet(FilterSet):def__init__(self,data=None,*args,**kwargs):# if filterset is bound, use initial values as defaultsifdataisnotNone:# get a mutable copy of the QueryDictdata=data.copy()forname,finself.base_filters.items():initial=f.extra.get('initial')# filter param is either missing or empty, use initial as defaultifnotdata.get(name)andinitial:data[name]=initialsuper(BaseFilterSet,self).__init__(data,*args,**kwargs)