Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Django Filter

ASecurity

Use when filtering Django querysets - FilterSet, custom filters, Django REST Framework integration, explicit fields

21 stars
0 votes
0 copies
0 views
Added 10/2/2026
databasespythongobashsqlexpressdjangogitapibackendperformance

Works with

api

Security Analysis

A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned 10/2/2026

$npx -y skills add CodeAtCode/oss-ai-skills --skill django-filter --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Django Filter?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Django Filter
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/codeatcode-django-filter/badge)](https://www.skillsdirectory.com/skills/codeatcode-django-filter)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: django-filter
description: Use when filtering Django querysets - FilterSet, custom filters, Django REST Framework integration, explicit fields
metadata:
  author: mte90
  version: 1.0.1
  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 integration
pip install django-filter djangorestframework
```

Add to `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
    'django_filters',
]
```

---

## Basic FilterSet Pattern

```python
# filters.py
import django_filters
from .models import Product

class ProductFilter(django_filters.FilterSet):
    # Range filters (min/max naming convention)
    price_min = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
    price_max = django_filters.NumberFilter(field_name='price', lookup_expr='lte')
    
    # Text search
    name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
    
    # Boolean filter
    in_stock = django_filters.BooleanFilter(method='filter_in_stock')
    
    # Multiple selection (comma-separated values)
    categories = django_filters.CharFilter(field_name='category__slug', lookup_expr='in')
    
    # Date range
    created_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
    
    # Ordering
    order_by = django_filters.OrderingFilter(
        fields=[('price', 'price'), ('created_at', 'created_at'), ('name', 'name')]
    )

    class Meta:
        model = Product
        fields = '__all__'  # Django 6.0+: must be string '__all__' or explicit list

    def filter_in_stock(self, queryset, name, value):
        if value:
            return queryset.filter(stock__gt=0)
        return queryset

    class Meta:
        model = Product
        fields = ['category', 'name', 'price', 'stock']
```

### Django 6.0 Breaking Change

The `fields` attribute requires explicit `__all__`:

```python
class UserFilter(FilterSet):
    class Meta:
        model = User
        fields = '__all__'  # String, not list
```

Using `fields = []` without `__all__` raises an error in Django 6.0+.

---

## Filter Type Selection

Choose filter types based on query shape, not field type. See [django-filter field documentation](https://django-filter.readthedocs.io/en/stable/guide/fields.html) for the full API.

| Query shape | Filter type | Example |
|-------------|-------------|---------|
| Exact match | `CharFilter` / `NumberFilter` | `category = CharFilter()` |
| Multiple values | `CharFilter` with `lookup_expr='in'` | `ids = CharFilter(lookup_expr='in')` |
| Range (min/max) | Two separate filters | `price_min`, `price_max` |
| Date range | `DateFromToRangeFilter` | `date_range = DateFromToRangeFilter()` |
| Relational (FK/M2M) | Filter on related field | `author__name = CharFilter()` |
| Custom logic | `Filter` with `method=` | `status = Filter(method='filter_status')` |

**Avoid enumerating all 12+ filter types** — focus on the lookup expressions you actually need: `exact`, `icontains`, `gt`, `gte`, `lt`, `lte`, `in`, `isnull`.

---

## Composable QuerySet Methods

When filtering is driven by code paths rather than user-supplied query params,
define a custom `QuerySet` with one method per `WHERE` clause. The view reads
like a sentence, and each method is independently testable.

```python
from django.db import models
from django.utils import timezone

class EventQuerySet(models.QuerySet):
    def approved(self):
        return self.filter(approved_at__isnull=False)

    def future(self):
        today = timezone.localdate()
        return self.filter(end__gt=self._midnight(today))

    def _midnight(self, date):
        return timezone.datetime(date.year, date.month, date.day, tzinfo=timezone.utc)

    def with_tags(self, tags):
        if not tags:
            return self
        return self.filter(tags__name__in=tags).distinct()

    def is_free(self, free):
        if free is None:
            return self
        return self.filter(price=0) if free else self

    def for_tab(self, tab):
        if tab == 'featured':
            return self.filter(featured=True)
        if tab == 'popular':
            return self.filter(views__gte=100)
        return self

class Event(models.Model):
    objects = EventQuerySet.as_manager()
    # ...
```

Usage in a view — each method returns a queryset, so they chain naturally:

```python
def event_list(request, tab):
    qs = (
        Event.objects.approved()
        .for_tab(tab)
        .with_tags(request.GET.getlist('tags'))
        .is_free(request.GET.get('free') == 'true')
    )
    return render(request, 'events/list.html', {'events': qs})
```

### Guidelines for QuerySet methods

- Return `self` (not `None`) when the filter is a no-op, so chains never break.
- Guard each method against its argument being empty/None — callers should not
  have to branch before calling.
- Keep methods single-purpose; compose in the view rather than adding flags.

---

## FilterSet Composition

### Combining FilterSets with `&`

Combine multiple `FilterSet` classes to create composite filters:

```python
class BaseProductFilter(django_filters.FilterSet):
    """Common filters used across multiple views."""
    search = django_filters.CharFilter(method='filter_search')
    category = django_filters.ModelChoiceFilter(
        field_name='category',
        queryset=Category.objects.active()
    )

    class Meta:
        model = Product
        fields = []

    def filter_search(self, queryset, name, value):
        if not value:
            return queryset
        return queryset.filter(
            models.Q(name__icontains=value) |
            models.Q(description__icontains=value)
        )

class PriceFilter(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')
    on_sale = django_filters.BooleanFilter(method='filter_on_sale')

    class Meta:
        model = Product
        fields = []

    def filter_on_sale(self, queryset, name, value):
        if value:
            return queryset.filter(discount__gt=0)
        return queryset

# Combine filters
ProductFilter = BaseProductFilter & PriceFilter
```

**Failure mode**: Without composition, you either duplicate filter definitions or create bloated monolithic `FilterSet` classes. The `&` operator merges filters cleanly.

### Subclassing for shared base filters

```python
class BaseFilter(django_filters.FilterSet):
    """Base class with filters shared across multiple FilterSets."""
    created_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
    created_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
    ordering = django_filters.OrderingFilter(
        fields=['created_at', 'updated_at', 'name']
    )

    class Meta:
        model = None  # Subclasses must define Meta.model
        fields = []

class ProductFilter(BaseFilter):
    class Meta:
        model = Product
        fields = ['category', 'is_active']

class OrderFilter(BaseFilter):
    class Meta:
        model = Order
        fields = ['status', 'customer']
```

**Failure mode**: Duplicating date range and ordering filters across multiple `FilterSet` classes leads to drift when you add a new field to one but forget another.

### Using `@property` for computed filters

```python
class ProductFilter(django_filters.FilterSet):
    price_min = django_filters.NumberFilter()
    price_max = django_filters.NumberFilter()

    class Meta:
        model = Product
        fields = []

    @property
    def is_price_range_valid(self):
        """Check if both min and max are provided and valid."""
        price_min = self.data.get('price_min')
        price_max = self.data.get('price_max')
        if price_min and price_max:
            return float(price_min) <= float(price_max)
        return True

    @property
    def qs(self):
        qs = super().qs
        # Access computed property to trigger validation
        if not self.is_price_range_valid:
            # Return empty queryset or raise error
            return qs.none()
        return qs
```

**Failure mode**: Trying to validate cross-field constraints in `__init__` fails because filters haven't been instantiated yet. Use `@property` on `qs` to validate after filter application.

### Overriding `get_queryset()` for cross-field logic

```python
class ProductFilter(django_filters.FilterSet):
    min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
    max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte')
    category = django_filters.CharFilter()

    class Meta:
        model = Product
        fields = []

    def get_queryset(self):
        """Access other filter values for cross-field logic."""
        qs = super().get_queryset()
        
        # Example: filter based on relationship between fields
        min_price = self.data.get('min_price')
        max_price = self.data.get('max_price')
        
        if min_price and max_price:
            # Custom logic: ensure price is within 10% of midpoint
            midpoint = (float(min_price) + float(max_price)) / 2
            tolerance = midpoint * 0.1
            qs = qs.filter(price__gte=midpoint - tolerance, price__lte=midpoint + tolerance)
        
        return qs
```

**Failure mode**: `FilterSet` re-runs `get_queryset()` per request — there's no caching of the base queryset. If your `get_queryset()` performs expensive operations, cache the result or move logic to the view.

### `distinct()` for ManyToMany traversals

```python
class ProductFilter(django_filters.FilterSet):
    tags = django_filters.CharFilter(field_name='tags__name', lookup_expr='in')
    categories = django_filters.CharFilter(field_name='category__slug', lookup_expr='in')

    class Meta:
        model = Product
        fields = ['tags', 'categories']
        # Enable distinct to avoid duplicate rows from M2M joins
        # Note: django-filter doesn't auto-add distinct; you must handle it
```

In the view:

```python
def product_list(request):
    filter = ProductFilter(request.GET, queryset=Product.objects.all())
    qs = filter.qs.distinct()  # Required for M2M filters
    return render(request, 'products.html', {'products': qs})
```

**Failure mode**: Without `distinct()`, filtering on multiple M2M relationships produces duplicate rows (Cartesian product of joins).

---

## Decision: django-filter vs plain query params vs manual filtering

### Use django-filter when

- You need **DRF integration** (automatic filter schema, OpenAPI docs)
- Filters are **user-facing and dynamic** (users can combine any filter)
- You have **10+ filter parameters** or complex cross-field logic
- Multiple views share **the same filter set**
- You want **validation** of filter values (type coercion, range checks)

### Use plain `request.GET` when

- You have **2-3 stable parameters** (e.g., `page`, `limit`, `sort`)
- The API is **internal** with controlled consumers
- You need **full control** over query construction
- The threshold: hand-written `request.GET` handling is simpler for ≤3 params

```python
def product_list(request):
    page = int(request.GET.get('page', 1))
    limit = min(int(request.GET.get('limit', 20)), 100)  # Cap at 100
    sort = request.GET.get('sort', 'created_at')
    
    if sort not in ['created_at', 'price', 'name']:
        sort = 'created_at'  # Whitelist validation
    
    qs = Product.objects.all().order_by(sort)[(page-1)*limit:page*limit]
    return render(request, 'products.html', {'products': qs})
```

### When django-filter is overkill

- **Public API with a handful of stable params** — manual handling is clearer
- **Filters that depend on session/auth state** — put logic in `get_queryset()`
- **Complex business logic** — use QuerySet methods instead of filters

---

## Correctness & Performance

### `distinct()` on ManyToMany traversals

When filtering on M2M relationships, JOINs produce duplicate rows:

```python
# Without distinct: products with 3 tags appear 3 times
qs = Product.objects.filter(tags__name__in=['sale', 'new'])

# With distinct: each product appears once
qs = Product.objects.filter(tags__name__in=['sale', 'new']).distinct()
```

**Warning**: `distinct()` without arguments uses all fields. If you need to order by a filtered field, use `distinct('field_name')` (PostgreSQL only) or restructure the query.

### Indexing filtered columns

A filter on an unindexed column is a table scan:

```python
class Product(models.Model):
    name = models.CharField(max_length=255, db_index=True)  # Index on filter field
    price = models.DecimalField(max_digits=10, 2, db_index=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    
    class Meta:
        indexes = [
            models.Index(fields=['-created_at']),  # For ordering
            models.Index(fields=['category', '-created_at']),  # Composite
        ]
```

Use `EXPLAIN` to verify index usage on slow filters.

### Bounding `__in` with user-supplied lists

```python
# Dangerous: unbounded list from user input
ids = request.GET.getlist('ids')
Product.objects.filter(id__in=ids)  # Could be millions of rows

# Safe: cap the list length
MAX_IDS = 100
ids = request.GET.getlist('ids')[:MAX_IDS]
Product.objects.filter(id__in=ids)
```

**Failure mode**: `id__in` with 10,000 values creates a massive SQL IN clause, causing query timeouts.

### `FilterSet` re-runs `get_queryset()` per request

```python
class ProductViewSet(viewsets.ModelViewSet):
    filterset_class = ProductFilter
    
    def get_queryset(self):
        # This runs EVERY request, not just once
        return Product.objects.select_related('category')
```

**Failure mode**: Assuming `get_queryset()` is cached and putting expensive operations there (e.g., calling an external API). Cache explicitly if needed:

```python
from django.core.cache import cache

def get_queryset(self):
    cache_key = 'product_base_qs'
    qs = cache.get(cache_key)
    if qs is None:
        qs = Product.objects.select_related('category')
        cache.set(cache_key, qs, timeout=300)
    return qs
```

---

## Django REST Framework Integration

### Basic ViewSet setup

```python
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from .models import Product
from .filters import ProductFilter

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    filterset_class = ProductFilter
    ordering_fields = ['price', 'created_at']
    ordering = ['-created_at']
```

### Ordering pitfall: `OrderingFilter` vs FilterSet ordering

`OrderingFilter` (from DRF) and `FilterSet` ordering are separate:

```python
class ProductFilter(django_filters.FilterSet):
    # This creates an 'order_by' filter param
    order_by = django_filters.OrderingFilter(
        fields=['price', 'created_at']
    )

    class Meta:
        model = Product
        fields = []

class ProductViewSet(viewsets.ModelViewSet):
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    filterset_class = ProductFilter
    # ordering_fields applies to DRF's OrderingFilter, NOT FilterSet
    ordering_fields = ['price', 'created_at']
```

**Failure mode**: Using both `OrderingFilter` in `filter_backends` AND an `OrderingFilter` field in your `FilterSet` creates conflicting ordering behavior. Pick one:
- Use `FilterSet.ordering` for filter-param-based ordering (user types `?order_by=-price`)
- Use `OrderingFilter` in `filter_backends` for header-based ordering (user sends `Order: -price`)

---

## URL Patterns

```bash
# Basic filtering
GET /api/products/?category=1
GET /api/products/?price_min=10&price_max=100

# Text search
GET /api/products/?name__icontains=laptop

# Multiple values (comma-separated)
GET /api/products/?categories=electronics,books

# Date range
GET /api/products/?created_after=2024-01-01

# Ordering
GET /api/products/?order_by=-price
GET /api/products/?ordering=-price  # DRF OrderingFilter
```

---

## Common Pitfalls

### S silently ignoring unknown query parameters

django-filter ignores parameters not defined in your `FilterSet`:

```python
class ProductFilter(django_filters.FilterSet):
    price_min = django_filters.NumberFilter()
    
    class Meta:
        model = Product
        fields = []

# GET /api/products/?unknown_param=123 → silently ignored
```

**Fix**: Validate with `strict=True` (django-filter 4.0+):

```python
class ProductFilter(django_filters.FilterSet):
    class Meta:
        model = Product
        fields = []
        strict = True  # Raise error on unknown params
```

### Not exposing all model fields accidentally

```python
class Meta:
    model = Product
    fields = '__all__'  # Exposes EVERY field — often unintended
```

**Fix**: Be explicit:

```python
class Meta:
    model = Product
    fields = ['category', 'price', 'name', 'is_active']
```

---

## 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
- **jvns.ca – More nice Django things (composable QuerySet methods)**: https://jvns.ca/blog/2026/07/21/more-nice-django-things/

Attribution

CodeAtCodeCodeAtCode
View sourceSee grades on GitHubMore from CodeAtCode →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Mysql Best Practices

MySQL development best practices for schema design, query optimization, and database administration

2481 votes

Jpa Patterns

Spring Boot中的JPA/Hibernate实体设计、关系、查询优化、事务、审计、索引、分页和连接池模式。

2456590 votes

Clickhouse Io

ClickHouse数据库模式、查询优化、分析和数据工程最佳实践,适用于高性能分析工作负载。

2456590 votes

Postgres Patterns

基于Supabase最佳实践的PostgreSQL数据库模式,用于查询优化、架构设计、索引和安全。

2456590 votes

Sql Pro

Master modern SQL with cloud-native databases, OLTP/OLAP

458250 votes
View all in databases →