Advanced Filtering and Search
The filtering and search system in this codebase provides a flexible framework for narrowing down querysets in the Django admin. It ranges from automatic field-based filters to custom logic via SimpleListFilter and AJAX-powered search using AutocompleteJsonView.
Custom Logic with SimpleListFilter
When filtering requirements do not map directly to a single model field, SimpleListFilter (found in django/contrib/admin/filters.py) allows for arbitrary filtering logic. To implement a custom filter, you must define:
title: The human-readable label shown in the sidebar.parameter_name: The URL query parameter used for the filter.lookups(): Returns a list of tuples representing the choices available in the UI.queryset(): Defines how the queryset is modified based on the selected value.
Example: Filtering by Decade
In tests/admin_filters/tests.py, a DecadeListFilter is implemented to group records by time periods:
class DecadeListFilter(SimpleListFilter):
title = "publication decade"
parameter_name = "publication-decade"
def lookups(self, request, model_admin):
return (
("the 80s", "the 1980's"),
("the 90s", "the 1990's"),
("the 00s", "the 2000's"),
)
def queryset(self, request, queryset):
if self.value() == "the 80s":
return queryset.filter(year__gte=1980, year__lte=1989)
if self.value() == "the 90s":
return queryset.filter(year__gte=1990, year__lte=1999)
# ... additional logic for other decades
Field-Based Filtering
The FieldListFilter class serves as the base for filters tied to specific model fields. The system uses a registration mechanism (FieldListFilter.register) to determine which filter class to use for a given field type.
In django/contrib/admin/filters.py, several specialized subclasses are registered:
RelatedFieldListFilter: Used forForeignKeyandManyToManyField.BooleanFieldListFilter: Used forBooleanField.DateFieldListFilter: Provides date ranges like "Today", "Past 7 days", and "This month".
Customizing Field Filters in ModelAdmin
You can override the default filter for a field by providing a tuple in list_filter. For example, RelatedOnlyFieldListFilter limits choices to objects that actually have a relationship with the current model:
class BookAdmin(ModelAdmin):
list_filter = (
"year",
("is_best_seller", BooleanFieldListFilter),
("author", RelatedOnlyFieldListFilter),
DecadeListFilter,
)
Search and Autocomplete
The search functionality is driven by ModelAdmin.search_fields. When a user types into the search box, the admin calls get_search_results(), which constructs a filter using Q objects across the specified fields.
Autocomplete for Relations
For models with large numbers of related records, autocomplete_fields replaces the standard dropdown with a Select2-based AJAX search. This is handled by AutocompleteJsonView in django/contrib/admin/views/autocomplete.py.
The flow of an autocomplete request is as follows:
process_request: Validates that the targetModelAdminhassearch_fieldsdefined and that the user has view permissions.get_queryset: Retrieves the base queryset and appliesget_search_resultsusing the search term provided by the widget.serialize_result: Converts the model instances into JSON objects withidandtextkeys.
Customizing Autocomplete Results
You can extend AutocompleteJsonView to return additional data to the frontend. As seen in tests/admin_views/test_autocomplete_view.py, you can override serialize_result to include extra fields:
class AutocompleteJsonSerializeResultView(AutocompleteJsonView):
def serialize_result(self, obj, to_field_name):
return {
**super().serialize_result(obj, to_field_name),
"posted": str(obj.posted),
}
Performance and Faceting
The filtering system includes a "faceting" feature that shows the count of records for each filter choice. This is implemented via FacetsMixin and the get_facet_counts method.
In SimpleListFilter, get_facet_counts iterates through lookup_choices and performs an aggregation:
def get_facet_counts(self, pk_attname, filtered_qs):
counts = {}
for i, choice in enumerate(self.lookup_choices):
self.used_parameters[self.parameter_name] = choice[0]
lookup_qs = self.queryset(self.request, filtered_qs)
if lookup_qs is not None:
counts[f"{i}__c"] = models.Count(
pk_attname,
filter=models.Q(pk__in=lookup_qs),
)
return counts
This feature is controlled by the show_facets attribute on the ChangeList or ModelAdmin. When enabled, it provides real-time feedback on how many results match each filter option, though it may impact performance on very large datasets due to the additional count queries.