How to implementing pagination for Django 5 admin sidebar filters?
The Problem
Django's admin sidebar filters show all options without pagination, which becomes unwieldy when you have hundreds of related objects:
By Study
• All
• Study-001: Genetic markers in mice
• Study-002: Effects of caffeine
...
• Study-999: Soil microbiome analysis
With many options, this makes the UI unusable and harms performance.
Our Approach
We implemented custom filters with pagination to display only a subset of options at a time:
By Study
• All
• Study-001: Genetic markers in mice
• Study-002: Effects of caffeine
...
• Study-010: Plant growth factors
Page 1 of 100 | Next >
Implementation
- Custom filter class:
class StudyListFilter(SimpleListFilter):
title = _('By Study')
parameter_name = 'study__id__exact'
template = 'admin/filter_pagination.html' # Custom template
def lookups(self, request, model_admin):
# Return minimal data - real items are provided in the template
return [('', 'All')]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(study__id=self.value())
return queryset
- Modified admin class:
@admin.register(Assay)
class AssayAdmin(admin.ModelAdmin):
list_filter = (StudyListFilter, 'measurement_type')
change_list_template = 'admin/study_change_list.html'
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
# Get paginated studies
studies = Study.objects.all().order_by('id')
paginator = Paginator(studies, 10)
page_number = request.GET.get('filter_page', 1)
page_obj = paginator.get_page(page_number)
# Pass pagination data to template
extra_context.update({
'filter_page_obj': page_obj,
'filter_items': [(s.id, f"{s.accession_code} - {s.title[:30]}") for s in page_obj],
'filter_name': 'study__id__exact',
'filter_title': 'By Study',
})
return super().changelist_view(request, extra_context)
- Custom template for filter:
<h3>{{ title }}</h3>
<ul>
<li {% if not spec.value %}class="selected"{% endif %}>
<a href="?">All</a>
</li>
{% for id, name in filter_items %}
<li {% if spec.value == id|stringformat:'s' %}class="selected"{% endif %}>
<a href="?{{ filter_name }}={{ id }}">{{ name }}</a>
</li>
{% endfor %}
{% if filter_page_obj.has_other_pages %}
<li class="pagination">
{% if filter_page_obj.has_previous %}
<a href="?filter_page={{ filter_page_obj.previous_page_number }}">Previous</a>
{% endif %}
Page {{ filter_page_obj.number }} of {{ filter_page_obj.paginator.num_pages }}
{% if filter_page_obj.has_next %}
<a href="?filter_page={{ filter_page_obj.next_page_number }}">Next</a>
{% endif %}
</li>
{% endif %}
</ul>
Result
The buttons for pagination appear, but are disfunctional.
# The button points to
http://localhost:8000/admin/isa_api/assay/?e=1
Is there a simpler way archiving the desired outcome?