I'm using Django-filters to filter results. The filter is working correctly, but now the pagination is not working. It's being rendered, but now all the products are being displayed in one page. I was using paginate_by = 6
as it is a class based view. Even after filtering results, for example there are 8 products matching the filter, everything is being displayed in one single page. Why is it not working? Can anyone please help me out? Thanks in advance!
My filters.py:
import django_filters
from .models import Item
class ItemFilter(django_filters.FilterSet):
class Meta:
model = Item
fields = {
'category': ['exact'],
'price': ['lte']
}
My views.py:
from .filters import ItemFilter
class homeview(ListView):
model = Item
template_name = 'products/home.html'
paginate_by = 6
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = ItemFilter(self.request.GET, queryset=self.get_queryset())
return context
My index.html:
<div class="card mb-4">
<div class="card-body">
<div class="container">
<form method="GET">
{{ filter.form|crispy }}
<button type="submit" class="btn btn-primary mt-4">Filter</button>
</form>
</div>
</div>
</div>
<h1>List Of Items</h1>
<div class="row mb-4">
{% for item in filter.qs %}
<div class="col-lg-4">
<img class="thumbnail" src="{{ item.image_url }}">
<div class="box-element product">
<h6><strong>{{ item.title }}</strong></h6>
<h6 class="text-success">Category - {{ item.get_category_display }}</h6>
<hr>
</div>
</div>
{% endfor %}
</div>
<ul class="pagination justify-content-center">
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-dark mb-4" href="?page=1">First</a>
<a class="btn btn-outline-dark mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-dark mb-4" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-dark mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-outline-dark mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
<a class="btn btn-outline-dark mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
{% endif %}
</ul>
First of all You're not using page object created by django's ListView methods. Technically you pass a new queryset instead. Therefore you're listing all the results instead of paginated results.
super().context already has a page object which is not used here. since you use ItemFilter
Here's how I'd handle the situation. without django_filters though
forms.py
class FilterForm(forms.Form):
category = forms.CharField()
price = forms.IntegerField()
views.py
from .forms.py import FilterForm
class HomeView(ListView):
model = Item
template_name = 'products/home.html'
paginate_by = 6
def get_filter_args(self):
request = self.request
filter_args = {}
filter_args['category'] = request.GET.get('category')
filter_args['price__lte'] = request.GET.get('price')
# To remove filter arg if the value is null. to avoid errors
filter_args = {key: value for key, value in filter_args.items() if value}
return filter_args
def get_queryset(self):
filter_args = self.get_filter_args()
queryset = super().get_queryset().filter(**filter_args)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = FilterForm()
return context
In order to prevent the applied filters from losing on pagination, use a helper to build url for pagination add this file in a templatetags folder inside the app folder (same path where views.py, models.py etc...)
temptag.py
register = template.Library()
@register.simple_tag
def paginate_url(field_name, value, urlencode=None):
get_query = f'{field_name}={value}'
if urlencode:
qs = urlencode.split('&')
_filtered = filter(lambda p: p.split('=')[0] != field_name, qs)
querystring = '&'.join(_filtered)
get_query = f'{get_query}&{querystring}'
return get_query
you can import the template helper inside html file html file
{% load temptag %}
<div class="card mb-4">
<div class="card-body">
<div class="container">
<form method="GET">
{{ form|crispy }}
<button type="submit" class="btn btn-primary mt-4">Filter</button>
</form>
</div>
</div>
</div>
<h1>List Of Items</h1>
<div class="row mb-4">
{% for item in object_list %}
<div class="col-lg-4">
<img class="thumbnail" src="{{ item.image_url }}">
<div class="box-element product">
<h6><strong>{{ item.title }}</strong></h6>
<h6 class="text-success">Category - {{ item.get_category_display }}</h6>
<hr>
</div>
</div>
{% endfor %}
</div>
<ul class="pagination justify-content-center">
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-dark mb-4" href="?{% paginate_url 'page' 1 request.GET.urlencode %}">First</a>
<a class="btn btn-outline-dark mb-4" href="?{% paginate_url 'page' page_obj.previous_page_number request.GET.urlencode %}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-dark mb-4" href="?{% paginate_url 'page' num request.GET.urlencode %}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-dark mb-4" href="?{% paginate_url 'page' num request.GET.urlencode %}"</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="btn btn-outline-dark mb-4" href="?{% paginate_url 'page' page_obj.next_page_number request.GET.urlencode %}">Next</a>
<a class="btn btn-outline-dark mb-4" href="?{% paginate_url 'page' page_obj.paginator.num_pages request.GET.urlencode %}">Last</a>
{% endif %}
{% endif %}
</ul>