Skip to content

Filtering Guide - djadmin-filters

Plugin: djadmin-filters Version: 1.0.0 Last Updated: October 31, 2025

Overview

The djadmin-filters plugin provides column-based filtering using django-filter with a sidebar UI. Filters are configured directly on Column objects and support various lookup expressions, custom widgets, and method filters.

Prerequisites

  • djadmin-filters plugin installed and configured
  • django_filters in INSTALLED_APPS
  • See Configuration Guide for setup

Basic Filtering

Enable Filtering

Use filter=True for simple exact-match filters:

from djadmin import Column

Column('category', filter=True)
# Generates: <input name="category" type="text">

Disable Filtering

Explicitly disable filtering for a column:

Column('description', filter=False)
# No filter input generated

Simple Example

from djadmin import ModelAdmin, register, Column

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name', filter=True),      # Exact match filter
        Column('category', filter=True),  # Exact match filter
        Column('stock', filter=True),     # Exact match filter
    ]

Lookup Expressions

Use the Filter dataclass to specify lookup expressions:

from djadmin import Column
from djadmin.dataclasses import Filter

Column('name', filter=Filter(lookup_expr='icontains'))

Common Lookup Expressions

Text Searches

# Contains (case-sensitive)
Column('name', filter=Filter(lookup_expr='contains'))

# Contains (case-insensitive) - Most common for text search
Column('name', filter=Filter(lookup_expr='icontains'))

# Exact match (case-sensitive)
Column('sku', filter=Filter(lookup_expr='exact'))

# Exact match (case-insensitive)
Column('code', filter=Filter(lookup_expr='iexact'))

# Starts with
Column('name', filter=Filter(lookup_expr='startswith'))
Column('name', filter=Filter(lookup_expr='istartswith'))  # Case-insensitive

# Ends with
Column('email', filter=Filter(lookup_expr='endswith'))
Column('email', filter=Filter(lookup_expr='iendswith'))  # Case-insensitive

Numeric Comparisons

# Greater than or equal
Column('price', filter=Filter(lookup_expr='gte'))

# Greater than
Column('price', filter=Filter(lookup_expr='gt'))

# Less than or equal
Column('price', filter=Filter(lookup_expr='lte'))

# Less than
Column('price', filter=Filter(lookup_expr='lt'))

Range Filters

Use a list of lookup expressions for range filters:

# Price range (min/max)
Column('price', filter=Filter(lookup_expr=['gte', 'lte']))
# Generates:
#   <input name="price__gte" placeholder="Min">
#   <input name="price__lte" placeholder="Max">

# Date range
Column('created_at', filter=Filter(lookup_expr=['gte', 'lte']))
# Generates:
#   <input type="date" name="created_at__after">
#   <input type="date" name="created_at__before">

Other Lookup Expressions

# In list
Column('status', filter=Filter(lookup_expr='in'))

# Is null / Is not null
Column('deleted_at', filter=Filter(lookup_expr='isnull'))

Complete Lookup Reference

All Django field lookup expressions are supported:

Expression Description Example
exact Exact match (case-sensitive) ?sku=ABC123
iexact Exact match (case-insensitive) ?code=abc
contains Contains substring ?name__contains=laptop
icontains Contains substring (case-insensitive) ?name__icontains=laptop
startswith Starts with ?name__startswith=Pro
istartswith Starts with (case-insensitive) ?name__istartswith=pro
endswith Ends with ?email__endswith=@example.com
iendswith Ends with (case-insensitive) ?email__iendswith=@example.com
gt Greater than ?price__gt=100
gte Greater than or equal ?price__gte=100
lt Less than ?price__lt=500
lte Less than or equal ?price__lte=500
in In list ?status__in=active,pending
isnull Is null ?deleted_at__isnull=True
range Range ?price__range=100,500
date Date match ?created_at__date=2025-10-31
year Year match ?created_at__year=2025
month Month match ?created_at__month=10
day Day match ?created_at__day=31

Custom Widgets

Provide custom Django form widgets for filter inputs:

Select Widget

from django import forms
from djadmin.dataclasses import Filter

Column('status',
       filter=Filter(
           lookup_expr='exact',
           widget=forms.Select(choices=[
               ('', '-- All --'),
               ('active', 'Active'),
               ('inactive', 'Inactive'),
               ('archived', 'Archived'),
           ])
       ))

Checkbox Widget

Column('is_featured',
       filter=Filter(
           lookup_expr='exact',
           widget=forms.CheckboxInput()
       ))

Date Widget

Column('published_date',
       filter=Filter(
           lookup_expr='gte',
           widget=forms.DateInput(attrs={'type': 'date'})
       ))

Number Widget

Column('stock',
       filter=Filter(
           lookup_expr='lte',
           widget=forms.NumberInput(attrs={
               'min': 0,
               'placeholder': 'Max stock'
           })
       ))

Multiple Select Widget

Column('tags',
       filter=Filter(
           lookup_expr='in',
           widget=forms.SelectMultiple(choices=Tag.choices)
       ))

Method Filters

Use custom filter methods for complex logic:

Basic Method Filter

from djadmin.dataclasses import Filter

def filter_is_featured(queryset, name, value):
    """Custom filter method."""
    if value:
        return queryset.filter(featured=True, published=True)
    return queryset

Column('featured',
       filter=Filter(method=filter_is_featured))

Method Filter with Widget

def filter_price_range(queryset, name, value):
    """Filter by price range category."""
    ranges = {
        'budget': (0, 50),
        'mid': (50, 200),
        'premium': (200, 1000),
        'luxury': (1000, float('inf')),
    }

    if value in ranges:
        min_price, max_price = ranges[value]
        return queryset.filter(price__gte=min_price, price__lt=max_price)

    return queryset

Column('price_range',
       label='Price Range',
       filter=Filter(
           method=filter_price_range,
           widget=forms.Select(choices=[
               ('', '-- All --'),
               ('budget', 'Budget (< $50)'),
               ('mid', 'Mid-range ($50-$200)'),
               ('premium', 'Premium ($200-$1000)'),
               ('luxury', 'Luxury ($1000+)'),
           ])
       ))

Method Filter Signature

Method filters must accept three arguments:

def my_filter_method(queryset, name, value):
    """
    Args:
        queryset: Current queryset
        name: Field name (usually ignored)
        value: User-provided filter value

    Returns:
        Filtered queryset
    """
    # Your filtering logic here
    return queryset.filter(...)

Filter Labels

Override the filter label displayed in the sidebar:

Column Label vs Filter Label

Column('price',
       label='Price',          # Column header label
       filter=Filter(
           lookup_expr=['gte', 'lte'],
           label='Price Range'  # Filter label in sidebar
       ))

Range Filter Labels

Column('price',
       filter=Filter(
           lookup_expr=['gte', 'lte'],
           label='Price Range',
           min_label='Minimum Price',
           max_label='Maximum Price'
       ))
# Generates inputs with custom placeholders

Filter Examples

Text Search Filter

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name',
               filter=Filter(
                   lookup_expr='icontains',
                   label='Search Products'
               )),
    ]

Category Filter with Dropdown

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('category',
               filter=Filter(
                   lookup_expr='exact',
                   widget=forms.Select(choices=Category.choices)
               )),
    ]

Price Range Filter

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('price',
               filter=Filter(
                   lookup_expr=['gte', 'lte'],
                   label='Price Range'
               )),
    ]

Date Range Filter

@register(Order)
class OrderAdmin(ModelAdmin):
    list_display = [
        Column('created_at',
               label='Order Date',
               filter=Filter(
                   lookup_expr=['gte', 'lte'],
                   label='Date Range'
               )),
    ]

Boolean Filter

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('is_active',
               label='Active',
               filter=Filter(
                   lookup_expr='exact',
                   widget=forms.Select(choices=[
                       ('', '-- All --'),
                       ('True', 'Active'),
                       ('False', 'Inactive'),
                   ])
               )),
    ]

Multi-Select Filter

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('tags',
               filter=Filter(
                   lookup_expr='in',
                   widget=forms.SelectMultiple(
                       choices=[(t.id, t.name) for t in Tag.objects.all()]
                   )
               )),
    ]

Complete Example

from django import forms
from djadmin import ModelAdmin, register, Column
from djadmin.dataclasses import Filter
from myapp.models import Product, Category

def filter_in_stock(queryset, name, value):
    """Filter products that are in stock."""
    if value:
        return queryset.filter(stock__gt=0)
    return queryset

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        # Text search
        Column('name',
               label='Product Name',
               filter=Filter(
                   lookup_expr='icontains',
                   label='Search by name'
               )),

        # Dropdown selection
        Column('category',
               filter=Filter(
                   lookup_expr='exact',
                   widget=forms.Select(
                       choices=[('', '-- All --')] + Category.choices
                   )
               )),

        # Range filter
        Column('price',
               filter=Filter(
                   lookup_expr=['gte', 'lte'],
                   label='Price Range'
               )),

        # Method filter
        Column('in_stock',
               label='In Stock',
               filter=Filter(
                   method=filter_in_stock,
                   widget=forms.CheckboxInput()
               )),

        # Date range
        Column('created_at',
               label='Created',
               filter=Filter(
                   lookup_expr=['gte', 'lte'],
                   label='Created Date'
               )),
    ]

Best Practices

Use Case-Insensitive Lookups for Text

# Good - works for all user input variations
Column('name', filter=Filter(lookup_expr='icontains'))

# Avoid - only matches exact case
Column('name', filter=Filter(lookup_expr='contains'))

Provide "All" Option in Dropdowns

# Good - allows clearing the filter
widget=forms.Select(choices=[
    ('', '-- All --'),
    ('active', 'Active'),
    ('inactive', 'Inactive'),
])

# Avoid - user can't clear filter
widget=forms.Select(choices=[
    ('active', 'Active'),
    ('inactive', 'Inactive'),
])

Use Range Filters for Numeric/Date Fields

# Good - allows min/max filtering
Column('price', filter=Filter(lookup_expr=['gte', 'lte']))

# Avoid - only exact match
Column('price', filter=Filter(lookup_expr='exact'))

Label Filters Clearly

# Good - clear what the filter does
Column('price',
       label='Price',
       filter=Filter(
           lookup_expr=['gte', 'lte'],
           label='Price Range'
       ))

# Avoid - ambiguous label
Column('price', filter=Filter(lookup_expr=['gte', 'lte']))

Troubleshooting

Filter Not Showing

Problem: Filter configured but not appearing in sidebar

Solutions: 1. Check django_filters is in INSTALLED_APPS 2. Verify filter=True or filter=Filter(...) is set 3. Check browser console for JavaScript errors 4. Clear browser cache

Filter Not Working

Problem: Filter appears but doesn't filter results

Solutions: 1. Check field name matches model field 2. Verify lookup expression is valid for field type 3. Check URL parameters in address bar 4. Inspect queryset in Django Debug Toolbar

Wrong Widget Type

Problem: Text input instead of dropdown

Solutions: 1. Explicitly set widget parameter:

filter=Filter(
    lookup_expr='exact',
    widget=forms.Select(choices=[...])
)

See Also