How to filter and paginate in ListView Django
Asked Answered
N

2

7

I have a problem when I want to paginate the filter that I create with django_filter, in my template it shows me the query set and filter but paginate does not work, I would like to know why this happens and if you could help me.

I'll insert snippets of my code so you can see.

This is my views.py

PD: i have all the necesary imports.

@method_decorator(staff_member_required, name='dispatch')
class EmployeeListView(ListView):
    model = Employee
    paginate_by = 4

    def dispatch(self, request, *args, **kwargs):
        if not request.user.has_perm('employee.view_employee'):
            return redirect(reverse_lazy('home'))
        return super(EmployeeListView, self).dispatch(request, *args, **kwargs)
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['filter'] = EmployeeFilter(self.request.GET, queryset = self.get_queryset())
        return context

filters.py

import django_filters
from .models import Employee, Accident

class EmployeeFilter(django_filters.FilterSet):

    class Meta:
        model = Employee
        fields = {
            'rutEmployee' : ['startswith']
        }
Nook answered 31/10, 2020 at 5:10 Comment(0)
G
7

You should override get_queryset.This means you have to put your filter in get_queryset like this:

@method_decorator(staff_member_required, name='dispatch')
class EmployeeListView(ListView):
    model = Employee
    paginate_by = 4

    def dispatch(self, request, *args, **kwargs):
        if not request.user.has_perm('employee.view_employee'):
            return redirect(reverse_lazy('home'))
        return super(EmployeeListView, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['filter'] = EmployeeFilter(self.request.GET, queryset = self.get_queryset())
        return context
    
    def get_queryset(self):
        queryset = super().get_queryset()
        return EmployeeFilter(self.request.GET, queryset=queryset).qs

and use object_list instead of filter in employee_list.html like this:

{% for employee in object_list|dictsort:"id" reversed %}
Grainfield answered 31/10, 2020 at 6:3 Comment(7)
Hi thanks for your help, apply the changes you suggest but it gives me the following error: TypeError at /employee/ filter_queryset() got multiple values for argument 'queryset' in this line: return EmployeeFilter().filter_queryset(self.request.GET, queryset=queryset)Nook
hi, i just applied the changes and it throws me the following: filter_queryset() takes 2 positional arguments but 4 were givenNook
I thought you used rest filters. Sorry for the mistake. Please try again with the new changesGrainfield
Thank you very much friend, it served me perfectly, would you mind giving me an explanation of why it works now with the function of get_queryset?Nook
If you see get_context_data method of MultipleObjectMixin in github.com/django/django/blob/master/django/views/generic/… you know how queryset is paginated. Each time you use ListView, object_list is the paginated queryset, so if you filter queryset by overriding get_queryset, object_list will be filtered.Grainfield
I understand, thank you very much for the help and the explanation, greetings from Chile.Nook
This answer is like gold for figuring out how to combine FilterView and ListView. Thank youSouther
O
1

You could also try this: (A snippet from my source code)

class ModelListView(ListView):
    model = YourModel
    paginate_by = 4 # Change this if you don't intend to paginate by 4
    ordering = model_field_to_order_by
    # variable used to know if a match was found for the search made using django_filters
    no_search_result = False

    def get_queryset(self, **kwargs):
        search_results = YourDjangoFiltersForm(self.request.GET, self.queryset)
        self.no_search_result = True if not search_results.qs else False
        # Returns the default queryset if an empty queryset is returned by the django_filters
        # You could as well return just the search result's queryset if you want to
        return search_results.qs.distinct() or self.model.objects.all()

    def get_query_string(self):
        query_string = self.request.META.get("QUERY_STRING", "")
        # Get all queries excluding pages from the request's meta
        validated_query_string = "&".join([x for x in re.findall(
            r"(\w*=\w{1,})", query_string) if not "page=" in x])
        # Avoid passing the query path to template if no search result is found using the previous query
        return "&" + validated_query_string.lower() if (validated_query_string and not self.no_search_result) else ""

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Pass to template if you want to do something whenever an empty queryset is return by django_filters
        context["no_search_result"] = self.no_search_result
        # This is the query string which should be appended to the current page in your template for pagination, very critical
        context["query_string"] = self.get_query_string()
        context['filter'] = YourDjangoFiltersForm()
        return context

In your html template you need to append the querystring passed to your template from the view, a sample is shown below

{% for i in page_obj.paginator.page_range %}
    {% if page_obj.number == i %}
          <li class="page-item active" aria-current="page">
            <span class="page-link">{{ i }}<span class="sr-only">(current)</span></span>
          </li>
          {% elif i > page_obj.number|add:'-5' and i < page_obj.number|add:'5' %} <li class="page-item"><a
              class="page-link" href="?page={{ i }}{{ query_string }}">{{ i }}</a></li>
    {% endif %}
{% endfor %}

Octavia answered 15/9, 2021 at 1:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.