--- name: django-filter description: "django-filter - Django filtering library for querysets with Django REST Framework integration" metadata: author: mte90 version: 1.0.0 tags: - django - django-filter - filtering - django-rest-framework - queryset --- # django-filter Django filtering library for dynamically filtering querysets, with full Django REST Framework integration. ## Overview django-filter provides a declarative way to filter querysets based on URL query parameters. - **Declarative** - Define filters as Python classes - **DRF Integration** - Seamless Django REST Framework support - **Flexible** - Custom filter backends and fields - **Auto-generation** - FilterSet from Django models --- ## Installation ```bash pip install django-filter # With DRF support pip install django-filter djangorestframework ``` Add to `INSTALLED_APPS`: ```python INSTALLED_APPS = [ # ... 'django_filters', ] ``` --- ## Basic Usage ### FilterSet Class ```python # filters.py import django_filters from .models import Product class ProductFilter(django_filters.FilterSet): # Exact match category = django_filters.NumberFilter(field_name='category_id') # Lookup expressions (icontains, exact, gt, gte, lt, lte, contains, in) name = django_filters.CharFilter(field_name='name', lookup_expr='icontains') price_min = django_filters.NumberFilter(field_name='price', lookup_expr='gte') price_max = django_filters.NumberFilter(field_name='price', lookup_expr='lte') # Boolean filter in_stock = django_filters.BooleanFilter(field_name='stock', lookup_expr='gt', method='filter_in_stock') # Date filters created_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte') created_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte') # Multiple selection (comma-separated) categories = django_filters.CharFilter(field_name='category_id', lookup_expr='in') # Ordering filter order_by = django_filters.OrderingFilter( fields=['price', 'created_at', 'name'], field_labels={'price': 'Price', 'created_at': 'Date'} ) class Meta: model = Product fields = ['category', 'name', 'price', 'in_stock', 'is_active'] def filter_in_stock(self, queryset, name, value): """Custom filter method for in_stock.""" if value: return queryset.filter(stock__gt=0) return queryset.filter(stock=0) ``` ### FilterSet with ModelForm ```python # Auto-generate filters from model fields class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = ['name', 'category', 'price', 'is_active', 'stock'] ``` --- ## Django REST Framework Integration ### ViewSet Integration ```python # views.py from rest_framework import viewsets from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters from .models import Product from .serializers import ProductSerializer from .filters import ProductFilter class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer # Filter backends filter_backends = [ DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter ] # Filterset class filterset_class = ProductFilter # Or inline filterset_fields filter_fields = ['category', 'is_active'] # Search configuration search_fields = ['name', 'description', 'category__name'] # Ordering fields ordering_fields = ['price', 'created_at', 'name'] ordering = ['-created_at'] ``` ### APIView Integration ```python # views.py from rest_framework.views import APIView from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters from .models import Product from .serializers import ProductSerializer from .filters import ProductFilter class ProductListView(APIView): def get(self, request): queryset = Product.objects.all() # Apply filters manually filter_backend = DjangoFilterBackend() queryset = filter_backend.filter_queryset(request, queryset, self) serializer = ProductSerializer(queryset, many=True) return Response(serializer.data) ``` --- ## Filter Types Reference ### NumberFilter ```python price = django_filters.NumberFilter() price_gt = django_filters.NumberFilter(field_name='price', lookup_expr='gt') price_range = django_filters.RangeFilter(field_name='price') ``` ### CharFilter ```python name = django_filters.CharFilter(lookup_expr='icontains') description = django_filters.CharFilter(lookup_expr='contains') ``` ### DateFilter / DateTimeFilter ```python created = django_filters.DateFilter() created_after = django_filters.DateTimeFilter(field_name='created_at', lookup_expr='gte') ``` ### BooleanFilter ```python is_active = django_filters.BooleanFilter() is_featured = django_filters.BooleanFilter(field_name='is_featured', distinct=False) ``` ### ModelChoiceFilter / ModelMultipleChoiceFilter ```python category = django_filters.ModelChoiceFilter( queryset=Category.objects.all(), empty_label='All Categories' ) tags = django_filters.ModelMultipleChoiceFilter( queryset=Tag.objects.all(), conjoined=True # AND vs OR behavior ) ``` ### ChoiceFilter ```python status = django_filters.ChoiceFilter( choices=Product.STATUS_CHOICES, empty_label=None ) ``` ### UUIDFilter ```python id = django_filters.UUIDFilter(field_name='id') ``` ### AllValuesFilter / AllValuesMultipleFilter ```python # Dropdown with all possible values category = django_filters.AllValuesFilter(field_name='category_id') ``` ### DateFromToRangeFilter ```python date_range = django_filters.DateFromToRangeFilter(field_name='created_at') ``` ### TimeFromToRangeFilter ```python time_range = django_filters.TimeFromToRangeFilter(field_name='created_at') ``` ### DateTimeFromToRangeFilter ```python datetime_range = django_filters.DateTimeFromToRangeFilter(field_name='created_at') ``` ### NumberRangeFilter ```python price_range = django_filters.NumberRangeFilter(field_name='price') ``` --- ## Custom Filter Methods ### Method Filter with Request Context ```python class ProductFilter(django_filters.FilterSet): # Access request in filter method category = django_filters.NumberFilter(method='filter_by_category') class Meta: model = Product fields = ['category'] def filter_by_category(self, queryset, name, value): # Access request, user, etc. via self.request user = self.request.user if user.is_premium: return queryset.filter(category_id=value) return queryset.filter(category_id=value, category__is_premium=False) ``` ### Filter with Multiple Fields ```python class ProductFilter(django_filters.FilterSet): price_min = django_filters.NumberFilter(field_name='price', lookup_expr='gte') price_max = django_filters.NumberFilter(field_name='price', lookup_expr='lte') min_max_price = django_filters.NumberFilter(method='filter_price_range') def filter_price_range(self, queryset, name, value): # Handle custom filter logic return queryset.filter(price__gte=value) ``` --- ## DRF Tips & Patterns ### FilterBackend per Action ```python class ProductViewSet(viewsets.ModelViewSet): def get_filter_backends(self): if self.action == 'list': return [DjangoFilterBackend, filters.SearchFilter] return [] class OrderViewSet(viewsets.ModelViewSet): filter_backends = [DjangoFilterBackend] filterset_class = OrderFilter def get_queryset(self): # Exclude cancelled orders for non-admin users if not self.request.user.is_staff: return Order.objects.exclude(status='cancelled') return Order.objects.all() ``` ### Conditional Filter Fields ```python class ProductFilter(django_filters.FilterSet): class Meta: model = Product fields = [] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Add fields dynamically based on user if self.request.user.is_staff: self.filters['is_active'].field_class = django_filters.BooleanFilter() ``` ### Select Related in Filtered Viewsets ```python class ProductViewSet(viewsets.ModelViewSet): serializer_class = ProductSerializer def get_queryset(self): # Use select_related to avoid N+1 return Product.objects.select_related('category').prefetch_related('tags') ``` --- ## URL Patterns ### Basic ```bash GET /api/products/ GET /api/products/?category=1 GET /api/products/?category=1&price_min=10&price_max=100 GET /api/products/?search=laptop GET /api/products/?ordering=price GET /api/products/?ordering=-price GET /api/products/?categories=1,2,3 ``` ### With Filterset Class ```bash GET /api/products/?name__icontains=laptop GET /api/products/?price__gte=10 GET /api/products/?in_stock=true GET /api/products/?created_after=2024-01-01 ``` ### Range Filters ```bash GET /api/products/?price_range_min=10&price_range_max=100 GET /api/products/?date_range_after=2024-01-01&date_range_before=2024-12-31 ``` --- ## Best Practices ### Filter Naming Conventions ```python # Use descriptive names that map to query params price_min = django_filters.NumberFilter(field_name='price', lookup_expr='gte') price_max = django_filters.NumberFilter(field_name='price', lookup_expr='lte') # In URL: ?price_min=10&price_max=100 ``` ### Performance ```python # Always use select_related/prefetch_related in get_queryset class ProductViewSet(viewsets.ModelViewSet): def get_queryset(self): return Product.objects.select_related('category', 'vendor').prefetch_related('tags') ``` ### Validation ```python class ProductFilter(django_filters.FilterSet): price_min = django_filters.NumberFilter() price_max = django_filters.NumberFilter() class Meta: model = Product fields = ['price_min', 'price_max'] def validate_price_range(self, cleaned_data): if cleaned_data.get('price_min') and cleaned_data.get('price_max'): if cleaned_data['price_min'] > cleaned_data['price_max']: raise serializers.ValidationError("price_min must be less than price_max") return cleaned_data ``` --- ## Do - Use `filterset_class` for complex filtering logic - Use `select_related` and `prefetch_related` to avoid N+1 queries - Use meaningful filter field names (`price_min`, `price_max` instead of `price` used twice) - Add empty labels for optional filters with ModelChoiceFilter ## Don't - Don't expose all model fields as filters - only expose what's needed - Don't forget to add appropriate indexes on filtered fields - Don't use filters that require expensive operations without pagination --- ## References - **django-filter Docs**: https://django-filter.readthedocs.io/en/stable/guide/usage.html - **DRF Integration**: https://django-filter.readthedocs.io/en/stable/guide/rest_framework.html - **Tips**: https://django-filter.readthedocs.io/en/stable/guide/tips.html - **GitHub**: https://github.com/carltongibson/django-filter