Skip to Content

Filters Guide

Complete guide to configuring and using filters in Django Admin v2.0.

Overview

Django Admin uses the list_filter field in AdminConfig to define filters for the changelist view. Due to Pydantic’s type validation, there are specific requirements for filter configuration.

Type Definition

class AdminConfig(BaseModel): list_filter: List[Union[str, Type, Tuple[str, Type]]] = []

Accepted values:

  • str - Field name for simple filters
  • Type - Custom filter class
  • Tuple[str, Type] - Field name + filter class (for third-party filters like RangeFilter)

Simple Filters

Use field names as strings for basic filtering:

from django_cfg.modules.django_admin import AdminConfig config = AdminConfig( model=Payment, # Simple field filters list_filter=["status", "created_at", "is_active"], # Foreign key filters (shows related objects) list_filter=["user", "currency", "created_at"], # Choice field filters (shows available choices) list_filter=["status", "payment_method"], )

Auto-detected Filter Types

Django automatically selects the appropriate filter type based on the field:

Field TypeFilter TypeExample
BooleanFieldBoolean filter (Yes/No/All)is_active
CharField with choicesChoice filterstatus
ForeignKeyRelated object filteruser
DateField/DateTimeFieldDate hierarchy filtercreated_at
IntegerFieldExact matchamount

Custom Filter Classes

Define custom filter classes for advanced filtering logic:

Basic Custom Filter

from django.contrib import admin from django_cfg.modules.django_admin import AdminConfig class StatusFilter(admin.SimpleListFilter): title = 'payment status' parameter_name = 'status' def lookups(self, request, model_admin): return [ ('pending', 'Pending'), ('completed', 'Completed'), ('failed', 'Failed'), ] def queryset(self, request, queryset): if self.value() == 'pending': return queryset.filter(status='pending') elif self.value() == 'completed': return queryset.filter(status='completed') elif self.value() == 'failed': return queryset.filter(status='failed') return queryset config = AdminConfig( model=Payment, list_filter=[StatusFilter, "created_at"], # ✅ Class, then string )

Filter with Dynamic Choices

class UserWorkspaceFilter(admin.SimpleListFilter): title = 'workspace' parameter_name = 'workspace' def lookups(self, request, model_admin): # Get unique workspaces from database workspaces = Workspace.objects.filter( owner=request.user ).values_list('id', 'name') return workspaces def queryset(self, request, queryset): if self.value(): return queryset.filter(workspace_id=self.value()) return queryset config = AdminConfig( model=Session, list_filter=[UserWorkspaceFilter, "status", "created_at"], )

Date Hierarchy

For date-based filtering with drill-down navigation:

config = AdminConfig( model=Payment, # Adds year/month/day drill-down navigation at the top date_hierarchy="created_at", # Can still include in list_filter for sidebar list_filter=["status", "created_at"], )

Third-Party Filter Libraries

django-admin-rangefilter

✅ Tuple syntax is now fully supported!

from rangefilter.filters import DateRangeFilter, NumericRangeFilter config = AdminConfig( model=Payment, list_filter=[ ("created_at", DateRangeFilter), # ✅ Works! ("amount", NumericRangeFilter), # ✅ Works! "status", ] )

Unfold provides modern range filters with better UX. Use tuples to specify the field:

from unfold.contrib.filters.admin import ( RangeDateFilter, RangeDateTimeFilter, SingleNumericFilter, RangeNumericFilter, ) config = AdminConfig( model=Payment, list_filter=[ ("created_at", RangeDateFilter), # ✅ Date range with calendar ("amount", RangeNumericFilter), # ✅ Numeric range with inputs "status", ] )

For filtering by multiple related objects:

class HasRelatedFilter(admin.SimpleListFilter): title = 'has transactions' parameter_name = 'has_transactions' def lookups(self, request, model_admin): return [ ('yes', 'Has transactions'), ('no', 'No transactions'), ] def queryset(self, request, queryset): if self.value() == 'yes': return queryset.filter(transactions__isnull=False).distinct() elif self.value() == 'no': return queryset.filter(transactions__isnull=True) return queryset config = AdminConfig( model=Payment, list_filter=[HasRelatedFilter, "status", "created_at"], )

Complete Example

from django.contrib import admin from django.db.models import Q from rangefilter.filters import DateRangeFilter from django_cfg.modules.django_admin import ( AdminConfig, BadgeField, CurrencyField, DateTimeField, FieldsetConfig, Icons, UserField, ) from django_cfg.modules.django_admin.base import PydanticAdmin # Custom status filter with counts class PaymentStatusFilter(admin.SimpleListFilter): title = 'payment status' parameter_name = 'status' def lookups(self, request, model_admin): return [ ('pending', 'Pending'), ('processing', 'Processing'), ('completed', 'Completed'), ('failed', 'Failed'), ] def queryset(self, request, queryset): if self.value(): return queryset.filter(status=self.value()) return queryset # Amount range filter class AmountRangeFilter(admin.SimpleListFilter): title = 'amount range' parameter_name = 'amount_range' def lookups(self, request, model_admin): return [ ('0-100', '$0 - $100'), ('100-500', '$100 - $500'), ('500-1000', '$500 - $1000'), ('1000+', '$1000+'), ] def queryset(self, request, queryset): if self.value() == '0-100': return queryset.filter(amount_usd__gte=0, amount_usd__lt=100) elif self.value() == '100-500': return queryset.filter(amount_usd__gte=100, amount_usd__lt=500) elif self.value() == '500-1000': return queryset.filter(amount_usd__gte=500, amount_usd__lt=1000) elif self.value() == '1000+': return queryset.filter(amount_usd__gte=1000) return queryset # Configuration payment_config = AdminConfig( model=Payment, # List display list_display=[ 'internal_payment_id', 'user', 'amount_usd', 'status', 'created_at' ], # Display fields display_fields=[ BadgeField(name='internal_payment_id', variant='info', icon=Icons.RECEIPT), UserField(name='user', header=True), CurrencyField(name='amount_usd', currency='USD', precision=2), BadgeField( name='status', label_map={ 'pending': 'warning', 'processing': 'info', 'completed': 'success', 'failed': 'danger', } ), DateTimeField(name='created_at', ordering='created_at'), ], # Filters - mix of custom classes, tuples, and simple strings list_filter=[ PaymentStatusFilter, # Custom filter with choices AmountRangeFilter, # Custom range filter ("created_at", DateRangeFilter), # ✅ Date range filter (tuple) "currency", # Simple foreign key filter "is_test", # Simple boolean filter ], # Date hierarchy for top navigation date_hierarchy="created_at", # Search search_fields=['internal_payment_id', 'user__email', 'user__username'], # Other options ordering=['-created_at'], list_per_page=50, ) @admin.register(Payment) class PaymentAdmin(PydanticAdmin): """Payment admin with advanced filtering.""" config = payment_config # Autocomplete autocomplete_fields = ['user', 'currency']

Best Practices

1. Order Filters by Importance

# ✅ Good - most used filters first list_filter=[ "status", # Most common filter "created_at", # Date filter "is_active", # Boolean filter "user", # Less common ] # ❌ Avoid - random order list_filter=["user", "is_active", "status", "created_at"]

2. Use Custom Filters for Complex Logic

# ✅ Good - custom filter for complex queries class RecentPaymentsFilter(admin.SimpleListFilter): title = 'recent payments' parameter_name = 'recent' def lookups(self, request, model_admin): return [ ('24h', 'Last 24 hours'), ('7d', 'Last 7 days'), ('30d', 'Last 30 days'), ] def queryset(self, request, queryset): from datetime import timedelta from django.utils import timezone now = timezone.now() if self.value() == '24h': return queryset.filter(created_at__gte=now - timedelta(hours=24)) elif self.value() == '7d': return queryset.filter(created_at__gte=now - timedelta(days=7)) elif self.value() == '30d': return queryset.filter(created_at__gte=now - timedelta(days=30)) return queryset

3. Limit Filters for Large Datasets

# ❌ Avoid - too many foreign key filters on large datasets list_filter=["user", "workspace", "project", "task", "category"] # ✅ Good - use autocomplete or search instead list_filter=["status", "created_at"] search_fields=["user__email", "workspace__name"]

4. Use Date Hierarchy for Time-based Data

# ✅ Good - date hierarchy for drill-down config = AdminConfig( model=Payment, date_hierarchy="created_at", list_filter=["status", "currency"], )
# ✅ Good - filters for categories, search for specific items config = AdminConfig( model=Payment, list_filter=["status", "currency", "created_at"], search_fields=["internal_payment_id", "user__email", "transaction_hash"], )

Troubleshooting

Filter Not Appearing

Problem: Filter specified but not showing in admin

Solutions:

  1. Check field exists in model
  2. Verify field is not in exclude list
  3. For custom filters, check lookups() returns values
  4. Check user has permission to see filtered data

Performance Issues

Problem: Filters slow on large datasets

Solutions:

  1. Add database indexes on filtered fields
  2. Use select_related for foreign key filters
  3. Consider custom filter with optimized queryset
  4. Use autocomplete instead of showing all options
# ✅ Optimized configuration config = AdminConfig( model=Payment, # Add select_related for foreign key filters select_related=["user", "currency"], # Use indexed fields for filters list_filter=["status", "created_at"], # Both indexed )

Next Steps