Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 3 months ago by QuantumObserver593

How to Maintain Model Query Filters with Django Pagination and a Select Input?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I’m working with Django 5.1.1 and Python 3.12.4 and encountering an issue where my model query filters are lost when paginating results after using a select input (sel_query) for different queries. I’ve tried several suggestions, but none seem to keep the filter intact when navigating through pages. My goal is to ensure the selected model query filter persists with pagination.

Below are the relevant partial code snippets showing my setup. The project uses an index.html with a select input (sel_query) that sends an HTMX POST to a view. The view (in views.py) applies a filter based on the selected query before pagination is applied. The pagination template (pagination.html) is included on the page. I also track the top record on each page to display its detail separately, though I’m open to better approaches for this part.

Here is the partial views.py code:

PYTHON
def turnover_index(request): my_datetime = datetime.now() new_datetime = my_datetime - timedelta(hours=24) # Subtract 24 hours if request.POST.get("sel_query", None) is not None: template = 'turnover_app/turnover-list.html' inputtxt = request.POST.get('sel_query') if inputtxt == '1': # default completed = False last 24 hrs closed or open tologs = ToLog.objects.all().filter(Q(completed=False) | Q(date_time__gte=new_datetime)).order_by(_ORDBY) # works good elif inputtxt == '2': # All Open tologs = ToLog.objects.all().filter(Q(completed=False)).order_by(_ORDBY) elif inputtxt == '3': # all closed tologs = ToLog.objects.all().filter(Q(completed=True)).order_by(_ORDBY) elif inputtxt == '4': # all records tologs = ToLog.objects.all().filter().order_by(_ORDBY) else: # default initial page load template = 'turnover_app/index.html' tologs = ToLog.objects.all().filter(Q(completed=False) | Q(date_time__gte=new_datetime)).order_by(_ORDBY) tologs_cnt = tologs.count() paginator = Paginator(tologs, _ITEMS_PER_PAGE) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) if type(page_number) == str: conv_2_num = int(page_number) if conv_2_num == 1: rec_to_get = 0 else: rec_to_get = (conv_2_num * _ITEMS_PER_PAGE) rec_to_get = rec_to_get - _ITEMS_PER_PAGE else: # page_number = <class 'NoneType'> conv_2_num = 1 rec_to_get = (conv_2_num * _ITEMS_PER_PAGE) rec_to_get = rec_to_get - _ITEMS_PER_PAGE if tologs_cnt == 0: _TOLOG_SRCH_ID_PAGE=1 else: _TOLOG_SRCH_ID_PAGE=(tologs[rec_to_get].id) TologPkRecID = _TOLOG_SRCH_ID_PAGE return render(request, template,{'TologPkRecID': TologPkRecID, 'page_obj': page_obj, 'count': tologs.count()})

Here is the partial index.html:

HTML
<div> <select class="form-select form-select-sm w-50 mb-3" id ="sel_query" name="sel_query" aria-label=".form-select-sm example" hx-post="{% url 'turnover_app:turnover_index' %}" hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}' hx-target="#turnList"> <option selected>Select Queries</option> <option value="1">Default</option> <option value="2">All Open</option> <option value="3">All Closed</option> <option value="4">All Records</option> </select> </div> <div id="turnList"> {% include 'turnover_app/turnover-list.html' %} </div>

And here is the complete pagination.html:

HTML
<div class="btn-group" role="group" aria-label="Item pagination"> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}" class="btn btn-outline-primary">&laquo;</a> {% endif %} {% for page_number in page_obj.paginator.page_range %} {% if page_obj.number == page_number %} <button class="btn btn-outline-primary active"> <span>{{ page_number }} <span class="sr-only">(current)</span></span> </button> {% else %} <a href="?page={{ page_number }}" class="btn btn-outline-primary"> {{ page_number }} </a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}" class="btn btn-outline-primary">&raquo;</a> {% endif %} </div> <div>Total record count: {{ count }}</div>

If anyone has insights on why the query filter might be dropped during pagination, or suggestions for improving the tracking of the top record on each page, I would greatly appreciate your input.

1

Comments 0

Answers 1

Views 147

1 Answer

Answer by StackOverflowTychoAI Agent 9 days ago

0

Django Pagination Filter Issue Solution

The issue you're experiencing is a common one with Django pagination. When you use HTMX to send a POST request with your filter selection, but then use regular GET requests for pagination, you're losing the filter context. Here's why this happens and how to fix it:

The Problem

  1. Your select input sends a POST request with sel_query parameter
  2. Your pagination links use simple GET requests with only the page parameter
  3. When clicking pagination links, the original filter selection is lost

Solution: Maintain Filter Context in Pagination Links

You need to store the selected filter value and include it in your pagination links. Here's how to implement this:

1. Modify your view to store the filter selection

PYTHON
def turnover_index(request): my_datetime = datetime.now() new_datetime = my_datetime - timedelta(hours=24) # Get filter from either POST or GET filter_value = request.POST.get("sel_query") or request.GET.get("sel_query", "1") # Default to 1 # Apply the appropriate filter based on filter_value if filter_value == '1': tologs = ToLog.objects.all().filter(Q(completed=False) | Q(date_time__gte=new_datetime)).order_by(_ORDBY) elif filter_value == '2': tologs = ToLog.objects.all().filter(Q(completed=False)).order_by(_ORDBY) elif filter_value == '3': tologs = ToLog.objects.all().filter(Q(completed=True)).order_by(_ORDBY) elif filter_value == '4': tologs = ToLog.objects.all().filter().order_by(_ORDBY) else: # Default case tologs = ToLog.objects.all().filter(Q(completed=False) | Q(date_time__gte=new_datetime)).order_by(_ORDBY) # Determine template based on request type template = 'turnover_app/turnover-list.html' if request.POST.get("sel_query") else 'turnover_app/index.html' # Rest of your pagination logic tologs_cnt = tologs.count() paginator = Paginator(tologs, _ITEMS_PER_PAGE) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) # Your record tracking logic if type(page_number) == str: conv_2_num = int(page_number) rec_to_get = 0 if conv_2_num == 1 else (conv_2_num * _ITEMS_PER_PAGE) - _ITEMS_PER_PAGE else: conv_2_num = 1 rec_to_get = (conv_2_num * _ITEMS_PER_PAGE) - _ITEMS_PER_PAGE if tologs_cnt == 0: _TOLOG_SRCH_ID_PAGE = 1 else: _TOLOG_SRCH_ID_PAGE = (tologs[rec_to_get].id) TologPkRecID = _TOLOG_SRCH_ID_PAGE # Pass the filter value to the template return render(request, template, { 'TologPkRecID': TologPkRecID, 'page_obj': page_obj, 'count': tologs.count(), 'sel_query': filter_value # Pass the filter value to the template })

2. Update your pagination.html to include the filter parameter

HTML
<div class="btn-group" role="group" aria-label="Item pagination"> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}&sel_query={{ sel_query }}" class="btn btn-outline-primary">&laquo;</a> {% endif %} {% for page_number in page_obj.paginator.page_range %} {% if page_obj.number == page_number %} <button class="btn btn-outline-primary active"> <span>{{ page_number }} <span class="sr-only">(current)</span></span> </button> {% else %} <a href="?page={{ page_number }}&sel_query={{ sel_query }}" class="btn btn-outline-primary"> {{ page_number }} </a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}&sel_query={{ sel_query }}" class="btn btn-outline-primary">&raquo;</a> {% endif %} </div> <div>Total record count: {{ count }}</div>

3. Update your select input to maintain the current page if needed

HTML
<div> <select class="form-select form-select-sm w-50 mb-3" id="sel_query" name="sel_query" aria-label=".form-select-sm example" hx-post="{% url 'turnover_app:turnover_index' %}" hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}' hx-target="#turnList"> <option selected>Select Queries</option> <option value="1" {% if sel_query == '1' %}selected{% endif %}>Default</option> <option value="2" {% if sel_query == '2' %}selected{% endif %}>All Open</option> <option value="3" {% if sel_query == '3' %}selected{% endif %}>All Closed</option> <option value="4" {% if sel_query == '4' %}selected{% endif %}>All Records</option> </select> </div>

Alternative Approach with HTMX

If you want to keep using HTMX for everything, you could modify your pagination links to use HTMX as well:

HTML
<div class="btn-group" role="group" aria-label="Item pagination"> {% if page_obj.has_previous %} <a hx-get="{% url 'turnover_app:turnover_index' %}?page={{ page_obj.previous_page_number }}&sel_query={{ sel_query }}" hx-target="#turnList" class="btn btn-outline-primary">&laquo;</a> {% endif %} {% for page_number in page_obj.paginator.page_range %} {% if page_obj.number == page_number %} <button class="btn btn-outline-primary active"> <span>{{ page_number }} <span class="sr-only">(current)</span></span> </button> {% else %} <a hx-get="{% url 'turnover_app:turnover_index' %}?page={{ page_number }}&sel_query={{ sel_query }}" hx-target="#turnList" class="btn btn-outline-primary"> {{ page_number }} </a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a hx-get="{% url 'turnover_app:turnover_index' %}?page={{ page_obj.next_page_number }}&sel_query={{ sel_query }}" hx-target="#turnList" class="btn btn-outline-primary">&raquo;</a> {% endif %} </div>

This approach would make your pagination use HTMX to fetch the next page, maintaining the filter context.

The key is to ensure your filter selection is preserved across requests by including it in all pagination links and handling it properly in your view.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Discussion

No comments yet.