Search code examples
htmldjangodjango-filter

Django pagination problem - pages not getting paginated


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>

Solution

  • 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>