Django Search (with Q objects) Tutorial
Assuming you are implementing your django blog post with search form(Bootstrap) or any HTML forms, but do not know how make it functional. This post is created for you.
Prerequisites
YOU SHOULD
- Get familiar with basic Django, and its structures
- Get familiar with Bootstrap, and basic frontend programming languages , HTML, CSS.
How does search work ?(Brief)
We type any word e.g. “django” to our search form from a navbar section, then a client(Web browser) sends request to a server, a server makes query to a database. Based on a query, if found, response a post we previously made query to show on a web page
For instance, look at the images below captured from Django official website’s documentation
It has a search form
4 Steps closer
- We typed “admin” on this form to get desired admin results related to the word admin
- q (String field) is sent to a Django server with a value “admin”
- Django server receives q variable, and navigate to a database for seaching any posts related to, or contained with “admin”
- Then send posts back to a client showing all posts related to admin on a web page
Look at this url → search/?q=admin
While…
search: Location of search form indicated that search swill start executing after this url
? : A Separator or Indicator used to indicate that after this sign is a query string part
q: A field
=: Equals sign used to encapsulate value with q(string parametr)
admin: Value we typed to search for the result of “admin”
Note: This is a query string q=admin composed with field and value pair
→ https://docs.djangoproject.com/en/3.1/search/?q=admin
Let’s get started
In this article, to reduce time consuming on writing, I did not show you how to build a project from scratch e.g. creating virtual environment, installing Django, etc. As mentioned above, I assumed that you should get familiar with Django beforehand.
Create Django project
The first step is to start a project (Assuming you all have created a directory to contain a project, virtual environment, and installed Django already)
django-admin startproject myproject
then start an app
python manage.py startapp myapp
Now we already have our first project and app
Let’s create our first Django model
models.py
from django.db import models# Create your models here.class Post(models.Model): title = models.CharField(max_length=80) content = models.TextField() date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True)
def __str__(self): return self.title
Note: we use auto_now_add=True to automatically update time after creating a post, this will show only one time, and auto_now=True to automatically update after editing a post, and it changes every time we edit a post
Register the app
settings.py
INSTALLED_APPS = [ 'myapp', # New 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles',]
Note: In this post, I use SQLite as a default database, if you want to chose the most recommended DB for Django “PostgreSQL” you can follow my previous article How to Start Django Project with a Database(PostgreSQL)
The next step is to migrate our model into the database
python manage.py makemigrations
Then
python manage.py migrate
Now we have all tables containing in a single file called db.sqlite3
Create a superuser to access an admin site
Type the following command to create a superuser
python manage.py createsuperuser
Register a Django model
Go to admin.py file
admin.py
from django.contrib import adminfrom .models import Post # New# Register your models here.admin.site.register(Post) # New
Now we just finish registering our model, let’s get into Django admin site to add some posts.
Django Admin Site
Make sure your server is still running, then navigate to an admin site by placing this url http://127.0.0.1:8000/admin
Then use previous username and password we just created
After that
Add some posts
Finally, we have 3 simple posts
Creating Django Templates
from your app directory, the path will look like this
→ myapp/templates/myapp/index.html
index.html
<!DOCTYPE html>
<head><title>Home</title></head>
<body> <h1>Hello Django</h1></body>
Now we already created an HTML file to display our posts
Django Render Template
It’s time to render an HTML file to show on a web browser.
from django.shortcuts import renderfrom .models import Post # Create your views here.def index(request):
return render(request, 'myapp/index.html')
Django URL Routing
We just created a simple function only rendering an HTML file to display to a client side, so now we have to map the function with urls we are designing now. Keep in mind that urls.py does not come default with Django, so we should create a new one.
myapp/urls.py
from django.urls import pathfrom . import viewsurlpatterns = [ path('', views.index, name="index")]
We have one step left, to register myapp.urls module into myproject’s urls
myproject/urls.py
from django.contrib import adminfrom django.urls import path, include # Newurlpatterns = [path('admin/', admin.site.urls),path('', include('myapp.urls')) # New]
Integrating Bootstrap Navbar to our Django site
Now we have a simple web page, but it does not look good enought, and not responsive as well. So Bootstrap can help us with 2 very easy steps by adding the following block code into index.html
- Add Bootstrap CDN
- Add Bootstrap Navbar
Browse to Bootstrap official website to get Bootstrap CDN
Add Bootstrap CDN and Navbar to our page
index.html
<!DOCTYPE html><head> <!-- Boostrap CSS CDN --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous"> <title>Home</title></head>
<body><!-- Boostrap Navbar --><nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav> <!-- Our content --> <h1>Hello Django</h1> <!-- Boostrap JS CDN --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script></body>
Refresh a web page, we will see
Django QuerySet(Query data from a database)
The previous step We just rendered a template(HTML file) to display on a web browser but did not send any data back. Now it’s time to do that.
views.py
from django.shortcuts import renderfrom .models import Post # New# Create your views here.def index(request):
# Query all posts posts = Post.objects.all()
return render(request, 'myapp/index.html', {'posts': posts})
Loop to show all posts
index.html
<div class="container"> <h1>Hello Django</h1><br> {% for post in posts %} <h1>{{forloop.counter}}. {{post.title}}</h1> <p>{{post.content}}</p> <p>Date Created: {{post.date_created}}</p> <p>Last Update: {{post.date_updated}}</p> {% endfor %}</div>
Note: {{forloop.counter}} used to count posts ascending order. You don’t have to use it in this post but you can integrate it with a table that used to represent a number of items. You can read more here
Descending order of our posts
We can arrange our posts by using order_by(“-date_create”) with negative sign “-” so that the lastest post will be showed on the top of a web page
posts = Post.objects.all().order_by("-date_created")
Implementing Django Search with Bootstrap form
Adding search string parameter and url endpoint to search for
- adding search string parameter called “search”
name="search"
- Add url endpoint to an action attribute of form after submitting a form
action="{% url 'index' %}"
index.html
Now, the form will look like this
<form class="form-inline my-2 my-lg-0" action="{% url 'index' %}"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" name="search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button></form>
Test searching by typing a desired word
Search with Q objects
This is the core section of this article, we use Q objects to make more complex queries following
- Use & (AND) operator to search for more than 2 fields. For example, when searching title & content, both should be true, when searching for a word such as “python”, so it(python) should be contained in both title and content fields
- Use | (OR) operator to search for only one field. For example, when searching title | content, both don’t have to be true, only one is okay, when searching for a word such as “python”, so it(python) doesn’t have to be contained in both title and content fields
Do not forget to import Q objects
For more details about Q objects, you can read from the document here
views.py
from django.shortcuts import renderfrom .models import Postfrom django.db.models import Q # New
In index function, create a new variable called “search_post” to get url parameter from a form submitting from a web page
search_post = request.GET.get('search')
We use GET method in this context because we did not make change to our resource(database) only show search result, so GET method is used.
Afterwards, add if-else conditional statements to the function
if getting a search param from client side, then do something, else does a default return data to a web page showing all posts
if search_post: posts = Post.objects.filter(Q(title__icontains=search_post) & Q(content__icontains=search_post)else: # If not searched, return default posts posts = Post.objects.all().order_by("-date_created")
It founds because I typed “flask” and of course, Flask lives both title and content fields
Not get a result because “python” lives only a title field, but we use & (AND) for our searching condition
Not get a result because “django” lives only a title field, but we use & (AND) for our searching condition
Congratulations !! you just finished implementing a search form to search posts from a database
Conclusions
What we’ve learned in this article are
- How search works
- Basic Django MTV
- Understanding Q objects
- How to implement our existing search form with Django Q objects
If this article is helpful, please clap to support me continuing my good work, and do not hesitate to drop your comment below sharing me your opinion or problem. See you again next article.
Sonny STACKPYTHON
Follow me on
My recommended article on this topic (Django search)
Django Search with Q objects (My YouTube Channel)
References