=================
FilterSet Options
=================

This document provides a guide on using additional FilterSet features.

Meta options
------------

- :ref:`model <model>`
- :ref:`fields <fields>`
- :ref:`exclude <exclude>`
- :ref:`form <form>`
- :ref:`filter_overrides <filter_overrides>`
- :ref:`unknown_field_behavior <unknown_field_behavior>`


.. _model:

Automatic filter generation with ``model``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``FilterSet`` is capable of automatically generating filters for a given
``model``'s fields. Similar to Django's ``ModelForm``, filters are created
based on the underlying model field's type. This option must be combined with
either the ``fields`` or ``exclude`` option, which is the same requirement for
Django's ``ModelForm`` class, detailed `here`__.

__ https://docs.djangoproject.com/en/stable/topics/forms/modelforms/#selecting-the-fields-to-use

.. code-block:: python

    class UserFilter(django_filters.FilterSet):
        class Meta:
            model = User
            fields = ['username', 'last_login']


.. _fields:

Declaring filterable ``fields``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``fields`` option is combined with ``model`` to automatically generate
filters. Note that generated filters will not overwrite filters declared on
the ``FilterSet``. The ``fields`` option accepts two syntaxes:

* a list of field names
* a dictionary of field names mapped to a list of lookups

.. code-block:: python

    class UserFilter(django_filters.FilterSet):
        class Meta:
            model = User
            fields = ['username', 'last_login']

    # or

    class UserFilter(django_filters.FilterSet):
        class Meta:
            model = User
            fields = {
                'username': ['exact', 'contains'],
                'last_login': ['exact', 'year__gt'],
            }

The list syntax will create an ``exact`` lookup filter for each field included
in ``fields``. The dictionary syntax will create a filter for each lookup
expression declared for its corresponding model field. These expressions may
include both transforms and lookups, as detailed in the `lookup reference`__.

__ https://docs.djangoproject.com/en/stable/ref/models/lookups/#module-django.db.models.lookups

Note that it is **not** necessary to include declared filters in a ``fields``
list - doing so will only affect the order in which fields appear on a FilterSet's form.  Including declarative aliases in a
``fields`` dict will raise an error.

.. code-block:: python

    class UserFilter(django_filters.FilterSet):
        username = filters.CharFilter()
        login_timestamp = filters.IsoDateTimeFilter(field_name='last_login')

        class Meta:
            model = User
            fields = {
                'username': ['exact', 'contains'],
                'login_timestamp': ['exact'],
            }

    TypeError("'Meta.fields' contains fields that are not defined on this FilterSet: login_timestamp")


.. _exclude:

Disable filter fields with ``exclude``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``exclude`` option accepts a blacklist of field names to exclude from
automatic filter generation. Note that this option will not disable filters
declared directly  on the ``FilterSet``.

.. code-block:: python

    class UserFilter(django_filters.FilterSet):
        class Meta:
            model = User
            exclude = ['password']


.. _form:

Custom Forms using ``form``
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The inner ``Meta`` class also takes an optional ``form`` argument.  This is a
form class from which ``FilterSet.form`` will subclass.  This works similar to
the ``form`` option on a ``ModelAdmin.``


.. _filter_overrides:

Customise filter generation with ``filter_overrides``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The inner ``Meta`` class also takes an optional ``filter_overrides`` argument.
This is a map of model fields to filter classes with options::

   class ProductFilter(django_filters.FilterSet):

        class Meta:
            model = Product
            fields = ['name', 'release_date']
            filter_overrides = {
                models.CharField: {
                    'filter_class': django_filters.CharFilter,
                    'extra': lambda f: {
                        'lookup_expr': 'icontains',
                    },
                },
                models.BooleanField: {
                    'filter_class': django_filters.BooleanFilter,
                    'extra': lambda f: {
                        'widget': forms.CheckboxInput,
                    },
                },
            }


.. _unknown_field_behavior:

Handling unknown fields with ``unknown_field_behavior``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``unknown_field_behavior`` option specifies how unknown fields are handled 
in a ``FilterSet``. You can set this option using the values of the 
``UnknownFieldBehavior`` enum:

- ``UnknownFieldBehavior.RAISE``: Raise an assertion error (default)
- ``UnknownFieldBehavior.WARN``: Issue a warning and ignore the field
- ``UnknownFieldBehavior.IGNORE``: Silently ignore the field

Note that both the ``WARN`` and ``IGNORE`` options do not include the unknown 
field(s) in the list of filters.

.. code-block:: python

    from django_filters import UnknownFieldBehavior

    class UserFilter(django_filters.FilterSet):
        class Meta:
            model = User
            fields = ['username', 'last_login']
            unknown_field_behavior = UnknownFieldBehavior.WARN


Overriding ``FilterSet`` methods
--------------------------------

When overriding classmethods, calling ``super(MyFilterSet, cls)`` may result
in a ``NameError`` exception. This is due to the ``FilterSetMetaclass`` calling
these classmethods before the ``FilterSet`` class has been fully created.
There are two recommmended workarounds:

1. If using python 3.6 or newer, use the argumentless ``super()`` syntax.
2. For older versions of python, use an intermediate class. Ex::

    class Intermediate(django_filters.FilterSet):

        @classmethod
        def method(cls, arg):
            super(Intermediate, cls).method(arg)
            ...

    class ProductFilter(Intermediate):
        class Meta:
            model = Product
            fields = ['...']

``filter_for_lookup()``
~~~~~~~~~~~~~~~~~~~~~~~

Prior to version 0.13.0, filter generation did not take into account the
``lookup_expr`` used. This commonly caused malformed filters to be generated
for 'isnull', 'in', and 'range' lookups (as well as transformed lookups). The
current implementation provides the following behavior:

- 'isnull' lookups return a ``BooleanFilter``
- 'in' lookups return a filter derived from the CSV-based ``BaseInFilter``.
- 'range' lookups return a filter derived from the CSV-based ``BaseRangeFilter``.

If you want to override the ``filter_class`` and ``params`` used to instantiate
filters for a model field, you can override ``filter_for_lookup()``. Ex::

    class ProductFilter(django_filters.FilterSet):
        class Meta:
            model = Product
            fields = {
                'release_date': ['exact', 'range'],
            }

        @classmethod
        def filter_for_lookup(cls, f, lookup_type):
            # override date range lookups
            if isinstance(f, models.DateField) and lookup_type == 'range':
                return django_filters.DateRangeFilter, {}

            # use default behavior otherwise
            return super().filter_for_lookup(f, lookup_type)


.. _filterset_factory:

Using ``filterset_factory``
---------------------------

A ``FilterSet`` for a ``model`` can also be created by the
``filterset_factory``, which creats a ``FilterSet`` with the ``model`` set in
the FilterSets Meta. You can pass a customized ``FilterSet`` class to the
``filterset_factory``, which then uses this class a a base for the created
``FilterSet``. Ex::

    class CustomFilterSet(django_filters.FilterSet):
        class Meta:
            form = CustomFilterSetForm


    filterset = filterset_factory(Product, filterset=CustomFilterSet)
