diff --git a/docs/ref/contrib/admin/filters.txt b/docs/ref/contrib/admin/filters.txt new file mode 100644 index 0000000000..a0cd931ce4 --- /dev/null +++ b/docs/ref/contrib/admin/filters.txt @@ -0,0 +1,193 @@ +.. _modeladmin-list-filters: + +=========================== +``ModelAdmin`` List Filters +=========================== + +.. currentmodule:: django.contrib.admin + +``ModelAdmin`` classes can define list filters that appear in the right sidebar +of the change list page of the admin, as illustrated in the following +screenshot: + +.. image:: _images/list_filter.png + +To activate per-field filtering, set :attr:`ModelAdmin.list_filter` to a list +or tuple of elements, where each element is one of the following types: + +- A field name. +- A subclass of ``django.contrib.admin.SimpleListFilter``. +- A 2-tuple containing a field name and a subclass of + ``django.contrib.admin.FieldListFilter``. + +See the examples below for discussion of each of these options for defining +``list_filter``. + +Using a field name +================== + +The simplest option is to specify the required field names from your model. + +Each specified field should be either a ``BooleanField``, ``CharField``, +``DateField``, ``DateTimeField``, ``IntegerField``, ``ForeignKey`` or +``ManyToManyField``, for example:: + + class PersonAdmin(admin.ModelAdmin): + list_filter = ('is_staff', 'company') + +Field names in ``list_filter`` can also span relations +using the ``__`` lookup, for example:: + + class PersonAdmin(admin.UserAdmin): + list_filter = ('company__name',) + +Using a ``SimpleListFilter`` +============================ + +For custom filtering, you can define your own list filter by subclassing +``django.contrib.admin.SimpleListFilter``. You need to provide the ``title`` +and ``parameter_name`` attributes, and override the ``lookups`` and +``queryset`` methods, e.g.:: + + from datetime import date + + from django.contrib import admin + from django.utils.translation import gettext_lazy as _ + + class DecadeBornListFilter(admin.SimpleListFilter): + # Human-readable title which will be displayed in the + # right admin sidebar just above the filter options. + title = _('decade born') + + # Parameter for the filter that will be used in the URL query. + parameter_name = 'decade' + + def lookups(self, request, model_admin): + """ + Returns a list of tuples. The first element in each + tuple is the coded value for the option that will + appear in the URL query. The second element is the + human-readable name for the option that will appear + in the right sidebar. + """ + return ( + ('80s', _('in the eighties')), + ('90s', _('in the nineties')), + ) + + def queryset(self, request, queryset): + """ + Returns the filtered queryset based on the value + provided in the query string and retrievable via + `self.value()`. + """ + # Compare the requested value (either '80s' or '90s') + # to decide how to filter the queryset. + if self.value() == '80s': + return queryset.filter( + birthday__gte=date(1980, 1, 1), + birthday__lte=date(1989, 12, 31), + ) + if self.value() == '90s': + return queryset.filter( + birthday__gte=date(1990, 1, 1), + birthday__lte=date(1999, 12, 31), + ) + + class PersonAdmin(admin.ModelAdmin): + list_filter = (DecadeBornListFilter,) + +.. note:: + + As a convenience, the ``HttpRequest`` object is passed to the ``lookups`` + and ``queryset`` methods, for example:: + + class AuthDecadeBornListFilter(DecadeBornListFilter): + + def lookups(self, request, model_admin): + if request.user.is_superuser: + return super().lookups(request, model_admin) + + def queryset(self, request, queryset): + if request.user.is_superuser: + return super().queryset(request, queryset) + + Also as a convenience, the ``ModelAdmin`` object is passed to the + ``lookups`` method, for example if you want to base the lookups on the + available data:: + + class AdvancedDecadeBornListFilter(DecadeBornListFilter): + + def lookups(self, request, model_admin): + """ + Only show the lookups if there actually is + anyone born in the corresponding decades. + """ + qs = model_admin.get_queryset(request) + if qs.filter( + birthday__gte=date(1980, 1, 1), + birthday__lte=date(1989, 12, 31), + ).exists(): + yield ('80s', _('in the eighties')) + if qs.filter( + birthday__gte=date(1990, 1, 1), + birthday__lte=date(1999, 12, 31), + ).exists(): + yield ('90s', _('in the nineties')) + +Using a field name and an explicit ``FieldListFilter`` +====================================================== + +Finally, if you wish to specify an explicit filter type to use with a field you +may provide a ``list_filter`` item as a 2-tuple, where the first element is a +field name and the second element is a class inheriting from +``django.contrib.admin.FieldListFilter``, for example:: + + class PersonAdmin(admin.ModelAdmin): + list_filter = ( + ('is_staff', admin.BooleanFieldListFilter), + ) + +Here the ``is_staff`` field will use the ``BooleanFieldListFilter``. Specifying +only the field name, fields will automatically use the appropriate filter for +most cases, but this format allows you to control the filter used. + +The following examples show available filter classes that you need to opt-in +to use. + +You can limit the choices of a related model to the objects involved in +that relation using ``RelatedOnlyFieldListFilter``:: + + class BookAdmin(admin.ModelAdmin): + list_filter = ( + ('author', admin.RelatedOnlyFieldListFilter), + ) + +Assuming ``author`` is a ``ForeignKey`` to a ``User`` model, this will +limit the ``list_filter`` choices to the users who have written a book, +instead of listing all users. + +You can filter empty values using ``EmptyFieldListFilter``, which can +filter on both empty strings and nulls, depending on what the field +allows to store:: + + class BookAdmin(admin.ModelAdmin): + list_filter = ( + ('title', admin.EmptyFieldListFilter), + ) + +.. note:: + + The :class:`~django.contrib.contenttypes.fields.GenericForeignKey` field is + not supported. + +List filters typically appear only if the filter has more than one choice. A +filter's ``has_output()`` method controls whether or not it appears. + +It is possible to specify a custom template for rendering a list filter:: + + class FilterWithCustomTemplate(admin.SimpleListFilter): + template = "custom_template.html" + +See the default template provided by Django (``admin/filter.html``) for a +concrete example. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 0f427c06f0..5caea603c1 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -66,6 +66,7 @@ Other topics :maxdepth: 1 actions + filters admindocs javascript @@ -853,159 +854,11 @@ subclass:: .. attribute:: ModelAdmin.list_filter Set ``list_filter`` to activate filters in the right sidebar of the change - list page of the admin, as illustrated in the following screenshot: + list page of the admin. - .. image:: _images/list_filter.png - - ``list_filter`` should be a list or tuple of elements, where each element - should be of one of the following types: - - * a field name, where the specified field should be either a - ``BooleanField``, ``CharField``, ``DateField``, ``DateTimeField``, - ``IntegerField``, ``ForeignKey`` or ``ManyToManyField``, for example:: - - class PersonAdmin(admin.ModelAdmin): - list_filter = ('is_staff', 'company') - - Field names in ``list_filter`` can also span relations - using the ``__`` lookup, for example:: - - class PersonAdmin(admin.UserAdmin): - list_filter = ('company__name',) - - * a class inheriting from ``django.contrib.admin.SimpleListFilter``, - which you need to provide the ``title`` and ``parameter_name`` - attributes to and override the ``lookups`` and ``queryset`` methods, - e.g.:: - - from datetime import date - - from django.contrib import admin - from django.utils.translation import gettext_lazy as _ - - class DecadeBornListFilter(admin.SimpleListFilter): - # Human-readable title which will be displayed in the - # right admin sidebar just above the filter options. - title = _('decade born') - - # Parameter for the filter that will be used in the URL query. - parameter_name = 'decade' - - def lookups(self, request, model_admin): - """ - Returns a list of tuples. The first element in each - tuple is the coded value for the option that will - appear in the URL query. The second element is the - human-readable name for the option that will appear - in the right sidebar. - """ - return ( - ('80s', _('in the eighties')), - ('90s', _('in the nineties')), - ) - - def queryset(self, request, queryset): - """ - Returns the filtered queryset based on the value - provided in the query string and retrievable via - `self.value()`. - """ - # Compare the requested value (either '80s' or '90s') - # to decide how to filter the queryset. - if self.value() == '80s': - return queryset.filter(birthday__gte=date(1980, 1, 1), - birthday__lte=date(1989, 12, 31)) - if self.value() == '90s': - return queryset.filter(birthday__gte=date(1990, 1, 1), - birthday__lte=date(1999, 12, 31)) - - class PersonAdmin(admin.ModelAdmin): - list_filter = (DecadeBornListFilter,) - - .. note:: - - As a convenience, the ``HttpRequest`` object is passed to the - ``lookups`` and ``queryset`` methods, for example:: - - class AuthDecadeBornListFilter(DecadeBornListFilter): - - def lookups(self, request, model_admin): - if request.user.is_superuser: - return super().lookups(request, model_admin) - - def queryset(self, request, queryset): - if request.user.is_superuser: - return super().queryset(request, queryset) - - Also as a convenience, the ``ModelAdmin`` object is passed to - the ``lookups`` method, for example if you want to base the - lookups on the available data:: - - class AdvancedDecadeBornListFilter(DecadeBornListFilter): - - def lookups(self, request, model_admin): - """ - Only show the lookups if there actually is - anyone born in the corresponding decades. - """ - qs = model_admin.get_queryset(request) - if qs.filter(birthday__gte=date(1980, 1, 1), - birthday__lte=date(1989, 12, 31)).exists(): - yield ('80s', _('in the eighties')) - if qs.filter(birthday__gte=date(1990, 1, 1), - birthday__lte=date(1999, 12, 31)).exists(): - yield ('90s', _('in the nineties')) - - * a tuple, where the first element is a field name and the second - element is a class inheriting from - ``django.contrib.admin.FieldListFilter``, for example:: - - class PersonAdmin(admin.ModelAdmin): - list_filter = ( - ('is_staff', admin.BooleanFieldListFilter), - ) - - You can limit the choices of a related model to the objects involved in - that relation using ``RelatedOnlyFieldListFilter``:: - - class BookAdmin(admin.ModelAdmin): - list_filter = ( - ('author', admin.RelatedOnlyFieldListFilter), - ) - - Assuming ``author`` is a ``ForeignKey`` to a ``User`` model, this will - limit the ``list_filter`` choices to the users who have written a book - instead of listing all users. - - You can filter empty values using ``EmptyFieldListFilter``, which can - filter on both empty strings and nulls, depending on what the field - allows to store:: - - class BookAdmin(admin.ModelAdmin): - list_filter = ( - ('title', admin.EmptyFieldListFilter), - ) - - .. note:: - - The ``FieldListFilter`` API is considered internal and might be - changed. - - .. note:: - - The :class:`~django.contrib.contenttypes.fields.GenericForeignKey` - field is not supported. - - List filter's typically appear only if the filter has more than one choice. - A filter's ``has_output()`` method controls whether or not it appears. - - It is possible to specify a custom template for rendering a list filter:: - - class FilterWithCustomTemplate(admin.SimpleListFilter): - template = "custom_template.html" - - See the default template provided by Django (``admin/filter.html``) for - a concrete example. + At it's simplest ``list_filter`` takes a list or tuple of field names to + activate filtering upon, but several more advanced options as available. + See :ref:`modeladmin-list-filters` for the details. .. attribute:: ModelAdmin.list_max_show_all