Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 43

Implementing Search for Tasks

Recommended resources

Hello! Welcome back to our course on building a full-stack task manager with Python and Django.

In the last lesson, you did a fantastic job of integrating the Bootstrap CSS framework, transforming your application's appearance from a basic prototype to a professional-looking web app. We now have a solid, well-styled foundation to build upon.

This lesson focuses on a crucial piece of functionality for any data-driven application: adding a search feature. As a user's task list grows, finding a specific item becomes difficult. You will implement a search bar that allows users to quickly filter their tasks by keywords, a feature that significantly improves usability and is a great skill to showcase in your portfolio project.

We will break this down into three main steps:

  1. Adding a search form to your task list template.
  2. Modifying your view to process the search query.
  3. Using Django's powerful query capabilities to filter the task list.

1. Creating the Search Form

First, we need to give the user a place to type their search query. The most logical place for this is on the task_list.html page, right above the list of tasks. We will use a standard HTML <form> that sends the user's input to our view using the GET method.

Using GET is the standard for search functionality. It appends the search query to the URL (e.g., .../tasks/?q=meeting), which means the search results page can be bookmarked and shared. Since a search doesn't change any data in the database, GET is the appropriate choice over POST.

The following article provides a clear guide on the entire search implementation process. We'll start by focusing on just the HTML form.

Django Search Tutorial

The article 'Django Search Tutorial' from LearnDjango.com provides an excellent walkthrough of this process using Class-Based Views, which aligns perfectly with our project structure. Let's start by looking at how they build the search form.

Read the short section titled 'Search Form'. Pay attention to the three key parts of the form: the action, the method, and the name of the input field. We'll use these same principles.

Now, let's apply this to your task_list.html template. We'll add a form that includes Bootstrap classes for styling, which you learned about in the previous lesson.

Open your task_list.html and add the following code, perhaps right below the "Add New Task" button and before your <table>:

<!-- In templates/task_list.html -->

<hr>
<form method="GET" class="mb-4">
    <div class="input-group">
        <input type="text" name="q" class="form-control" placeholder="Search tasks...">
        <button class="btn btn-outline-primary" type="submit">Search</button>
    </div>
</form>

<h3>Your Tasks</h3>
<table class="table">
    <!-- ... rest of your table ... -->

Here's what this code does:

  • <form method="GET">: We're using the GET method. Since we haven't specified an action attribute, the form will submit back to the same URL it's on—our task list page. This is exactly what we want.
  • <input type="text" name="q" ...>: This is the search box. The name="q" is crucial; q (for query) is the key we will use in our view to retrieve whatever the user typed.
  • Bootstrap Classes: input-group, form-control, and btn are used to style the form nicely without any custom CSS.

If you run your server and refresh the page, you'll see the new search bar. If you type something and hit "Search," you'll notice the URL changes, but the list of tasks remains the same. The next step is to make the view respond to that query.


2. Filtering the QuerySet in the View

Your task list is displayed by the TaskListView, which is a ListView. The best way to change the data a ListView displays is by overriding its get_queryset() method. This method is responsible for determining the list of objects that the template will receive.

Currently, your TaskListView likely has a method to ensure users only see their own tasks. We're going to extend that logic.

Django Search Tutorial

Let's return to the 'Django Search Tutorial' article. The final section shows exactly how to connect the form's input to the view's logic.

Read the section that begins right after the 'San Diego Search Result' image. Focus on how get_queryset is modified. It retrieves the query from self.request.GET.get("q") and then uses it in a filter() call.

Now, let's update your views.py. You'll need to modify your TaskListView.

  1. Get the search query: Inside get_queryset(), we'll retrieve the value of q from the URL.
  2. Filter the tasks: If a query exists, we'll add another .filter() to our queryset to search the task title. We'll use the __icontains lookup, which performs a case-insensitive "contains" search.

Here is how your modified TaskListView should look. Pay close attention to the get_queryset method.

# In your views.py file

from django.views.generic import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Task

class TaskListView(LoginRequiredMixin, ListView):
    model = Task
    context_object_name = 'tasks'
    template_name = 'task_list.html'

    def get_queryset(self):
        # Start with all tasks for the current user
        queryset = super().get_queryset().filter(user=self.request.user)
        
        # Get the search query from the URL's 'q' parameter
        query = self.request.GET.get('q')
        
        # If a query is present, filter the queryset
        if query:
            queryset = queryset.filter(title__icontains=query)
        
        return queryset.order_by('created') # Or however you prefer to order them

With this change, your search bar is now functional! Try searching for a word that appears in one of your task titles. The list should update to show only the matching tasks.


3. Searching Multiple Fields with Q Objects

This is great, but what if a user wants to search for something in the task's description, not just the title? Chaining .filter() calls performs an AND search. To perform an OR search (e.g., find tasks where the title OR the description contains the query), we need a more powerful tool: Q objects.

Q objects allow you to build complex queries with AND (&) and OR (|) operators.

Django Search Tutorial

The 'Django Search Tutorial' also has a great section on this concept.

Read the section titled 'Q Objects'. It clearly explains how to import Q and use the | (pipe) operator to combine two filter conditions.

Let's update get_queryset one more time to search both the title and description fields of your Task model. (This assumes your Task model has a description field; if not, you can adapt this to other text-based fields you may have).

First, import Q at the top of your views.py:

# At the top of views.py
from django.db.models import Q

Next, modify the if query: block in your get_queryset method:

# In your TaskListView's get_queryset method

# ... inside get_queryset ...
if query:
    queryset = queryset.filter(
        Q(title__icontains=query) | 
        Q(description__icontains=query)
    )
# ... rest of the method ...

Now your search is much more powerful. It will return any task where the search term appears in either the title or the description.

Test your understanding!

Imagine your Task model has a BooleanField called complete. How would you modify the get_queryset method to allow a user to search for tasks, but only show results that are incomplete (i.e., complete=False)?

Show answer

You would add another .filter() call that is not part of the if query: block, so it always applies.

def get_queryset(self):
    queryset = super().get_queryset().filter(user=self.request.user)
    
    # Always filter for incomplete tasks
    queryset = queryset.filter(complete=False)
    
    query = self.request.GET.get('q')
    if query:
        queryset = queryset.filter(
            Q(title__icontains=query) | 
            Q(description__icontains=query)
        )
    
    return queryset.order_by('created')

4. Improving the User Experience

To round out this feature, let's make two final improvements:

  1. Display the search term on the results page so the user knows what they searched for.
  2. Keep the searched term in the search box after the form is submitted.

We can achieve both by passing the search query into the template's context. Override the get_context_data method in TaskListView to do this.

# In your TaskListView
def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    # Pass the search query back to the template
    context['search_query'] = self.request.GET.get('q', '')
    return context

Now, in task_list.html, you can access the {{ search_query }} variable.

  1. Set the input value:
    <input type="text" name="q" class="form-control" placeholder="Search tasks..." value="{{ search_query }}">
    
  2. Display the search term:
    {% if search_query %}
        <p>Showing results for: <strong>"{{ search_query }}"</strong></p>
    {% endif %}
    

Place this {% if %} block just below your form. Now, when a user searches, the text stays in the input box and a confirmation message appears on the page.


Conclusion

Excellent work! You've successfully added a robust, user-friendly search feature to your task manager. This is a common requirement in web development, and you've implemented it using clean, efficient, and standard Django practices.

Key Takeaways:

  • Search forms should use the GET method, submitting the query as a URL parameter (e.g., ?q=...).
  • In a ListView, the get_queryset() method is the perfect place to filter data based on user input.
  • The __icontains lookup provides a simple, case-insensitive way to find text within a field.
  • Q objects are essential for building complex queries, allowing you to combine filters with OR (|) and AND (&) logic.
  • Passing the search query back to the template context allows you to create a better user experience.

Preview of the Next Lesson:

Building on what you learned about manipulating querysets, the next lesson will focus on implementing filtering. You will add controls to allow users to sort their tasks or view only those with a specific status or priority, further enhancing the application's utility.