Jon
Jon

Reputation: 2577

Django pagination -- maintaining filter and order by

I've a small Django project basic-pagination. There is one model, one form and two views (list view, form view). User submits data in the form view, then the data is displayed in the list view. Pagination is enabled to only display 5 posts at a time.

What I've implemented is a form with a GET response to get the data I want to display (e.g. name, date). See code below

class FormListView(ListView):
    model = models.ToDoList
    paginate_by = 5  # if pagination is desired
    template_name = 'pagination/listview.html'
    context_object_name = 'forms'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Form List View'
        context['filter_form'] = forms.FilterListView(self.request.GET)
        return context

    def get_queryset(self):
        queryset = models.ToDoList.objects.all().order_by('-id')
        name = self.request.GET.get('name')
        if name:
            queryset = queryset.filter(name=name)
        order_by = self.request.GET.get('order_by')
        if order_by:
            queryset = queryset.order_by(order_by)
        print(queryset)
        return queryset

The problem is that the class based view ListView calls the method get_queryset if you are moving from page 1 to page 2 and thus losing the filtered queryset I wanted.

How can I maintain the filtering throughout the pagination process?

Upvotes: 1

Views: 1813

Answers (2)

Jon
Jon

Reputation: 2577

As @WillemVanOnsem pointed out, the problem is not with the view, but rather with the URLs in the template (templates/pagination/listview.html). Previously, the next button had href="?page={{ page_obj.next_page_number }}" which meant that request.GET would only contain the page number for pagination and not the other filter and order by criteria.

The solution was then to append request.GET.urlencode to href like

<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}&{{ request.GET.urlencode }}">Next</a>

However, this is not a total fix because simply appending request.GET also appends the page number you're currently at. Simply put, if you jump from page 1 to 2 to 3, you will end up with a URL that looks like

http://localhost:8000/listview/?page=1&page=2&page=3...

The request.GET is a QueryDict like <QueryDict: {'page': ['1'], ...}>. A solution to this was to simply pop the page parameter, however, because request.GET is immutable you first have to make a copy of it. Essentially, I added the following lines to get_context_data method in the ListVew

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['title'] = 'Form List View'
    context['filter_form'] = forms.FilterListView(self.request.GET)
    get_copy = self.request.GET.copy()
    if get_copy.get('page'):
        get_copy.pop('page')
    context['get_copy'] = get_copy
    return context

And in the template I called the get_copy object like href="?page={{ page_obj.next_page_number }}&{{ get_copy.urlencode }}"

For the entire template example follow templates/pagination/listview.html

Not the most elegant solution, but I feel it's simple enough for most people to utilize.

Upvotes: 3

Paulista
Paulista

Reputation: 1

I was with the same problem and I solved it with the link below.

https://www.caktusgroup.com/blog/2018/10/18/filtering-and-pagination-django/

Upvotes: 0

Related Questions