Django Tutorial #8: Optional Functions

Please note that this post may contain affiliate links, and for every purchase you make, at no extra cost to you, a commission fee will be rewarded to me.

Search

As your blog grows bigger and bigger, having a search function and a paginator would be a good idea. Let’s first talk about the search box.

Search Form

<!-- Search Widget -->
    <div class="card my-4">
        <h5 class="card-header">Search</h5>
        <form class="card-body" action="/blog/search" method="GET" role="search">
            <div class="input-group">
                <label>
                    <input type="text" class="form-control" placeholder="Search for..." name="q">
                </label>
                <span class="input-group-btn">
                <button class="btn btn-secondary" type="submit">Go!</button>
              </span>
            </div>
        </form>
    </div>

Notice a few things, in the form label, the action should be the URL that the web browser will go to after you click the search button. The method should be GET.

For the input label, the name should be q, which stands for query. Of course, you can change the name, but you’ll have to change the view as well.

And finally, the type of button should be submit.

URL

Add a new URL declaration for the search function. Open blog/urls.py and add the following code:

path('search', views.search, name='search'),

View

Go to blog/views.py and add the following view:

def search(request):
    query = request.GET.get('q', '')
    if query:
        posts = Post.objects.filter(title__icontains=query)
    else:
        posts = []
    return render(request, 'blog/search.html', {
        'posts': posts,
        'query': query,
    })

First, retrieve the data from search form, and assign it to query. If query is not empty, find posts whose titles contain query. If it is empty, then return an empty array.

Template

{% extends "blog/layout.html" %}

{% block content %}

  <!-- Page Content -->
  <div class="container">

    <div class="row">

      <!-- Blog Entries Column -->
      <div class="col-md-8">

        <h1 class="my-4">Search Results For:
          <small>{{ query }}</small>
        </h1>

          {% include 'blog/widgets/blog_masonry.html' %}

      </div>

        {% include 'blog/widgets/sidebar.html'%}

    </div>
    <!-- /.row -->

  </div>
  <!-- /.container -->

{% endblock %}

Pagination

To add a pagenator, there are two things we need to change, view and template.

View

First, open blog/views.py. I will use the home view as an example:

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger


def home(request):
    p = request.GET.get('page', '')
    posts_list = Post.objects.all()
    paginator = Paginator(posts_list, 10)
    
    try:
        posts = paginator.page(p)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    return render(request, 'blog/home.html', {
        'posts': posts,
        'categories': categories,
        'tags': tags,
    })

I did not include unrelated code about tags and categories, so please do not just copy and paste.

First, import all necessary packages.

Second, get the current page number and the list of all posts we need. After that, create a paginator with the list of posts. The 10 in Paginator(posts_list, 10) indicates there will be 10 posts on one page, you can change it to whatever you want.

Finally, return the paginator to the template. There are three different conditions, they should be pretty straightforward.

Template

Open blog_masonry.html and add the following code:

<!-- Pagination -->
{% if posts.has_other_pages %}
  <ul class="pagination">
    {% if posts.has_previous %}
      <li class="page-item"><a class="page-link" href="?page={{ posts.previous_page_number }}">«</a></li>
    {% else %}
      <li class="page-item disabled"><span class="page-link">«</span></li>
    {% endif %}
    {% for i in posts.paginator.page_range %}
      {% if posts.number == i %}
        <li class="page-item active"><span class="page-link">{{ i }} <span class="sr-only">(current)</span></span></li>
      {% else %}
        <li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li>
      {% endif %}
    {% endfor %}
    {% if posts.has_next %}
      <li class="page-item"><a class="page-link" href="?page={{ posts.next_page_number }}">»</a></li>
    {% else %}
      <li class="page-item disabled"><span class="page-link">»</span></li>
    {% endif %}
  </ul>
{% endif %}

Related Posts

At the bottom of every post, there is usually a related post section that helps your audiance to keep reading. Tags can be used to achieve that function.

Open blog/views.py and add the following code in the post view:

related_posts = Post.objects.filter(tag__in=requested_post.tag.all()).order_by('-created_at').exclude(slug=slug).distinct()[:2]

The filter method will get all posts with the same tag as the requested post. [:2] means to retrieve the first two queries.

Open templates/blog/post.html, and add the related posts section:

<!-- Related Posts -->
        <div class="row">
          {% for post in related_posts %}
          <div class="col-md-6">
            <div class="card mb-4 box-shadow">
              <img class="card-img-top" data-src="holder.js/100px225?theme=thumb&bg=55595c&fg=eceeef&text=Thumbnail" alt="Thumbnail [100%x225]" src="{{ post.featured_image.url }}" data-holder-rendered="true" style="height: 225px; width: 100%; display: block;">
              <div class="card-body">
                <h3>{{ post.title }}</h3>
                <p class="card-text">{{ post.content|striptags|truncatewords_html:25 }}</p>
                <div class="d-flex justify-content-between align-items-center">
                  <div class="btn-group">
                  <a href="/blog/post/{{ post.slug }}" class="btn btn-sm btn-outline-secondary">View</a>
                  </div>
                </div>
              </div>
            </div>
          </div>
          {% endfor %}
        </div>

Comment

First, we need to register a Disqus account.

Create a new site.

Follow the instructions and put the code into the right place, and Disqus will take care of the rest.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *