Search

The Web is all about search. Two of the Net’s biggest success stories, Google and Yahoo, built their multi-billion-dollar businesses around search. Nearly every site sees a large percentage of traffic coming to and from its search pages. Often the difference between the success or failure of a site is the quality of its search. So it looks like we’d better add some searching to our fledgling books site, no?

We’ll start by adding the search view to our URLconf (mysite.urls). Recall that this means adding something like (r’^search/$’, ‘mysite.books.views.search’) to the set of URL patterns. Next, we’ll write this search view into our view module (mysite.books.views):

from django.db.models import Q
from django.shortcuts import render_to_response
from models import Book

def search(request):
query = request.GET.get('q', '')
if query:
qset = (
Q(title__icontains=query) |
Q(authors__first_name__icontains=query) |
Q(authors__last_name__icontains=query)
)
results = Book.objects.filter(qset).distinct()
else:
results = []
return render_to_response("books/search.html", {
"results": results,
"query": query
})

There are a couple of things going on here that you haven’t yet seen. First, there’s request.GET. This is how you access GET data from Django; POST data is accessed through a similar request.POST object. These objects behave exactly like standard Python dictionaries.

What’s GET and POST Data? – GET and POST are the two methods that browsers use to send data to a server. Most of the time, you’ll see them in HTML form tags:

<form action="/books/search/" method="get">

This instructs the browser to submit the form data to the URL /books/search/ using the GET method. There are important differences between the semantics of GET and POST that we won’t get into right now, but see http://www.w3.org/2001/tag/doc/whenToUseGet.html if you want to learn more. So the line:

query = request.GET.get('q', '')

looks for a GET parameter named q and returns an empty string if that parameter wasn’t submitted.

Note that we’re using the get() method on request.GET, which is potentially confusing. The get() method here is the one that every Python dictionary has. We’re using it here to be careful: it is not safe to assume that request.GET contains a ‘q’ key, so we use get(‘q’, ”) to provide a default fallback value of ” (the empty string). If we merely accessed the variable using request.GET[‘q’], that code would raise a KeyError if q wasn’t available in the GET data.

Second, what about this Q business? Q objects are used to build up complex queries — in this case, we’re searching for any books where either the title or the name of one of the authors matches the search query. Technically, these Q objects comprise a QuerySet.

In these queries, icontains is a case-insensitive search that uses the SQL LIKE operator in the underlying database.

Since we’re searching against a many-to-many field, it’s possible for the same book to be returned more than once by the query (e.g., a book with two authors who both match the search query). Adding .distinct() to the filter lookup eliminates any duplicate results.
There’s still no template for this search view, however. This should do the trick:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>Search{% if query %} Results{% endif %}</title>
</head>
<body>
<h1>Search</h1>
<form action="." method="GET">
<label for="q">Search: </label>
<input type="text" name="q" value="{{ query|escape }}">
<input type="submit" value="Search">
</form>

{% if query %}
<h2>Results for "{{ query|escape }}":</h2>

{% if results %}
<ul>
{% for book in results %}
<li>{{ book|escape }}</l1>
{% endfor %}
</ul>
{% else %}
<p>No books found</p>
{% endif %}
{% endif %}
</body>
</html>

Hopefully by now what this does is fairly obvious. However, there are a few subtleties worth pointing out: The form’s action is ., which means “the current URL.” This is a standard best practice: don’t use separate views for the form page and the results page; use a single one that serves the form and search results.

We reinsert the value of the query back into the <input>. This lets readers easily refine their searches without having to retype what they searched for. Everywhere query and book is used, we pass it through the escape filter to make sure that any potentially malicious search text is filtered out before being inserted into the page. It’s vital that you do this with any user-submitted content! Otherwise you open your site up to cross-site scripting (XSS) attacks.

However, we don’t need to worry about harmful content in your database lookups — we can simply pass the query into the lookup as is. This is because Django’s database layer handles this aspect of security for you.

Now we have a working search. A further improvement would be putting a search form on every page (i.e., in the base template); we’ll let you handle that one yourself.

So why the distinction?

Because both request.GET and request.POST have additional methods that normal dictionaries don’t have. We’ll get into these in a short while. You might have encountered the similar term “file-like objects” – Python objects that have a few basic methods, like read(), that let them act as stand-ins for “real” file objects.

HTML Forms

HTML forms are the backbone of interactive web sites, from the simplicity of Google’s single search box to ubiquitous blog comment submission forms, to complex custom data-entry interfaces. We will cover how you can use Django to access user-submitted form data, validate it and do something with it. Along the way, we’ll cover HttpRequest and Form objects.

Getting Data from the Request Object

Recall that each view function takes an HttpRequest object as its first parameter, as in our hello() view:

from django.http import HttpResponse

def hello(request):
return HttpResponse("Hello world")

HttpRequest objects, such as the variable request here, have a number of interesting attributes and methods that you should familiarize yourself with, so that you know what’s possible. You can use these attributes to get information about the current request (i.e., the user/web browser that’s loading the current page on your Django-powered site), at the time the view function is executed.
Information About the URL
HttpRequest objects contain several pieces of information about the currently requested URL.

Attribute/method Description Example
request.path The full path, not including the domain but including the leading slash. “/hello/“
request.get_host() The host (i.e., the “domain,” in common parlance). “127.0.0.1:8000” or “www.example.com“
request.get_full_path() The path, plus a query string (if available). “/hello/?print=true“
request.is_secure() True if the request was made via HTTPS. Otherwise, False. True or False

Always use these attributes/methods instead of hard-coding URLs in your views. This makes for more flexible code that can be reused in other places. A simplistic example:

# BAD!
def current_url_view_bad(request):
return HttpResponse("Welcome to the page at /current/")

# GOOD
def current_url_view_good(request):
return HttpResponse("Welcome to the page at %s"
% request.path)

Other Information About the Request

request.META is a Python dictionary containing all available HTTP headers for the given request – including the user’s IP address and user agent (generally the name and version of the web browser). Note that the full list of available headers depends on which headers the user sent and which headers your web server sets. Some commonly available keys in this dictionary are:

  •  HTTP_REFERER – The referring URL, if any. (Note the misspelling of REFERER.)Other Information About the Request
  • HTTP_USER_AGENT – The user’s browser’s user-agent string, if any. This looks something like: “Mozilla/5.0 (X11; U; Linux“ i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17”.
  • REMOTE_ADDR – The IP address of the client, e.g., “12.345.67.89”. (If the request has passed through any proxies, then this might be a comma-separated list of IP addresses, e.g., “12.345.67.89,23.456.78.90”.)

Note that because request.META is just a basic Python dictionary, you’ll get a KeyError exception if you try to access a key that doesn’t exist. (Because HTTP headers are external data – that is, they’re submitted by your users’ browsers – they shouldn’t be trusted, and you should always design your application to fail gracefully if a particular header is empty or doesn’t exist.) You should either use a try/except clause or the get() method to handle the case of undefined keys:

# BAD!
def ua_display_bad(request):
ua = request.META['HTTP_USER_AGENT'] # Might raise KeyError!
return HttpResponse("Your browser is %s" % ua)

# GOOD (VERSION 1)
def ua_display_good1(request):
try:
ua = request.META['HTTP_USER_AGENT']
except KeyError:
ua = 'unknown'
return HttpResponse("Your browser is %s" % ua)

# GOOD (VERSION 2)
def ua_display_good2(request):
ua = request.META.get('HTTP_USER_AGENT', 'unknown')
return HttpResponse("Your browser is %s" % ua)

I encourage you to write a small view that displays all of the request.META data so you can get to know what’s in there. Here’s what that view might look like:

def display_meta(request):
values = request.META 
html = []
for k in sorted(values):
html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, values[k]))
return HttpResponse('<table>%s</table>' % '\n'.join(html))

Another good way to see what sort of information that the request object contains is to look closely at the Django error pages when you crash the system – there is a wealth of useful information in there, including all the HTTP headers and other request objects (request.path for example).

Information About Submitted Data

Beyond basic metadata about the request, HttpRequest objects have two attributes that contain information submitted by the user: request.GET and request.POST. Both of these are dictionary-like objects that give you access to GET and POST data. POST data generally is submitted from an HTML <form>, while GET data can come from a <form> or the query string in the page’s URL.

Dictionary-like objects – When I say request.GET and request.POST are “dictionary-like” objects, I mean that they behave like standard Python dictionaries but aren’t technically dictionaries under the hood. For example, request.GET and request.POST both have get(), keys() and values() methods, and you can iterate over the keys by doing for key in request.GET.

A Simple Django Form-Handling Example

Continuing the ongoing example of books, authors and publishers, let’s create a simple view that lets users search our book database by title. Generally, there are two parts to developing a form: the HTML user interface and the backend view code that processes the submitted data. The first part is easy; let’s just set up a view that displays a search form. When you used startapp to create your books app, Django created a new views.py file for you in your \books folder. Go ahead and add a new view to this file:

# \books\views.py

from django.shortcuts import render

def search_form(request):
return render(request, 'books/search_form.html')

Next step is to create the template, however we first need to create a couple of new folders for the template. If you haven’t changed it, the ‘APP_DIRS’ in your settings file is set to True. This means that Django will search all of your apps for a folder named \templates.

Create a new \templates folder inside your books app folder. Then go ahead and create another folder inside the new \templates folder and call it books. Your final folder structure will be books\templates\books\.

This inner books folder is important for namespacing your templates. Because Django will search all apps for a matching template, creating a namespace for the app templates ensures that Django uses the correct template if two apps used the same template name.

Create the following search_form.html file and save it to your new folder:

# mysite_project\mysite\books\templates\books\search_form.html

<html>
<head>
<title>Search</title>
</head>
<body>
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
</body>
</html>

Now we need to create a URLconf so Django can find our new view. Inside your books folder, create a new urls.py file (startapp doesn’t create this file). Add the following URL pattern to this new urls.py:

# mysite\books\urls.py

from django.conf.urls import url
from books import views

urlpatterns = [
url(r'^search-form/

(Note that we’re importing the views module directly, instead of something like from books.views import search_form, because the former is less verbose.

One last thing – when Django searches for URL patterns, it will only search the base mysite\urls.py file, unless we explicitly include the URL patterns from other apps. So let’s go ahead and modify our site urlpatterns:

# mysite\urls.py

from django.conf.urls import include, url

urlpatterns = [
# ...
url(r'^', include('books.urls')),
]

This new URL pattern must be added to the end of the urlpatterns list. This is because the r'^' regex sends everything to books.urls, so we want to make sure none of the other patterns match, before sending Django to check books\urls.py for a matching pattern.

Now, if you run the development server and visit http://127.0.0.1:8000/search-form/, you’ll see the search interface (Figure 6.1). Simple enough.

Try submitting the form, though, and you’ll get a Django 404 error. The form points to the URL /search/, which hasn’t yet been implemented. Let’s fix that with a second view function:

# books/urls.py

urlpatterns = [
url(r'^search-form/
  • The HTML <form> defines a variable q. When it’s submitted, the value of q is sent via GET (method="get") to the URL /search/.
  • The Django view that handles the URL /search/ (search()) has access to the q value in request.GET.An important thing to point out here is that we explicitly check that 'q' exists in request.GET. As I pointed out in the request.META section above, you shouldn’t trust anything submitted by users or even assume that they’ve submitted anything in the first place. If we didn’t add this check, any submission of an empty form would raise KeyError in the view:
# BAD!
def bad_search(request):
# The following line will raise KeyError if 'q' hasn't been submitted!
message = 'You searched for: %r' % request.GET['q']
return HttpResponse(message)

Query String Parameters - Because GET data is passed in the query string (e.g., /search/?q=django), you can use request.GET to access query string variables. In introduction of Django’s URLconf system, I compared Django’s pretty URLs to more traditional PHP/Java URLs such as /time/plus?hours=3 and said I’d show you how to do the latter.

Now you know how to access query string parameters in your views (like hours=3 in this example) – use request.GET. POST data works the same way as GET data – just use request.POST instead of request.GET. What’s the difference between GET and POST?

Use GET when the act of submitting the form is just a request to “get” data. Use POST whenever the act of submitting the form will have some side effect – changing data, or sending an e-mail, or something else that’s beyond simple display of data. In our book search example, we’re using GET because the query doesn’t change any data on our server.

Now that we’ve verified request.GET is being passed in properly, let’s hook the user’s search query into our book database (again, in views.py):

from django.http import HttpResponse
from django.shortcuts import render
from books.models import Book

def search(request):
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
books = Book.objects.filter(title__icontains=q)
return render(request, 'books/search_results.html',
{'books': books, 'query': q})
else:
return HttpResponse('Please submit a search term.')

A couple of notes on what we did here:

  •  Aside from checking that 'q' exists in request.GET, we also make sure that request.GET['q'] is a non-empty value before passing it to the database query.
  • We’re using Book.objects.filter(title__icontains=q) to query our book table for all books whose title includes the given submission. The icontains is a lookup type, and the statement can be roughly translated as “Get the books whose title contains q, without being case-sensitive.”

This is a very simple way to do a book search. I wouldn’t recommend using a simple icontains query on a large production database, as it can be slow. (In the real world, you’d want to use a custom search system of some sort. Search the web for open-source full-text search to get an idea of the possibilities.)

We pass books, a list of Book objects, to the template. To get our new search form working, let’s create the search_results.html file:

# \books\templates\books\search_results.html

<html> 
<head> 
<title>Book Search</title> 
</head> 
<body> 
<p>You searched for: <strong>{{ query }}</strong></p>

{% if books %} 
<p>Found {{ books|length }} book{{ books|pluralize }}.</p>
<ul> 
{% for book in books %} 
<li>{{ book.title }}</li> 
{% endfor %} 
</ul> 
{% else %} 
<p>No books matched your search criteria.</p> 
{% endif %} 
</body>
</html>

Note usage of the pluralize template filter, which outputs an “s” if appropriate, based on the number of books found.

Now, when you run the development server and visit http://127.0.0.1:8000/search-form/, your search term should return a more useful result below figure.

Improving Our Simple Form-Handling Example - As in previous chapters, I’ve shown you the simplest thing that could possibly work. Now I’ll point out some problems and show you how to improve it. First, our search() view’s handling of an empty query is poor – we’re just displaying a “Please submit a search term.” message, requiring the user to hit the browser’s back button. This is horrid and unprofessional, and if you ever actually implement something like this in the wild, your Django privileges will be revoked.

It would be much better to redisplay the form, with an error above it, so that the user can try again immediately. The easiest way to do that would be to render the template again, like this:

from django.shortcuts import render
from django.http import HttpResponse
from books.models import Book

def search_form(request):
return render(request, 'books/search_form.html')

def search(request):
error = False
if 'q' in request.GET:
q = request.GET['q']
if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, 'books/search_results.html', {'books': books, 'query': q})
return render(request, 'books/search_form.html', {'error': error})


Note that I’ve included search_form() here so you can see both views in one place. Here, we’ve improved search() to render the search_form.html template again, if the query is empty. And because we need to display an error message in that template, we pass a template variable. Now we can edit search_form.html to check for the error variable:

<html>
<head>
<title>Search</title>
</head>
<body>
{% if error %}
<p style="color: red;">Please submit a search term.</p>
{% endif %}
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
</body>
</html>

We can still use this template from our original view, search_form(), because search_form() doesn’t pass error to the template – so the error message won’t show up in that case (Figure 6-4).

With this change in place, it’s a better application, but it now begs the question: is a dedicated search_form() view really necessary? As it stands, a request to the URL /search/ (without any GET parameters) will display the empty form (but with an error). We can remove the search_form() view, along with its associated URLpattern, as long as we change search() to hide the error message when somebody visits /search/ with no GET parameters:

def search(request):
error = False
if 'q' in request.GET:
q = request.GET['q'] if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, 'books/search_results.html', {'books': books, 'query': q})
return render(request, 'books/search_form.html', {'error': error})

In this updated view, if a user visits /search/ with no GET parameters, they’ll see the search form with no error message. If a user submits the form with an empty value for 'q', they’ll see the search form with an error message. And, finally, if a user submits the form with a non-empty value for 'q', they’ll see the search results.

We can make one final improvement to this application, to remove a bit of redundancy. Now that we’ve rolled the two views and URLs into one and /search/ handles both search-form display and result display, the HTML <form> in search_form.html doesn’t have to hard-code a URL. Instead of this:

<form action="/search/" method="get">

It can be changed to this:

<form action="" method="get">

The action="" means “Submit the form to the same URL as the current page.” With this change in place, you won’t have to remember to change the action if you ever hook the search() view to another URL.

, views.search_form), ]

(Note that we’re importing the views module directly, instead of something like from books.views import search_form, because the former is less verbose.

One last thing – when Django searches for URL patterns, it will only search the base mysite\urls.py file, unless we explicitly include the URL patterns from other apps. So let’s go ahead and modify our site urlpatterns:


This new URL pattern must be added to the end of the urlpatterns list. This is because the r'^' regex sends everything to books.urls, so we want to make sure none of the other patterns match, before sending Django to check books\urls.py for a matching pattern.

Now, if you run the development server and visit http://127.0.0.1:8000/search-form/, you’ll see the search interface (Figure 6.1). Simple enough.

Try submitting the form, though, and you’ll get a Django 404 error. The form points to the URL /search/, which hasn’t yet been implemented. Let’s fix that with a second view function:


  • The HTML <form> defines a variable q. When it’s submitted, the value of q is sent via GET (method="get") to the URL /search/.
  • The Django view that handles the URL /search/ (search()) has access to the q value in request.GET.An important thing to point out here is that we explicitly check that 'q' exists in request.GET. As I pointed out in the request.META section above, you shouldn’t trust anything submitted by users or even assume that they’ve submitted anything in the first place. If we didn’t add this check, any submission of an empty form would raise KeyError in the view:

Query String Parameters - Because GET data is passed in the query string (e.g., /search/?q=django), you can use request.GET to access query string variables. In introduction of Django’s URLconf system, I compared Django’s pretty URLs to more traditional PHP/Java URLs such as /time/plus?hours=3 and said I’d show you how to do the latter.

Now you know how to access query string parameters in your views (like hours=3 in this example) – use request.GET. POST data works the same way as GET data – just use request.POST instead of request.GET. What’s the difference between GET and POST?

Use GET when the act of submitting the form is just a request to “get” data. Use POST whenever the act of submitting the form will have some side effect – changing data, or sending an e-mail, or something else that’s beyond simple display of data. In our book search example, we’re using GET because the query doesn’t change any data on our server.

Now that we’ve verified request.GET is being passed in properly, let’s hook the user’s search query into our book database (again, in views.py):


A couple of notes on what we did here:

  •  Aside from checking that 'q' exists in request.GET, we also make sure that request.GET['q'] is a non-empty value before passing it to the database query.
  • We’re using Book.objects.filter(title__icontains=q) to query our book table for all books whose title includes the given submission. The icontains is a lookup type, and the statement can be roughly translated as “Get the books whose title contains q, without being case-sensitive.”

This is a very simple way to do a book search. I wouldn’t recommend using a simple icontains query on a large production database, as it can be slow. (In the real world, you’d want to use a custom search system of some sort. Search the web for open-source full-text search to get an idea of the possibilities.)

We pass books, a list of Book objects, to the template. To get our new search form working, let’s create the search_results.html file:


Note usage of the pluralize template filter, which outputs an “s” if appropriate, based on the number of books found.

Now, when you run the development server and visit http://127.0.0.1:8000/search-form/, your search term should return a more useful result below figure.

Improving Our Simple Form-Handling Example - As in previous chapters, I’ve shown you the simplest thing that could possibly work. Now I’ll point out some problems and show you how to improve it. First, our search() view’s handling of an empty query is poor – we’re just displaying a “Please submit a search term.” message, requiring the user to hit the browser’s back button. This is horrid and unprofessional, and if you ever actually implement something like this in the wild, your Django privileges will be revoked.

It would be much better to redisplay the form, with an error above it, so that the user can try again immediately. The easiest way to do that would be to render the template again, like this:


Note that I’ve included search_form() here so you can see both views in one place. Here, we’ve improved search() to render the search_form.html template again, if the query is empty. And because we need to display an error message in that template, we pass a template variable. Now we can edit search_form.html to check for the error variable:


We can still use this template from our original view, search_form(), because search_form() doesn’t pass error to the template – so the error message won’t show up in that case (Figure 6-4).

With this change in place, it’s a better application, but it now begs the question: is a dedicated search_form() view really necessary? As it stands, a request to the URL /search/ (without any GET parameters) will display the empty form (but with an error). We can remove the search_form() view, along with its associated URLpattern, as long as we change search() to hide the error message when somebody visits /search/ with no GET parameters:

def search(request):
error = False
if 'q' in request.GET:
q = request.GET['q'] if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, 'books/search_results.html', {'books': books, 'query': q})
return render(request, 'books/search_form.html', {'error': error})

In this updated view, if a user visits /search/ with no GET parameters, they’ll see the search form with no error message. If a user submits the form with an empty value for 'q', they’ll see the search form with an error message. And, finally, if a user submits the form with a non-empty value for 'q', they’ll see the search results.

We can make one final improvement to this application, to remove a bit of redundancy. Now that we’ve rolled the two views and URLs into one and /search/ handles both search-form display and result display, the HTML <form> in search_form.html doesn’t have to hard-code a URL. Instead of this:


It can be changed to this:


The action="" means “Submit the form to the same URL as the current page.” With this change in place, you won’t have to remember to change the action if you ever hook the search() view to another URL.

, views.search_form), url(r'^search/
  • The HTML <form> defines a variable q. When it’s submitted, the value of q is sent via GET (method="get") to the URL /search/.
  • The Django view that handles the URL /search/ (search()) has access to the q value in request.GET.An important thing to point out here is that we explicitly check that 'q' exists in request.GET. As I pointed out in the request.META section above, you shouldn’t trust anything submitted by users or even assume that they’ve submitted anything in the first place. If we didn’t add this check, any submission of an empty form would raise KeyError in the view:

Query String Parameters - Because GET data is passed in the query string (e.g., /search/?q=django), you can use request.GET to access query string variables. In introduction of Django’s URLconf system, I compared Django’s pretty URLs to more traditional PHP/Java URLs such as /time/plus?hours=3 and said I’d show you how to do the latter.

Now you know how to access query string parameters in your views (like hours=3 in this example) – use request.GET. POST data works the same way as GET data – just use request.POST instead of request.GET. What’s the difference between GET and POST?

Use GET when the act of submitting the form is just a request to “get” data. Use POST whenever the act of submitting the form will have some side effect – changing data, or sending an e-mail, or something else that’s beyond simple display of data. In our book search example, we’re using GET because the query doesn’t change any data on our server.

Now that we’ve verified request.GET is being passed in properly, let’s hook the user’s search query into our book database (again, in views.py):


A couple of notes on what we did here:

  •  Aside from checking that 'q' exists in request.GET, we also make sure that request.GET['q'] is a non-empty value before passing it to the database query.
  • We’re using Book.objects.filter(title__icontains=q) to query our book table for all books whose title includes the given submission. The icontains is a lookup type, and the statement can be roughly translated as “Get the books whose title contains q, without being case-sensitive.”

This is a very simple way to do a book search. I wouldn’t recommend using a simple icontains query on a large production database, as it can be slow. (In the real world, you’d want to use a custom search system of some sort. Search the web for open-source full-text search to get an idea of the possibilities.)

We pass books, a list of Book objects, to the template. To get our new search form working, let’s create the search_results.html file:


Note usage of the pluralize template filter, which outputs an “s” if appropriate, based on the number of books found.

Now, when you run the development server and visit http://127.0.0.1:8000/search-form/, your search term should return a more useful result below figure.

Improving Our Simple Form-Handling Example - As in previous chapters, I’ve shown you the simplest thing that could possibly work. Now I’ll point out some problems and show you how to improve it. First, our search() view’s handling of an empty query is poor – we’re just displaying a “Please submit a search term.” message, requiring the user to hit the browser’s back button. This is horrid and unprofessional, and if you ever actually implement something like this in the wild, your Django privileges will be revoked.

It would be much better to redisplay the form, with an error above it, so that the user can try again immediately. The easiest way to do that would be to render the template again, like this:


Note that I’ve included search_form() here so you can see both views in one place. Here, we’ve improved search() to render the search_form.html template again, if the query is empty. And because we need to display an error message in that template, we pass a template variable. Now we can edit search_form.html to check for the error variable:


We can still use this template from our original view, search_form(), because search_form() doesn’t pass error to the template – so the error message won’t show up in that case (Figure 6-4).

With this change in place, it’s a better application, but it now begs the question: is a dedicated search_form() view really necessary? As it stands, a request to the URL /search/ (without any GET parameters) will display the empty form (but with an error). We can remove the search_form() view, along with its associated URLpattern, as long as we change search() to hide the error message when somebody visits /search/ with no GET parameters:

def search(request):
error = False
if 'q' in request.GET:
q = request.GET['q'] if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, 'books/search_results.html', {'books': books, 'query': q})
return render(request, 'books/search_form.html', {'error': error})

In this updated view, if a user visits /search/ with no GET parameters, they’ll see the search form with no error message. If a user submits the form with an empty value for 'q', they’ll see the search form with an error message. And, finally, if a user submits the form with a non-empty value for 'q', they’ll see the search results.

We can make one final improvement to this application, to remove a bit of redundancy. Now that we’ve rolled the two views and URLs into one and /search/ handles both search-form display and result display, the HTML <form> in search_form.html doesn’t have to hard-code a URL. Instead of this:


It can be changed to this:


The action="" means “Submit the form to the same URL as the current page.” With this change in place, you won’t have to remember to change the action if you ever hook the search() view to another URL.

, views.search_form), ]

(Note that we’re importing the views module directly, instead of something like from books.views import search_form, because the former is less verbose.

One last thing – when Django searches for URL patterns, it will only search the base mysite\urls.py file, unless we explicitly include the URL patterns from other apps. So let’s go ahead and modify our site urlpatterns:


This new URL pattern must be added to the end of the urlpatterns list. This is because the r’^’ regex sends everything to books.urls, so we want to make sure none of the other patterns match, before sending Django to check books\urls.py for a matching pattern.

Now, if you run the development server and visit http://127.0.0.1:8000/search-form/, you’ll see the search interface (Figure 6.1). Simple enough.

Try submitting the form, though, and you’ll get a Django 404 error. The form points to the URL /search/, which hasn’t yet been implemented. Let’s fix that with a second view function:


  • The HTML <form> defines a variable q. When it’s submitted, the value of q is sent via GET (method=”get”) to the URL /search/.
  • The Django view that handles the URL /search/ (search()) has access to the q value in request.GET.An important thing to point out here is that we explicitly check that ‘q’ exists in request.GET. As I pointed out in the request.META section above, you shouldn’t trust anything submitted by users or even assume that they’ve submitted anything in the first place. If we didn’t add this check, any submission of an empty form would raise KeyError in the view:

Query String Parameters – Because GET data is passed in the query string (e.g., /search/?q=django), you can use request.GET to access query string variables. In introduction of Django’s URLconf system, I compared Django’s pretty URLs to more traditional PHP/Java URLs such as /time/plus?hours=3 and said I’d show you how to do the latter.

Now you know how to access query string parameters in your views (like hours=3 in this example) – use request.GET. POST data works the same way as GET data – just use request.POST instead of request.GET. What’s the difference between GET and POST?

Use GET when the act of submitting the form is just a request to “get” data. Use POST whenever the act of submitting the form will have some side effect – changing data, or sending an e-mail, or something else that’s beyond simple display of data. In our book search example, we’re using GET because the query doesn’t change any data on our server.

Now that we’ve verified request.GET is being passed in properly, let’s hook the user’s search query into our book database (again, in views.py):


A couple of notes on what we did here:

  •  Aside from checking that ‘q’ exists in request.GET, we also make sure that request.GET[‘q’] is a non-empty value before passing it to the database query.
  • We’re using Book.objects.filter(title__icontains=q) to query our book table for all books whose title includes the given submission. The icontains is a lookup type, and the statement can be roughly translated as “Get the books whose title contains q, without being case-sensitive.”

This is a very simple way to do a book search. I wouldn’t recommend using a simple icontains query on a large production database, as it can be slow. (In the real world, you’d want to use a custom search system of some sort. Search the web for open-source full-text search to get an idea of the possibilities.)

We pass books, a list of Book objects, to the template. To get our new search form working, let’s create the search_results.html file:


Note usage of the pluralize template filter, which outputs an “s” if appropriate, based on the number of books found.

Now, when you run the development server and visit http://127.0.0.1:8000/search-form/, your search term should return a more useful result below figure.

Improving Our Simple Form-Handling Example – As in previous chapters, I’ve shown you the simplest thing that could possibly work. Now I’ll point out some problems and show you how to improve it. First, our search() view’s handling of an empty query is poor – we’re just displaying a “Please submit a search term.” message, requiring the user to hit the browser’s back button. This is horrid and unprofessional, and if you ever actually implement something like this in the wild, your Django privileges will be revoked.

It would be much better to redisplay the form, with an error above it, so that the user can try again immediately. The easiest way to do that would be to render the template again, like this:


Note that I’ve included search_form() here so you can see both views in one place. Here, we’ve improved search() to render the search_form.html template again, if the query is empty. And because we need to display an error message in that template, we pass a template variable. Now we can edit search_form.html to check for the error variable:


We can still use this template from our original view, search_form(), because search_form() doesn’t pass error to the template – so the error message won’t show up in that case (Figure 6-4).

With this change in place, it’s a better application, but it now begs the question: is a dedicated search_form() view really necessary? As it stands, a request to the URL /search/ (without any GET parameters) will display the empty form (but with an error). We can remove the search_form() view, along with its associated URLpattern, as long as we change search() to hide the error message when somebody visits /search/ with no GET parameters:

def search(request):
error = False
if ‘q’ in request.GET:
q = request.GET[‘q’] if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, ‘books/search_results.html’, {‘books’: books, ‘query’: q})
return render(request, ‘books/search_form.html’, {‘error’: error})

In this updated view, if a user visits /search/ with no GET parameters, they’ll see the search form with no error message. If a user submits the form with an empty value for ‘q’, they’ll see the search form with an error message. And, finally, if a user submits the form with a non-empty value for ‘q’, they’ll see the search results.

We can make one final improvement to this application, to remove a bit of redundancy. Now that we’ve rolled the two views and URLs into one and /search/ handles both search-form display and result display, the HTML <form> in search_form.html doesn’t have to hard-code a URL. Instead of this:


It can be changed to this:


The action=”” means “Submit the form to the same URL as the current page.” With this change in place, you won’t have to remember to change the action if you ever hook the search() view to another URL.

, views.search), ] # books/views.py from django.http import HttpResponse # … def search(request): if ‘q’ in request.GET: message = ‘You searched for: %r’ % request.GET[‘q’] else: message = ‘You submitted an empty form.’ return HttpResponse(message) For the moment, this merely displays the user’s search term, so we can make sure the data is being submitted to Django properly, and so you can get a feel for how the search term flows through the system (Figure 6-2). in short
  • The HTML <form> defines a variable q. When it’s submitted, the value of q is sent via GET (method=”get”) to the URL /search/.
  • The Django view that handles the URL /search/ (search()) has access to the q value in request.GET.An important thing to point out here is that we explicitly check that ‘q’ exists in request.GET. As I pointed out in the request.META section above, you shouldn’t trust anything submitted by users or even assume that they’ve submitted anything in the first place. If we didn’t add this check, any submission of an empty form would raise KeyError in the view:

Query String Parameters – Because GET data is passed in the query string (e.g., /search/?q=django), you can use request.GET to access query string variables. In introduction of Django’s URLconf system, I compared Django’s pretty URLs to more traditional PHP/Java URLs such as /time/plus?hours=3 and said I’d show you how to do the latter.

Now you know how to access query string parameters in your views (like hours=3 in this example) – use request.GET. POST data works the same way as GET data – just use request.POST instead of request.GET. What’s the difference between GET and POST?

Use GET when the act of submitting the form is just a request to “get” data. Use POST whenever the act of submitting the form will have some side effect – changing data, or sending an e-mail, or something else that’s beyond simple display of data. In our book search example, we’re using GET because the query doesn’t change any data on our server.

Now that we’ve verified request.GET is being passed in properly, let’s hook the user’s search query into our book database (again, in views.py):


A couple of notes on what we did here:

  •  Aside from checking that ‘q’ exists in request.GET, we also make sure that request.GET[‘q’] is a non-empty value before passing it to the database query.
  • We’re using Book.objects.filter(title__icontains=q) to query our book table for all books whose title includes the given submission. The icontains is a lookup type, and the statement can be roughly translated as “Get the books whose title contains q, without being case-sensitive.”

This is a very simple way to do a book search. I wouldn’t recommend using a simple icontains query on a large production database, as it can be slow. (In the real world, you’d want to use a custom search system of some sort. Search the web for open-source full-text search to get an idea of the possibilities.)

We pass books, a list of Book objects, to the template. To get our new search form working, let’s create the search_results.html file:


Note usage of the pluralize template filter, which outputs an “s” if appropriate, based on the number of books found.

Now, when you run the development server and visit http://127.0.0.1:8000/search-form/, your search term should return a more useful result below figure.

Improving Our Simple Form-Handling Example – As in previous chapters, I’ve shown you the simplest thing that could possibly work. Now I’ll point out some problems and show you how to improve it. First, our search() view’s handling of an empty query is poor – we’re just displaying a “Please submit a search term.” message, requiring the user to hit the browser’s back button. This is horrid and unprofessional, and if you ever actually implement something like this in the wild, your Django privileges will be revoked.

It would be much better to redisplay the form, with an error above it, so that the user can try again immediately. The easiest way to do that would be to render the template again, like this:


Note that I’ve included search_form() here so you can see both views in one place. Here, we’ve improved search() to render the search_form.html template again, if the query is empty. And because we need to display an error message in that template, we pass a template variable. Now we can edit search_form.html to check for the error variable:


We can still use this template from our original view, search_form(), because search_form() doesn’t pass error to the template – so the error message won’t show up in that case (Figure 6-4).

With this change in place, it’s a better application, but it now begs the question: is a dedicated search_form() view really necessary? As it stands, a request to the URL /search/ (without any GET parameters) will display the empty form (but with an error). We can remove the search_form() view, along with its associated URLpattern, as long as we change search() to hide the error message when somebody visits /search/ with no GET parameters:

def search(request):
error = False
if ‘q’ in request.GET:
q = request.GET[‘q’] if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, ‘books/search_results.html’, {‘books’: books, ‘query’: q})
return render(request, ‘books/search_form.html’, {‘error’: error})

In this updated view, if a user visits /search/ with no GET parameters, they’ll see the search form with no error message. If a user submits the form with an empty value for ‘q’, they’ll see the search form with an error message. And, finally, if a user submits the form with a non-empty value for ‘q’, they’ll see the search results.

We can make one final improvement to this application, to remove a bit of redundancy. Now that we’ve rolled the two views and URLs into one and /search/ handles both search-form display and result display, the HTML <form> in search_form.html doesn’t have to hard-code a URL. Instead of this:


It can be changed to this:


The action=”” means “Submit the form to the same URL as the current page.” With this change in place, you won’t have to remember to change the action if you ever hook the search() view to another URL.

, views.search_form), ]

(Note that we’re importing the views module directly, instead of something like from books.views import search_form, because the former is less verbose.

One last thing – when Django searches for URL patterns, it will only search the base mysite\urls.py file, unless we explicitly include the URL patterns from other apps. So let’s go ahead and modify our site urlpatterns:


This new URL pattern must be added to the end of the urlpatterns list. This is because the r’^’ regex sends everything to books.urls, so we want to make sure none of the other patterns match, before sending Django to check books\urls.py for a matching pattern.

Now, if you run the development server and visit http://127.0.0.1:8000/search-form/, you’ll see the search interface (Figure 6.1). Simple enough.

Try submitting the form, though, and you’ll get a Django 404 error. The form points to the URL /search/, which hasn’t yet been implemented. Let’s fix that with a second view function:


  • The HTML <form> defines a variable q. When it’s submitted, the value of q is sent via GET (method=”get”) to the URL /search/.
  • The Django view that handles the URL /search/ (search()) has access to the q value in request.GET.An important thing to point out here is that we explicitly check that ‘q’ exists in request.GET. As I pointed out in the request.META section above, you shouldn’t trust anything submitted by users or even assume that they’ve submitted anything in the first place. If we didn’t add this check, any submission of an empty form would raise KeyError in the view:

Query String Parameters – Because GET data is passed in the query string (e.g., /search/?q=django), you can use request.GET to access query string variables. In introduction of Django’s URLconf system, I compared Django’s pretty URLs to more traditional PHP/Java URLs such as /time/plus?hours=3 and said I’d show you how to do the latter.

Now you know how to access query string parameters in your views (like hours=3 in this example) – use request.GET. POST data works the same way as GET data – just use request.POST instead of request.GET. What’s the difference between GET and POST?

Use GET when the act of submitting the form is just a request to “get” data. Use POST whenever the act of submitting the form will have some side effect – changing data, or sending an e-mail, or something else that’s beyond simple display of data. In our book search example, we’re using GET because the query doesn’t change any data on our server.

Now that we’ve verified request.GET is being passed in properly, let’s hook the user’s search query into our book database (again, in views.py):


A couple of notes on what we did here:

  •  Aside from checking that ‘q’ exists in request.GET, we also make sure that request.GET[‘q’] is a non-empty value before passing it to the database query.
  • We’re using Book.objects.filter(title__icontains=q) to query our book table for all books whose title includes the given submission. The icontains is a lookup type, and the statement can be roughly translated as “Get the books whose title contains q, without being case-sensitive.”

This is a very simple way to do a book search. I wouldn’t recommend using a simple icontains query on a large production database, as it can be slow. (In the real world, you’d want to use a custom search system of some sort. Search the web for open-source full-text search to get an idea of the possibilities.)

We pass books, a list of Book objects, to the template. To get our new search form working, let’s create the search_results.html file:


Note usage of the pluralize template filter, which outputs an “s” if appropriate, based on the number of books found.

Now, when you run the development server and visit http://127.0.0.1:8000/search-form/, your search term should return a more useful result below figure.

Improving Our Simple Form-Handling Example – As in previous chapters, I’ve shown you the simplest thing that could possibly work. Now I’ll point out some problems and show you how to improve it. First, our search() view’s handling of an empty query is poor – we’re just displaying a “Please submit a search term.” message, requiring the user to hit the browser’s back button. This is horrid and unprofessional, and if you ever actually implement something like this in the wild, your Django privileges will be revoked.

It would be much better to redisplay the form, with an error above it, so that the user can try again immediately. The easiest way to do that would be to render the template again, like this:


Note that I’ve included search_form() here so you can see both views in one place. Here, we’ve improved search() to render the search_form.html template again, if the query is empty. And because we need to display an error message in that template, we pass a template variable. Now we can edit search_form.html to check for the error variable:


We can still use this template from our original view, search_form(), because search_form() doesn’t pass error to the template – so the error message won’t show up in that case (Figure 6-4).

With this change in place, it’s a better application, but it now begs the question: is a dedicated search_form() view really necessary? As it stands, a request to the URL /search/ (without any GET parameters) will display the empty form (but with an error). We can remove the search_form() view, along with its associated URLpattern, as long as we change search() to hide the error message when somebody visits /search/ with no GET parameters:

def search(request):
error = False
if ‘q’ in request.GET:
q = request.GET[‘q’] if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render(request, ‘books/search_results.html’, {‘books’: books, ‘query’: q})
return render(request, ‘books/search_form.html’, {‘error’: error})

In this updated view, if a user visits /search/ with no GET parameters, they’ll see the search form with no error message. If a user submits the form with an empty value for ‘q’, they’ll see the search form with an error message. And, finally, if a user submits the form with a non-empty value for ‘q’, they’ll see the search results.

We can make one final improvement to this application, to remove a bit of redundancy. Now that we’ve rolled the two views and URLs into one and /search/ handles both search-form display and result display, the HTML <form> in search_form.html doesn’t have to hard-code a URL. Instead of this:


It can be changed to this:


The action=”” means “Submit the form to the same URL as the current page.” With this change in place, you won’t have to remember to change the action if you ever hook the search() view to another URL.

Back to Tutorial

Form Processing
The “Perfect Form”

Get industry recognized certification – Contact us

keyboard_arrow_up
Open chat
Need help?
Hello 👋
Can we help you?