django-filter use paginations

Question:

I’m using the django-filter package to provide a search functionality on my List View.

Now I want to add a pagination to that view as well.

I’m trying to combine pagination to a filtered queryset, but I have no clue on how to go on.

So far, I have tried the following on views.py:

def search(request):
    qs = local_url.objects.filter(global_url__id=1).all()
    paginator = Paginator(qs, 25)
    page = request.GET.get('page')
    try:
        pub = paginator.page(page)
    except PageNotAnInteger:
        pub = paginator.page(1)
    except EmptyPage:
       pub = paginator.page(paginator.num_pages)
    url_filter = PublicationFilter(request.GET, queryset=qs)
    return render(request, 'ingester/search_list.html', {'filter': url_filter, 'publication':pub})
Asked By: Anh Tuan Nguyen

||

Answers:

As I understood you goal is to paginate your filtered query set. If so, you can pass “qs” property of PublicationFilter object to Paginator constructor:

def search(request):
    qs = local_url.objects.filter(global_url__id=1).all()
    url_filter = PublicationFilter(request.GET, queryset=qs)
    paginator = Paginator(url_filter.qs, 25)
    page = request.GET.get('page')
    try:
        pub = paginator.page(page)
    except PageNotAnInteger:
        pub = paginator.page(1)
    except EmptyPage:
        pub = paginator.page(paginator.num_pages)
    url_filter = PublicationFilter(request.GET, queryset=qs)
    return render(request, 'ingester/search_list.html', {'publication':pub})

url_filter.qs contains filtered QuerySet
url_filter.queryset contains non-filtered QuerySet

Answered By: Alexey

To use Django Filter and paginate the filtered result you can do the following:

  1. Create a filter class for your model:

    On my_project/my_app/filters.py:

    import django_filters
    
    class MyModelFilter(django_filters.FilterSet):
        class Meta:
            model = MyModel
            # Declare all your model fields by which you will filter
            # your queryset here:
            fields = ['field_1', 'field_2', ...]
    
  2. Every FilterSet object has a .qs property which contains the filtered queryset and you can even override it if you want.

  3. We will paginate the .qs property of our MyModelFilter:

    On my_project/my_app/views.py:

    from . import filters
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    
    def my_view(request):
        # BTW you do not need .all() after a .filter() 
        # local_url.objects.filter(global_url__id=1) will do
        filtered_qs = filters.MyModelFilter(
                          request.GET, 
                          queryset=MyModel.objects.all()
                      ).qs
        paginator = Paginator(filtered_qs, YOUR_PAGE_SIZE)
    
        page = request.GET.get('page')
        try:
            response = paginator.page(page)
        except PageNotAnInteger:
            response = paginator.page(1)
        except EmptyPage:
            response = paginator.page(paginator.num_pages)
    
        return render(
            request, 
            'your_template.html', 
            {'response': response}
        )
    

And there you have it!


PS_1: Django filter in my experience, “plays” better with Django Rest Framework.

PS_2: If you are about to utilize DRF, I have written an example on how to use pagination in a function based view which you can easily combine with a FilterSet:

@api_view(['GET',])
def my_function_based_list_view(request):
    paginator = PageNumberPagination()
    filtered_set = filters.MyModelFilter(
                       request.GET, 
                       queryset=MyModel.objects.all()
                   ).qs
    context = paginator.paginate_queryset(filtered_set, request)
    serializer = MyModelSerializer(context, many=True)
    return paginator.get_paginated_response(serializer.data)
Answered By: John Moutafis

This worked for me:

in my template instead of using this

<li><a href="?page={{ i }}">{{ i }}</a></li>

I wrote this:

{% if 'whatever_parameter_you_use_to_filter' in request.get_full_path %}
   <li><a href="{{ request.get_full_path }}&page={{ i }}"{{ i }}</a></li>
{% else %}
   <li><a href="?page={{ i }}">{{ i }}</a></li>
{% endif %}

I hope it helps 🙂

Answered By: stathoula

The most important part here is the how you construct your URLs in the template.

you probably have

{% if pages.has_previous %}
<li><a href="?page={{ pages.previous_page_number }}">Prev</a></li>
{% endif %}

which is perfectly fine if you are using only it for switching between the initial paginated results.

But the tricky part is when you use the django-fitler filters, the querystring (that part after the ‘?’) gets totally new key-values pairs, disregarding your ?page=2 or similar.

So to make pagination work with filtered results, when you click the “Next” or “Prev” button – among the key-values from django-fitler you also need to pass the &page=5 as pair.

As @stathoula mentioned, you need to check if at least one of your filter fields is already present in the querystring. If it is, then you need to use the already present key-value pairs, followed by the new &page=3 pair.

It seems very simple, but I had to done small hackish not to repeat the &page=1 over and over again within the querystring as a user is clicking trough the arrows.

