Django-filter integration for django-admin-deux
Project description
djadmin-filters
Django-filter integration for django-admin-deux, providing filtering, ordering, and search capabilities.
Version: 1.0.0
License: MIT
Python: 3.11+
Django: 5.2+ (LTS - uses {% querystring %} tag introduced in 5.0)
Dependencies: django-filter >=23.0
Features
- 🔍 Filtering: Column-based filtering using django-filter with sidebar UI
- ↕️ Ordering: Sortable column headers with visual indicators (↑↓)
- Column-centric Configuration: Configure filters and ordering per column
- Boolean Normalization: Simple
filter=True, order=Truesyntax - Legacy Support: Compatible with
list_filterandorder_fields - Query Parameter Preservation: Filters, search, and ordering work seamlessly together
Note: Search functionality is provided by the core djadmin package, not this plugin.
Installation
Option 1: Install with django-admin-deux
pip install django-admin-deux[djadmin-filters]
Option 2: Install separately
pip install djadmin-filters
Configuration
Add both djadmin and djadmin_filters to your Django settings:
# settings.py
INSTALLED_APPS = [
# ... Django apps
'djadmin', # Core package
'djadmin_filters', # This plugin
'django_filters', # Required dependency
# ... your apps
]
Important: django_filters must be in INSTALLED_APPS for widget templates to be found.
Quick Start
# myapp/djadmin.py
from djadmin import ModelAdmin, register, Column
from djadmin.dataclasses import Filter, Order
@register(Product)
class ProductAdmin(ModelAdmin):
# Modern column-centric configuration
list_display = [
Column('name',
filter=Filter(lookup_expr='icontains'),
order=True),
Column('price',
filter=Filter(lookup_expr=['gte', 'lte']), # Range filter
order=True),
Column('category',
filter=True, # Simple exact match
order=False), # Not sortable
Column('stock',
filter=False, # No filter
order=True),
]
That's it! The admin will now display:
- A filter sidebar with filter inputs for name, price, and category
- Sortable column headers for name, price, and stock
- Visual sort indicators (↕️ ↑ ↓)
Filtering
Basic Filtering
Use filter=True for simple exact-match filters:
Column('category', filter=True)
# Generates: <input name="category" type="text">
Lookup Expressions
Use Filter(lookup_expr=...) for different filter types:
# Contains filter (case-insensitive)
Column('name', filter=Filter(lookup_expr='icontains'))
# Exact match
Column('status', filter=Filter(lookup_expr='exact'))
# Greater than / Less than
Column('price', filter=Filter(lookup_expr='gte'))
Column('price', filter=Filter(lookup_expr='lte'))
# Range filter (min/max)
Column('price', filter=Filter(lookup_expr=['gte', 'lte']))
# Generates: <input name="price_min"> <input name="price_max">
# Date filters
Column('created_at', filter=Filter(lookup_expr=['gte', 'lte']))
# Generates: <input type="date" name="created_at_after"> <input type="date" name="created_at_before">
Common lookup expressions:
exact- Exact matchiexact- Case-insensitive exact matchcontains/icontains- Substring matchstartswith/istartswith- Starts withendswith/iendswith- Ends withgte/lte- Greater/less than or equalgt/lt- Greater/less thanin- In listisnull- Is null
Custom Widgets
Provide custom Django form widgets:
from django import forms
from djadmin.dataclasses import Filter
Column('status',
filter=Filter(
lookup_expr='exact',
widget=forms.Select(choices=[
('active', 'Active'),
('inactive', 'Inactive'),
])
))
Method Filters
Use custom filter methods for complex logic:
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))
Filter Labels
Override the filter label:
Column('price',
label='Price', # Column header label
filter=Filter(
lookup_expr=['gte', 'lte'],
label='Price Range' # Filter label in sidebar
))
Ordering
Basic Ordering
Use order=True to make a column sortable:
Column('name', order=True)
# Clicking header cycles: neutral → ↑ → ↓ → neutral
Disable Ordering
Explicitly disable ordering:
Column('description', order=False)
# Header has no sort icon
Custom Labels
Provide custom labels for ascending/descending states:
from djadmin.dataclasses import Order
Column('price',
order=Order(
label='Price (low to high)',
descending_label='Price (high to low)'
))
Custom Fields
Order by different field(s):
Column('full_name',
order=Order(fields=['last_name', 'first_name']))
# Clicking "Full Name" orders by last name, then first name
Legacy Support
The plugin supports Django admin's list_filter and a new order_fields attribute for backwards compatibility:
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'price', 'category']
# Old-style filtering (still works!)
list_filter = ['category', 'status']
# Old-style ordering (new attribute, similar to search_fields)
order_fields = ['name', 'price']
The metaclass normalizes these to Column-based configuration.
Migration from Column.sortable
Breaking change: Column.sortable was removed in Milestone 2.
Before (Milestone 1):
Column('name', sortable=True)
Column('description', sortable=False)
After (Milestone 2):
Column('name', order=True)
Column('description', order=False)
Why: Consistent naming with list_display, list_filter → Column.filter, order_fields → Column.order.
URL Parameters
The plugin uses standard URL query parameters:
- Filtering:
?field=valueor?field__lookup=value - Ordering:
?ordering=fieldor?ordering=-field(descending)
Examples:
# Filter by category
/djadmin/webshop/product/?category=1
# Filter by price range
/djadmin/webshop/product/?price__gte=100&price__lte=500
# Sort by price ascending
/djadmin/webshop/product/?ordering=price
# Sort by price descending
/djadmin/webshop/product/?ordering=-price
# Combined: filter + order + search
/djadmin/webshop/product/?category=1&ordering=-price&search=laptop
Query Parameter Preservation
All features preserve other query parameters:
- Searching preserves filters and ordering
- Filtering preserves search and ordering
- Ordering preserves search and filters
- Pagination preserves all parameters
This is handled automatically by the {% query_params_as_hidden_inputs %} template tag.
Complete Example
# myapp/djadmin.py
from djadmin import ModelAdmin, register, Column
from djadmin.dataclasses import Filter, Order
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = [
# Text search
Column('name',
label='Product Name',
filter=Filter(lookup_expr='icontains'),
order=True),
# Exact match with custom widget
Column('category',
filter=Filter(
lookup_expr='exact',
widget=forms.Select(choices=Category.choices)
),
order=False),
# Range filter
Column('price',
filter=Filter(lookup_expr=['gte', 'lte']),
order=Order(
label='Price (low to high)',
descending_label='Price (high to low)'
)),
# Simple filter, sortable
Column('stock',
filter=True,
order=True),
# Not filterable, not sortable
Column('description',
filter=False,
order=False),
]
# Optional: Custom filterset class
# filterset_class = MyCustomFilterSet
Advanced Usage
Custom FilterSet
Provide a custom django-filter FilterSet:
import django_filters
class ProductFilterSet(django_filters.FilterSet):
# Custom filters
in_stock = django_filters.BooleanFilter(
method='filter_in_stock',
label='In Stock'
)
def filter_in_stock(self, queryset, name, value):
if value:
return queryset.filter(stock__gt=0)
return queryset
class Meta:
model = Product
fields = []
@register(Product)
class ProductAdmin(ModelAdmin):
filterset_class = ProductFilterSet
list_display = [
Column('name', filter=True, order=True),
# Column-based filters extend the custom filterset
]
Filtering Related Fields
Filter across relationships using Django's __ syntax:
Column('category__name',
label='Category',
filter=Filter(lookup_expr='icontains'),
order=True)
Conditional Filtering
Only show filters when conditions are met:
@register(Product)
class ProductAdmin(ModelAdmin):
def get_list_display(self, request):
columns = [Column('name', filter=True, order=True)]
# Only show category filter for superusers
if request.user.is_superuser:
columns.append(
Column('category', filter=True, order=False)
)
return columns
UI Customization
Override Filter Widget Template
{# myapp/templates/djadmin/djadmin_filters/filter_widget.html #}
<h3>Custom Filters</h3>
<form method="get" action="">
{% load djadmin_tags %}
{% query_params_as_hidden_inputs 'page' filterset.form.fields %}
{# Custom rendering #}
{% for field in filterset.form %}
<div class="custom-field">
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
<button type="submit">Filter</button>
</form>
Custom Icon Templates
Override sort icons:
{# myapp/templates/djadmin/icons/sort.html #}
<svg><!-- Your custom neutral icon --></svg>
{# myapp/templates/djadmin/icons/sort-up.html #}
<svg><!-- Your custom ascending icon --></svg>
{# myapp/templates/djadmin/icons/sort-down.html #}
<svg><!-- Your custom descending icon --></svg>
Performance
PostgreSQL
For best performance with PostgreSQL, add indexes:
from django.db import models
from django.contrib.postgres.indexes import GinIndex
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
price = models.DecimalField(max_digits=10, decimal_places=2, db_index=True)
class Meta:
indexes = [
models.Index(fields=['category', 'price']),
GinIndex(fields=['name']), # For text searches
]
Optimize Queries
Use select_related and prefetch_related:
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = [
Column('name', filter=True, order=True),
Column('category__name', filter=True, order=True),
]
def get_queryset(self, request):
return super().get_queryset(request).select_related('category')
Troubleshooting
Filters not showing
- Check
django_filtersis inINSTALLED_APPS - Check filter configuration:
Column('field', filter=True) - Check browser console for JavaScript errors
Ordering not working
- Check column has
order=True - Check field exists on model
- Check
orderingURL parameter is present
Widget templates not found
Add django_filters to INSTALLED_APPS:
INSTALLED_APPS = [
'djadmin',
'djadmin_filters',
'django_filters', # Required for widget templates
]
Development
Running Tests
cd djadmin-filters
pytest
pytest --cov=djadmin_filters
Running with Example App
# From repo root
cd tests
python manage.py runserver
# Visit: http://localhost:8000/djadmin/webshop/product/
Documentation
- Usage Guide - Detailed usage examples
- API Reference - Complete API documentation
- Migration Guide - Upgrading from Milestone 1
- Hook Reference - Plugin hooks
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass
- Submit a pull request
License
MIT License - see LICENSE file for details.
Credits
- Built on django-filter
- Part of django-admin-deux
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file djadmin_filters-0.1.1.tar.gz.
File metadata
- Download URL: djadmin_filters-0.1.1.tar.gz
- Upload date:
- Size: 26.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ff82b783432eeb0f82fe6bb454ac0f31fc06c414df466f5082e1d47f7ed5c72
|
|
| MD5 |
ab0b6ff80514aa3d185ef65ad95fe614
|
|
| BLAKE2b-256 |
5a14ac3f0e035d5525cad78b51a5e3051edf226c61a11fd61fcfa7c572d2633a
|
File details
Details for the file djadmin_filters-0.1.1-py3-none-any.whl.
File metadata
- Download URL: djadmin_filters-0.1.1-py3-none-any.whl
- Upload date:
- Size: 16.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67fc797eacaf111166313ef4f89115d0a7015e70f946a274c1df11af9ae21eeb
|
|
| MD5 |
c24a96d6415fc7ad9cb0038f741712b1
|
|
| BLAKE2b-256 |
670a031c98e29c3f2eeee6058ad648f02f9adb76db404975068b049bb5c8c432
|