djangodjango-viewsdjango-templatespaginator

Use Q object and Paginator together in Django


I've created View which filters data by search query given in textbox. As well as I used Paginator to show data divided into pages.

My problem is, when I filter data with Q object then and try to paginate by clicking the next button, all data is refreshed.

When I search text by Q object the URL becomes http://127.0.0.1:8000/mael/parties/?q=keyword

And from clicking the next button the URL becomes http://127.0.0.1:8000/mael/parties/?page=2

When I manually change URL http://127.0.0.1:8000/mael/parties/?q=keyword&page=2, then it works. But I don't know how to do this in code.

Is it possible to use Q object search and pagination together?

My View

from mael.models import PartyTotalBillsView
from django.views.generic import ListView
from django.db.models import Q
from django.http import HttpResponseRedirect

class PartyListView(ListView):
    paginate_by = 2
    model = PartyTotalBillsView

def parties(request):
    # Show all records or searched query record    
    search_text = request.GET.get('q','')   

    try:
        if search_text:
            queryset = (Q(party_name__icontains=search_text))
            party_list = PartyTotalBillsView.objects.filter(queryset).order_by('party_name')
        else:
            # Show all data if empty keyword is entered
            party_list = PartyTotalBillsView.objects.order_by('party_name')
    except PartyTotalBillsView.DoesNotExist:
        party_list = None
    
    paginator = Paginator(party_list, 2) # Show 2 rows per page: for Test
    page_number = request.GET.get('page')
    
    party_list = paginator.get_page(page_number)
    
    return render(request, 'mael/parties.html', {'party_list': party_list})

Template file

<form id="search-form" method="get" action="/mael/parties/">
    <input id="search-text" type="text" name="q" placeholder="Enter search keyword">
    <input class="btn-search-party" type="submit" value="Search" />
</form>        
<br/>


<table class="show-data">
    <thead>
    <tr>
        <th>ID</th>
        <th>Party Name</th>
        <th>Total Bill Amount</th>
        <th>Phone</th>
        <th>Address</th>
        <th></th>
    </tr>
    </thead>

    {% if party_list %}
    <tbody>
    {% for party in party_list %}
        <tr>
        <td class="party-id">{{ party.party_id }}</td>
        <td class="party-name">{{ party.party_name }}</td>
        <td>{{ party.total_bills }}</td>
        <td class="party-phone">{{ party.party_phone }}</td>
        <td class="party-address">{{ party.party_address }}</td>
        <td>
            <button class="btn-modify" data-partyid="{{party.party_id}}" type="buttton">
            Modify
            </button>
        </td>
        </tr>
    {% endfor %}
    </tbody>
    {% endif %}
</table>


<div class="pagination">
    <span class="step-links">
    {% if party_list.has_previous %}
        <a href="?page=1">&laquo; first</a>
        <a href="?page={{ party_list.previous_page_number }}">previous</a>
    {% endif %}

    <span class="current">
        Page {{ party_list.number }} of {{ party_list.paginator.num_pages }}
    </span>

    {% if party_list.has_next %}
        <a href="?page={{ party_list.next_page_number }}">next</a>
        <a href="?page={{ party_list.paginator.num_pages }}">last &raquo;</a>
    {% endif %}
    </span>
</div>

Solution

  • Please do not use two views. A ListView can perform filtering as well:

    class PartyListView(ListView):
        paginate_by = 2
        model = PartyTotalBillsView
        template_name = 'mael/parties.html'
        context_object_name = 'party_list'
    
        def querystring(self):
            qs = self.request.GET.copy()
            qs.pop(self.page_kwarg, None)
            return qs.urlencode()
    
        def get_queryset(self):
            qs = super().get_queryset()
            if 'q' in self.request.GET:
                qs = qs.filter(party_name__icontains=self.request.GET['q'])
            return qs.order_by('party_name')

    In the links for the previous and next pages, you then append the querystring of the view:

        <span class="step-links">
        {% if party_list.has_previous %}
            <a href="?page=1&amp;{{ view.querystring }}">&laquo; first</a>
            <a href="?page={{ page_obj.previous_page_number }}&amp;{{ view.querystring }}">previous</a>
        {% endif %}
    
        <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
        </span>
    
        {% if party_list.has_next %}
            <a href="?page={{ page_obj.next_page_number }}&amp;{{ view.querystring }}">next</a>
            <a href="?page={{ page_obj.paginator.num_pages }}&amp;{{ view.querystring }}">last &raquo;</a>
        {% endif %}
        </span>