In my case I’m having ‘title’ as a filter, so I need to check if it’s already present there.

Here’s a snippet of what I’ve made working perfectly fine for my project.

templates/pagination.html

<div class="paginator">

    {% with request.get_full_path as querystring %}
        <ul class="pagination nav navbar-nav">

            <!-- Previous page section -->
            {% if pages.has_previous %}
                {% if 'title' in querystring %}
                    {% if 'page' in querystring %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring|slice:":-7" }}&page={{ pages.previous_page_number }}">Prev</a>
                        </li>
                    {% else %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring }}&page={{ pages.previous_page_number }}">Prev</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="paginator {% if pages.number == page %}active{% endif %}">
                        <a href="?page={{ pages.previous_page_number }}">Prev</a>
                    </li>
                {% endif %}
            {% endif %}

            <!-- All pages section -->
            {% for page in pages.paginator.page_range %}
                {% if 'title' in querystring %}
                    {% if 'page' in querystring %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring|slice:":-7" }}&page={{ page }}">{{ page }}</a>
                        </li>
                    {% else %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring }}&page={{ page }}">{{ page }}</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="paginator {% if pages.number == page %}active{% endif %}">
                        <a href="?page={{ page }}">{{ page }}</a>
                    </li>
                {% endif %}
            {% endfor %}

            <!-- Next page section -->
            {% if pages.has_next %}
                {% if 'title' in querystring %}
                    {% if 'page' in querystring %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring|slice:":-7" }}&page={{ pages.next_page_number }}">Next</a>
                        </li>
                    {% else %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring }}&page={{ pages.next_page_number }}">Next</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="paginator {% if pages.number == page %}active{% endif %}">
                        <a href="?page={{ pages.next_page_number }}">Next</a>
                    </li>
                {% endif %}
            {% endif %}

        </ul>
    {% endwith %}

</div>

Here is the view, just in case:

app/views.py

def index(request):
    condo_list = Condo.objects.all().order_by('-timestamp_created')
    condo_filter = CondoFilter(request.GET, queryset=condo_list)

    paginator = Paginator(condo_filter.qs, MAX_CONDOS_PER_PAGE)
    page = request.GET.get('page')

    try:
        condos = paginator.page(page)
    except PageNotAnInteger:
        condos = paginator.page(1)
    except EmptyPage:
        condos = paginator.page(paginator.num_pages)


    return render(request, 'app/index.html', {
        'title': 'Home',
        'condos': condos,
        'page': page,
        'condo_filter': condo_filter,
    })

Here’s a working example:

.

Answered By: Donald

To add to the answers, I did it with html tables too along with django-filters and Paginator. Below are my view and template files. The template tag is needed to make sure you pass the right parameters to the pagination url.

search_view.py

from django.shortcuts import render
from app.models.filters_model import ApiStatusFilter
from app.models.api_status import ApiStatus
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from datetime import datetime, timedelta

def status(request):
    all_entries_ordered = ApiStatus.objects.values().order_by('-created_at')[:200]

    for dictionarys in all_entries_ordered:
        dictionarys

    apistatus_list = ApiStatus.objects.values().order_by('-created_at')
    apistatus_filter = ApiStatusFilter(request.GET, queryset=apistatus_list)

    paginator = Paginator(apistatus_filter.qs, 10)
    page = request.GET.get('page')
    try:
        dataqs = paginator.page(page)
    except PageNotAnInteger:
        dataqs = paginator.page(1)
    except EmptyPage:
        dataqs = paginator.page(paginator.num_pages)

    return render(request, 'status_page_template.html', {'dictionarys': dictionarys, 'apistatus_filter': apistatus_filter, 'dataqs': dataqs, 'allobjects': apistatus_list})

status_template.html

{% load static %}
{% load my_templatetags %}

<!DOCTYPE html>
<html lang="en">
    <head>
        <link rel="stylesheet" type="text/css" href="{% static 'css/table_styling.css' %}">
        <meta charset="UTF-8">
        <title>TEST</title>
    </head>

    <body>
         <table>
            <thead>
                <tr>
                    {% for keys in dictionarys.keys %} 
                        <th>{{ keys }}</th>
                    {% endfor %}
                </tr>
            </thead>
                <form method="get">
                    {{ apistatus_filter.form.as_p }}
                    <button type="submit">Search</button>
                        {% for user in dataqs.object_list %}
                        <tr>
                            <td>{{ user.id }}</td>
                            <td>{{ user.date_time }}</td>
                            <td>{{ user.log }}</td>
                        </tr>
                        {% endfor %}
                </form>
            </tbody>
        </table>

        <div class="pagination">
            <span>
                {% if dataqs.has_previous %}
                    <a href="?{% query_transform request page=1 %}">&laquo; first</a>
                    <a href="?{% query_transform request page=dataqs.previous_page_number %}">previous</a>
                {% endif %}

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

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

my_templatetags.py

from django import template

register = template.Library()

@register.simple_tag
def query_transform(request, **kwargs):
    updated = request.GET.copy()
    for k, v in kwargs.items():
        if v is not None:
            updated[k] = v
        else:
            updated.pop(k, 0)

    return updated.urlencode()
Answered By: Elia Ahadi

It took me some time to find the DRYer and cleaner solution to fix this issue and the best one, in my opinion, is the one using template tags.

from django import template

register = template.Library()

@register.simple_tag
def relative_url(value, field_name, urlencode=None):
    url = '?{}={}'.format(field_name, value)
    if urlencode:
        querystring = urlencode.split('&')
        filtered_querystring = filter(lambda p: p.split('=')[0] != field_name, querystring)
        encoded_querystring = '&'.join(filtered_querystring)
        url = '{}&{}'.format(url, encoded_querystring)
    return url

and in your template

<a href="{% relative_url i 'page' request.GET.urlencode %}">{{ i }}</a>

Source: Dealing With QueryString Parameters

Answered By: Yassine Belmamoun

Implementation Step

  1. install package by pip install filter-and-pagination
  2. import FilterPagination by from filter_and_pagination import FilterPagination in view.py
  3. in your function writte code as bellow standards…
queryset = FilterPagination.filter_and_pagination(request, Customer)
serialize_data = CustomerSerializer(queryset['queryset'], many=True).data
resultset = {'dataset': serialize_data, 'pagination': queryset['pagination']}
  • in this code Customer is Django model &
  • CustomerSerializer is a DRF Serializer class
  1. in the resultset it contains dataset & pagination data, In this format (API Response) link: https://github.com/ashish1997it/filter-pagination-dj#demo
  2. For the API request follow PostMan collection link: https://github.com/ashish1997it/filter-pagination-dj#postman in the header section it will take a parameter & request you customize as per your requirement

If you still face any difficulty then contact me 🙂

Answered By: Ashish Sondagar

My approach for "remember filter/query URL parameters" for paginated results: passing the current URL parameters as a context variable:

# views.py

class PublicationFilterView(FilterView):
    model = Publication
    filterset_class = PublicationFilter
    paginate_by = 15

    def get_context_data(self, *args, **kwargs):
        _request_copy = self.request.GET.copy()
        parameters = _request_copy.pop('page', True) and _request_copy.urlencode()
        context = super().get_context_data(*args, **kwargs)
        context['parameters'] = parameters
        return context
# templates/path/to/pagination.html

<a href="?page={{ page_obj.next_page_number }}&{{ parameters }}">
  Next
</a>
Answered By: tombreit

this one works 100% with me

views.py:

def search(request):
    
    category=Category.objects.all()
    try:
        qs=request.GET["qs"]
        products=Product.objects.filter(Q(name__icontains=qs) |Q(details__icontains=qs) | Q(category__name__icontains=qs) | Q(branch__child__icontains=qs) | Q(manufacturer__name__icontains=qs) | Q(color__name__icontains=qs)).distinct()
        print(products)
        search=f"qs={qs}"
    except:
        search=None

and in HTML

 <ul class="shop-p__pagination">
                                        {% if products.has_provious %}
                                        <li>

                                       <a class="fas fa-angle-left" href="?page={{ products.previous_page_number }}&{search}"></a></li>
                                       {% endif  %}
                                       {% for i in products.paginator.page_range %}
                                       {% if products.number == i %}
                                       
                                       <li class="is-active"><a href="?page={{i}}&{{search}}">{{i}}</a></li>
                                       {% else %}
                                       <li><a href="?page={{i}}&{{search}}">{{i}}</a></li>

                                       {% endif %}
                                     {% endfor %}
                               
                                       {% if products.has_next %}
                                   <li>

                                       <a class="fas fa-angle-right" href="?page={{ products.next_page_number }}&{{search}}"></a></li>
                                       {% endif %}
                                    </ul>

Answered By: Abdallah Nasir

In get_context_data() function:

form_submitted = 'csrfmiddlewaretoken' in self.request.GET
context['cleaned_full_path'] = '{}{}'.format(
    self.request.get_full_path().split('&page' if form_submitted else '?page')[0],
    '&' if form_submitted else '?'
)

Then, in your template, load in something like

<a href="{{ cleaned_full_path }}page={{ page_obj.paginator.num_pages }}"

Answered By: xemexpress

In addition to @stathoula and in response to @Benbb96, I managed to erase the additional page parameters with a regular expression, overriding the setup method in the class based view:

import re

...

class MyView(ListView):
...

    def setup(self, request, *args, **kwargs) -> None:
        request.GET.get("page")
        request.META["QUERY_STRING"] = re.sub("(&|?)page=(.)*", "", request.META.get("QUERY_STRING", ""))
        return super().setup(request, *args, **kwargs)

Hope it helps anyone!

More info: