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})
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
To use Django Filter and paginate the filtered result you can do the following:
-
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', ...]
-
Every FilterSet
object has a .qs
property which contains the filtered queryset and you can even override it if you want.
-
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)
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 🙂
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:
.
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 %}">« 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 »</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()
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>
- Simple & sweet,
- use this,
pip install filter-and-pagination
- https://pypi.org/project/filter-and-pagination/
Implementation Step
- install package by
pip install filter-and-pagination
- import FilterPagination by
from filter_and_pagination import FilterPagination
in view.py
- 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
- in the resultset it contains dataset & pagination data, In this format (API Response) link: https://github.com/ashish1997it/filter-pagination-dj#demo
- 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 🙂
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>
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>
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 }}"
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:
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})
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
To use Django Filter and paginate the filtered result you can do the following:
-
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', ...]
-
Every
FilterSet
object has a.qs
property which contains the filtered queryset and you can even override it if you want. -
We will paginate the
.qs
property of ourMyModelFilter
: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)
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 🙂
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:
.
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 %}">« 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 »</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()
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>
- Simple & sweet,
- use this,
pip install filter-and-pagination
- https://pypi.org/project/filter-and-pagination/
Implementation Step
- install package by
pip install filter-and-pagination
- import FilterPagination by
from filter_and_pagination import FilterPagination
in view.py - 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
- in the resultset it contains dataset & pagination data, In this format (API Response) link: https://github.com/ashish1997it/filter-pagination-dj#demo
- 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 🙂
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>
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>
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 }}"
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: