Django Pagination with Basic Search

Reading time ~2 minutes

In a web application, search and pagination are common things. Django offers a few classes to manage query and paginated data. In this post I will write about how you can create a basic search and number based pagination.

Let’s assume we have a Post model with some basic fields like title, content, user(foreign key), publish date, updated date etc.

Now we will create search form. In your post_list.html template create a search from like this.

 
<form method="GET" >
    <div class="input-field">
        <input type="text" name="q" value="{{ request.GET.q }}" placeholder="Search Here...">
       <button class="btn" type="submit" name="action">Search</button>
    </div>
</form>

To get search form working we can use Q query related class. If you don’t know about Q, Here is a quote from official documentation.

In general, Q() objects make it possible to define and reuse conditions. This permits the construction of complex database queries using | (OR) and & (AND) operators; in particular, it is not otherwise possible to use OR in QuerySets.

We can write this code snippet in views.py.

from django.shortcuts import render
from django.db.models import Q
from .models import Post

def post_list(request):
    posts_list = Post.objects.all()
    query = request.GET.get('q')
    if query:
        posts_list = Post.objects.filter(
            Q(title__icontains=query) | Q(content__icontains=query) |
            Q(user__first_name__icontains=query) | Q(user__last_name__icontains=query)
        ).distinct()
    
    context = {
        'posts': posts_list
    }
    return render(request, "post-list.html", context)

Here icontains means “Case-insensitive containment”. Now, you can display posts in posts_list.html template like this.

 
{% for post in posts %}

    <span class="meta">Posted on: {{ post.updated|date }}</span>
    <h3 class="title">{{ post.title }}</h3>
    <p>{{ post.content|truncatechars:140 }}</p>

{% endfor %}

Pagination

We will use Paginator class for pagination and EmptyPage, PageNotAnInteger for exception handling. Paginator class lives in django.core.paginator. Here is the full post_list view in views.py.

from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q
from .models import Post

def post_list(request):
    posts_list = Post.objects.all()
    query = request.GET.get('q')
    if query:
        posts_list = Post.objects.filter(
            Q(title__icontains=query) | Q(content__icontains=query) |
            Q(user__first_name__icontains=query) | Q(user__last_name__icontains=query)
        ).distinct()
    paginator = Paginator(posts_list, 6) # 6 posts per page
    page = request.GET.get('page')

    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    context = {
        'posts': posts
    }
    return render(request, "post-list.html", context)

Now we will add pagination in post-list.html template. Append this code snippet in post_list.html template.


{% if posts.has_other_pages %}
    <ul class="pagination">
        {% if posts.has_previous %}
            <li class="waves-effect"><a href="?page=
                    {{ posts.previous_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}"><i
                    class="material-icons">chevron_left</i></a></li>
        {% else %}
            <li class="disabled"><a href="#!"><i class="material-icons">chevron_left</i></a></li>
        {% endif %}
        {% for num in posts.paginator.page_range %}
            {% if posts.number == num %}
                <li class="active"><a href="#!">{{ num }}</a></li>
            {% else %}
                <li class="waves-effect"><a
                        href="?page={{ num }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">{{ num }}</a></li>
            {% endif %}
        {% endfor %}
        {% if posts.has_next %}
            <li class="waves-effect"><a
                    href="?page={{ posts.next_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}"><i
                    class="material-icons">chevron_right</i></a></li>
        {% else %}
            <li class="disabled"><a href="#!"><i class="material-icons">chevron_right</i></a></li>
        {% endif %}
    </ul>
{% endif %}

N.B: This pagination is based on Materializecss Pagination. You can follow this for Bootstrap.