[Fixed]-Pagination and get parameters

7👍

Typically, to preserve GET parameters you simply re-write them manually. There shouldn’t be many cases where having to do this will matter.

&page={{page}}&total={{total}}

You can abstract this away into a template include or a custom template tag.

https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/

Additionally, you could create a string filter that takes URL params as a string as well as a dict of values you want to change. The filter could then parse the params string, update the value, then recombine the string back into URL params.

{{ request.get_full_path | update_param:'page=8' }}
👤Soviut

15👍

The generic solution is to define a “custom template tag” (a function) which keeps the complete URL but updates the GET parameters you pass to it.

After registration, you can use this function in your templates:

<a href="?{% query_transform page=contacts.previous_page_number %}">previous</a>

To define and register the custom template tag, include this code in a python file:

from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def query_transform(context, **kwargs):
    query = context['request'].GET.copy()
    for k, v in kwargs.items():
        query[k] = v
    return query.urlencode()

*Thanks to Ben for the query_transform code. This is an adapation for python 3 from his code.

Why this method is better than reconstructing the URLs manually:

If you later decide that you need additional parameters in your URLs: 1. you don’t have to update all the links in your templates. 2. You don’t need to pass all the params required for recostructing the URL to the templates.

1👍

Subclassing

I solved this problem by subclassing Django’s Paginator class and adding the stuff I needed there.

You need to override the init method to allow yourself to pass the request and form objects from the view, so that you can retrieve the request.GET parameters and compare them to form.fields. You want to only allow GET parameter names that are predefined in your form. Otherwise you remove them.

Once you have subclassed the Pagination class, you can use it in your views.py files, passing the extra request and form parameters to it. Then you can render your template as suggested in the Django documentation, but now you have access to the paginator.first_params, paginator.last_params, page.previous_params, and page.next_params variables that you can use.

pagination.py

import urllib

from django.core.paginator import Paginator as DjangoPaginator, EmptyPage

class Paginator(DjangoPaginator):

    def __init__(self, *args, **kwargs):
        request = kwargs.pop('request', None)
        form = kwargs.pop('form', None)
        super().__init__(*args, **kwargs)
        
        self.params = {key: value for key, value in request.GET.items() if key in form.fields.keys()}

        self.first_params = urllib.parse.urlencode({**self.params, 'page': 1})
        self.last_params = urllib.parse.urlencode({**self.params, 'page': self.num_pages})

    def get_page(self, *args, **kwargs):
        page = super().get_page(*args, **kwargs)
    
        try:
            page.previous_page_number = page.previous_page_number()
        except EmptyPage:
            page.previous_page_number = None
        
        try:
            page.next_page_number = page.next_page_number()
        except EmptyPage:
            page.next_page_number = None
        
        page.previous_params = urllib.parse.urlencode({**self.params, 'page': page.previous_page_number})
        page.next_params = urllib.parse.urlencode({**self.params, 'page': page.next_page_number})
    
        return page

views.py

from .pagination import Paginator

paginator = Paginator(queryset, per_page, request=request, form=form)
page = paginator.get_page(page_number)

pagination.html

{% if page %}
    <nav id="pagination">
        {% if page.has_previous %}
            <a href="?{{ paginator.first_params }}">&laquo; First</a>
            <a href="?{{ page.previous_params }}" rel="prev">Previous</a>
        {% endif %}
        <span class="current">
            Page {{ page.number }} of {{ page.paginator.num_pages }}.
        </span>
        {% if page.has_next %}
            <a href="?{{ page.next_params }}" rel="next">Next</a>
            <a href="?{{ paginator.last_params }}">Last &raquo;</a>
        {% endif %}
    </nav>
{% endif %}

0👍

This is what I did and find easier. Many not be better approach though!

        param = ""
        if search_programs:
            qs = qs.filter(title__icontains=search_programs)
            param = param + f"&search_programs={search_programs}"
        if level:
            qs = qs.filter(level__icontains=level)
            param = param + f"&level={level}"
        if category:
            qs = qs.filter(sector__icontains=category)
            param = param + f"&category={category}"
        paginator = Paginator(qs, 16)
        page_number = self.request.GET.get('page')
        page_obj = paginator.get_page(page_number)
        context = {
            'page_obj': page_obj,
            "param": param
        }
        return render(self.request, self.template_name, context)
# in html
 <a href="?page={{ page_obj.next_page_number }}{{param}}" class="page-link">next</a>

Leave a comment