diff --git a/docs/guide/tips.txt b/docs/guide/tips.txt new file mode 100644 index 0000000..8548507 --- /dev/null +++ b/docs/guide/tips.txt @@ -0,0 +1,90 @@ +================== +Tips and Solutions +================== + +Common problems for declared filters +------------------------------------ + +Below are some of the common problem that occur when declaring filters. It is +recommended that you read this as it provides a more complete understanding of +how filters work. + + +Filter ``name`` and ``lookup_expr`` not configured +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +While ``name`` and ``lookup_expr`` are optional, it is recommended that you specify +them. By default, if ``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: + +.. code-block:: python + + class ProductFilter(django_filters.FilterSet): + price__gt = django_filters.NumberFilter() + +The filter instance will have a field name of ``price__gt`` and an ``exact`` +lookup type. Under the hood, this will incorrectly be resolved as: + +.. code-block:: python + + Produce.objects.filter(price__gt__exact=value) + +The above will most likely generate a ``FieldError``. The correct configuration +would be: + +.. code-block:: python + + class ProductFilter(django_filters.FilterSet): + price__gt = django_filters.NumberFilter(name='price', lookup_expr='gt') + + +Missing ``lookup_expr`` for text search filters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's quite common to forget to set the lookup expression for :code:`CharField` +and :code:`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. + + +Filter and lookup expression mismatch (in, range, isnull) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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: + +.. code-block:: python + + class Product(models.Model): + category = models.ForeignKey(Category, null=True) + +Given that ``category`` is optional, it's reasonable to want to enable a search +for uncategorized products. The following is an incorrectly configured +``isnull`` filter: + +.. code-block:: python + + class ProductFilter(django_filters.FilterSet): + uncategorized = django_filters.NumberFilter(name='category', lookup_expr='isnull') + +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: + +.. code-block:: python + + class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter): + pass + + class ProductFilter(django_filters.FilterSet): + categories = NumberInFilter(name='category', lookup_expr='in') + uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull') + +More info on constructing ``in`` and ``range`` csv :ref:`filters `. diff --git a/docs/guide/usage.txt b/docs/guide/usage.txt index 09f3397..425cf60 100644 --- a/docs/guide/usage.txt +++ b/docs/guide/usage.txt @@ -81,82 +81,6 @@ For Django version 1.8, transformed expressions are not supported. .. _`lookup reference`: https://docs.djangoproject.com/en/dev/ref/models/lookups/#module-django.db.models.lookups -Common declarative problems -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Below are some of the common problem that occur when declaring filters. It is -recommended that you do read this as it provides a more complete understanding -on how filters work. - - -Filter ``name`` and ``lookup_expr`` not configured -"""""""""""""""""""""""""""""""""""""""""""""""""" - -While ``name`` and ``lookup_expr`` are optional, it is recommended that you specify -them. By default, if ``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:: - - class ProductFilter(django_filters.FilterSet): - price__gt = django_filters.NumberFilter() - -The filter instance will have a field name of ``price__gt`` and an ``exact`` -lookup type. Under the hood, this will incorrectly be resolved as:: - - Produce.objects.filter(price__gt__exact=value) - -The above will most likely generate a ``FieldError``. The correct configuration -would be:: - - class ProductFilter(django_filters.FilterSet): - price__gt = django_filters.NumberFilter(name='price', lookup_expr='gt') - - -Missing ``lookup_expr`` for text search filters -""""""""""""""""""""""""""""""""""""""""""""""" - -It's quite common to forget to set the lookup expression for :code:`CharField` -and :code:`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. - - -Filter and lookup expression mismatch (in, range, isnull) -""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - -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:: - - class Product(models.Model): - category = models.ForeignKey(Category, null=True) - -Given that ``category`` is optional, it's reasonable to want to enable a search -for uncategorized products. The following is an incorrectly configured -``isnull`` filter:: - - class ProductFilter(django_filters.FilterSet): - uncategorized = django_filters.NumberFilter(name='category', lookup_expr='isnull') - -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 model -field. The following would correctly allow you to search for both uncategorized -products and products for a set of categories:: - - class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter): - pass - - class ProductFilter(django_filters.FilterSet): - categories = NumberInFilter(name='category', lookup_expr='in') - uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull') - -More info on constructing IN and RANGE csv :ref:`filters `. - - Generating filters with Meta.fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/index.txt b/docs/index.txt index 9d8f897..cb46017 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -14,6 +14,7 @@ do this. guide/install guide/usage guide/rest_framework + guide/tips guide/migration .. toctree::