djangofilterdjango-pagination

Filters and Pagination with Django Function-Based Views


I'm working on my first Django program and would like to create a template including filtering and pagination functions that could be used simultaneously. This is what I have so far:

models.py

class Player (models.Model):
    name = models.CharField(max_length=32)
    surname = models.CharField(max_length=32)
    
    def __str__(self):
        return f"{self.name} {self.surname}"
            
    class Meta:
        verbose_name_plural = "Players"

views.py

def GetPlayers(request):
    players = Player.objects.all().values()
    
    pn = request.GET.get('name')
    if pn != '' and pn is not None:
        players = players.filter(name__icontains=pn)

    page_num = request.GET.get('page', 1)
    paginator = Paginator(players, 2)
    page_obj = paginator.get_page(page_num)
    
    template = loader.get_template('players.html')
    context = {
        'players' : players,
        'page_obj' : page_obj,
    }
    return HttpResponse(template.render(context, request))

players.html

{% block content %}
  <div class="mycard">
    <h1>Players</h1>
    <div class="filters">
      <form action="" method="GET">
        <div class="row">
          <div class="col-xl-3">
            <label>Name:</label>
            <input type="text" class="form-control" placeholder="name" name="name" {% if name %} value = "{{ name }}" {% endif %}>
          </div>
          <div class="col-xl-2" style="padding-top: 2%;">
            <button type="submit" class="btn custom-btn">Filter</button>
          </div>
        </div>
      </form>
    </div>
    <p/>
    <div style="overflow-x:auto;">
      <table>
        <thead>
          <th>Name</th>
          <th>Surname</th>
        </thead>
        <tbody>
          {% for x in page_obj %}
            <td>{{ x.name }}</td>          
            <td>{{ x.surname }}</td>
          </tr>
          {% endfor %}
        </tbody>
      </table>
      {% include "pagination.html" %}
    </div>
  </div>
{% endblock %}

Let's imagine I have 6 players called "John", "Mark", "Phil", "Jason", "Jane" and "Juliet". If I don't apply any name filtering, they will be grouped as follows:

Based on this, if I filter by names containing "J" in the filter text field, I would expect to have:

Page 1 works as intended, but when I click on the link to navigate to page 2, the filter seems to be reset and I got "Phil" and "Jason"; besides, a link to the third page becomes available, including "Jane and Juliet" as if there was no filter applied at all.

How could I keep filters applied while navigating through the queryset pages?


Solution

  • You need to append your filter as a query param to the pagination URL. For that it is necessary to have it inside the context.

    views.py (a minor change to shorten and make it a bit more clean)

    def GetPlayers(request):
        players = Player.objects.all().values()
    
        pn = request.GET.get("name", None)
        if pn:
            players = players.filter(name__icontains=pn)
    
        paginator = Paginator(players, 2)
        page_num = request.GET.get("page")
        page_obj = paginator.get_page(page_num)
    
        ctx = {"players": players, "page_obj": page_obj, "pn": pn}
        return render(request, "players.html", ctx)
    

    pagination.html

    <div class="pagination">
      <span class="step-links">
        {% if page_obj.has_previous %}
        <a href="?page=1{% if pn %}&name={{pn}}{% endif %}">&laquo; first</a>
        <a href="?page={{ page_obj.previous_page_number }}{% if pn %}&name={{pn}}{% endif %}">previous</a>
        {% endif %}
    
        <span class="current">
          Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>
    
        {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}{% if pn %}&name={{pn}}{% endif %}">next</a>
        <a href="?page={{ page_obj.paginator.num_pages }}{% if pn %}&name={{pn}}{% endif %}">last &raquo;</a>
        {% endif %}
      </span>
    </div>
    

    Note: As a good practice (name convention), functions and variables are often named in snake_case