30 Aug 2016

Building a Simple Search in Django

This is a brief tutorial on how to implement a simple text search on your Django site, for something like a blog, as I've implemented here at kobrien.me.  

Step 1

The first piece we will write is the form itself, where users will enter their search term.  As seen in the HTML below, it is relatively simple.  We just have to tell it where to submit with the action attribute.  We will define this url in the following steps, although take note of the /blog/search portion after the {{ request.get_host }} tag.  In my case, I've set up my search as a part of my blog app.  Other than that, we just tell our button to submit the form by its id name when clicked, and it's all set.  

<form id="searchform" action="{{ request.get_host }}/blog/search/" method="get">
    <input type="text" name="q" type="text"/>
    <button type="button" onclick="searchform.submit()">Search</button>
</form>

Step 2

I will mention again, I've included the search within my blog app.  To use this example, you want to be working in the same app as the models you're trying to search, and the views that will display the results.  Add the following line to your app's urls.py file under the urlpatterns list in your app:

url(r'^search/', views.search, name="search")

Step 3

Now that the request is being directed to the correct view, let's create the view.  We will create a search function to retrieve the request, and match it against any blog post titles or contents containing this query string.  We will return these results, and sort them by date created, and feed them to our search.html page where they will be displayed to the user.

def search(request):
    query_string = ''
    found_entries = None
    if ('q' in request.GET) and request.GET['q'].strip():
        query_string = request.GET['q']
        entry_query = utils.get_query(query_string, ['title', 'body',])
        posts = Post.objects.filter(entry_query).order_by('created')
        return render(request, 'search.html', { 'query_string': query_string, 'posts': posts })
    else:
        return render(request, 'search.html', { 'query_string': 'Null', 'found_entries': 'Enter a search term' })

This search will only return exact matches.  Using more advanced Python libraries like FuzzyWuzzy, we can perform searches using queries with like text, instead of returning only exact matches.  

Step 4

Finally, lets create our search.html template to display the results to the user.

<p>Posts containing '{{query_string}}'</p> 
{% for post in posts %}
    <div>
        <span> {{ post.created }} </span>
        <a href="{% url 'post_detail' slug=post.slug %}"><h2 class="page-title">{{ post.title }}</h2></a>
        <div class="post">
            {{ post.body|safe }}
        </div>
    </div>
{% endfor %}

Your search functionality is now complete! 

Bonus

Lets add a finishing touch (assuming you're using jQuery like me).  At the bottom of your search.html template, include the jquery.highlight plugin (eg. <script src="{% static 'js/jquery.highlight-5.js' %}"></script>) and add the following: 

<script>
    $('body').highlight("{{query_string}}");
</script>

If executed correctly, this will retreive your query_string, and highlight on the page any text that matches, allowing users to easily find exactly what they were searching for.  Go ahead and give it a try using the search on my site.  

Please let me know if you have any questions or suggestions for improvement.  

Thanks for reading.