Using Django templates

Standard

In this new entry, we will go one step further in our Simpsons Quote database. In the last entry, we achieved some degree of interaction with the application. But in doing so, we mixed up HTML code with Python and crippled the security in Django. In this entry, we will solve both problems by introducing the use of HTML templates

Of course, in our normal workflow we will not disable any security middleware and we will use templates from the beginning. The use of templates is part of the MVC pattern. Specifically, templates conform the MVC View part; but remember that in Django, a view is actually an MVC Controller. But that is not relevant for us.

So we start by reactivating CSRF protection in file SQProject/settings.py:

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

In the last entry, we created a view in SQApp/views.py called quotesearch that used a variable htmlcode that contained the HTML code for the initial search prompt shown to the user when accessing the URL. We defined htmlcode outside of quotesearch because it was very long, but we defined the HTML code for the other responses inside of quotesearch. One way or the other, we are hardcoding HTML code in Python files. Fortunately, Django deals with this, with the use of templates. A template is an HTML (or XML, JSON …) file with specially marked variables that will be filled (rendered) by the Python views and returned to the client in the response. This permits us developing the format of the responses separately of the code that processes the data. Moreover, this also lets Django add special security features on the HTML response automatically.

We will create one HTML template per response. For that, we create a new directory: SQapp/templates/. In that directory we create three HTML files: one file for the search form, one for the results, and one for the error messages.

The search form will only contain the contents of variable htmlcode:

<!-- File: SQapp/templates/searchform.html -->
<html>
	<head>
		<title>Search Simpsons quote</title>
	</head>
	<body>
		<form action='quotesearch' method='POST'>
			{% csrf_token %}
			<input type='text' name='terms' /> <input type='submit' value='Search' />
		</form>
	</body>
</html>

We must add the {% csrf_token %} tag when using a POST in order to prevent CSRF attacks. Django will automatically deal with all the details, and there is no additional action to take.

The code for the error messages contains a single variable message that will be filled by the view. In the Django template language, a variable is defined by its name and surrounded by two braces. The value filled by the view replaces all the characters from the first brace to the last one.

<!-- File: SQapp/templates/error.html -->
<html>
	<head>
		<title>Error</title>
	</head>
	<body>
		<p>Error: {{message}}</p>
	</body>
</html>

The code for the search results is a little bit more tricky, because an undisclosed number of variables is needed. We must fill several variables for each result. Fortunately, Django lets us use template tags, that add some processing power to the template. What we want our template to do is to process a set of results and automatically add a p HTML element with the fields of each result appropriately organized. Therefore, we must use the for template tag and use the syntax for accessing fields of variables:

<!-- File: SQapp/templates/searchresults.html -->
<html>
	<head>
		<title>Search results</title>
	</head>
	<body>
		{% for quote in results %}
		<p>"{{quote.content}}" {{quote.character}} (S{{quote.season}}E{{quote.episode}})</p>
		{% endfor %}
	</body>
</html>

Once we have defined the templates, we must rewrite our view in order to use them:

def quotesearch(request):
	if request.method == 'GET':
		# First user request
		return render(request, 'searchform.html')
	else:
		# Second request
		searchterms = request.POST['terms'].split()
		if len(searchterms) >= 1:
			searchresults = SimpsonQuote.objects.filter(content__icontains = searchterms[0])
			for additional_term in searchterms[1:]: # Filter by any additional terms
				searchresults = searchresults.filter(content__icontains = additional_term)
			if len(searchresults) > 0:
				return render(request, 'searchresults.html', {'results':searchresults})
			else:
				return render(request, 'error.html', {'message':'No results found'})
		else:
			# Do some error handling here if the client sends an empty request
			return render(request, 'error.html', {'message':'Please enter some search terms'})

We use a new function render, that we must previously import (from django.shortcuts import render), that takes the request, a filename of a template stored in the templates subdirectory of the app, and a dictionary that has the variable names in the template as keys and the values as the variables of the view that we want to return to the client. The first call to render uses a template that has no variables, so we do not pass a dictionary argument. The second call, contains the results as a set of entries in our database, mapped to the variable results of the template searchresults.html. We can see that the tedious code for processing has been moved to the template, leaving a clean view: we got the results, we map them to a variable in the template and return them to the user. The use of the error template is similar.

Notice that the CSRF middleware transparently deals with the problem, so we do not have to take that into account. We can now just execute the code without additional changes (since we have overwritten quotesearch, and it has already been mapped to a URL), and see that the results are identical to those of the last entry, only with a much more organized (and safe) code. We can now play with the HTML files in order to decorate it as we wish, or change how the results are shown; and we can do it without changing a single Python line. In fact, we can also do the reverse: create a sample of the results page we want, and then convert it to a template. Summarizing, we can now introduce web design in Django. But more than that, we can even change the type of result we return to the users. To serve data as a data base, for instance, we will want to return a JSON document. More on that in the next entry.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.