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

  1. 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
  1. 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)
  1. 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.

enter image description here

# The button points to 
http://localhost:8000/admin/isa_api/assay/?e=1

Is there a simpler way archiving the desired outcome?

Вернуться на верх