13 Templates: Best Practices
One of Django's early design decisions was to limit the functionality of the template language. This heavily constrains what can be done with Django templates, which we often think is a very good thing since it forces us to keep business logic in the Python side of things.
Think about it: the limitations fo Django tmepaltes force us to put the most critical, complex and detailed parts of our project info .py files rather than into template files. Python happens to be one of the most clear, concise, elegant programming languages of the planet, so why would we want things any other way?
TIP: Using Jinja2 With Django
Starting with 1.8 Django natively supports Jinja2, as well as providing a interface for including other template languages. We cover this topic in chapter 15, Using Alternate Template Systems.
13.1 Keep Templates Mostly in templates/
In our projects, we keep the majority of our templates in the main 'templates/' directory. We put subdirectories in 'templates/' to correspond to each of our apps, as shown here:
EXAMPLE 13.1
templates/
base.html
... (other sitewide templates in here)
freezers/
("freezers" app templates in here)
However, some tutorials advocate putting templates within a subdirectory of each app. We find that the extra nesting is a pain to deal with, as shown here:
BAD EXAMPLE 13.1
freezers/
templates/
freezers/
... ("freezers" app templates in here)
templates/
base.html
... (other sitewide templates in here)
That said, some people like to do in the second way, and that's alright.
The exception to all of this is when we work with Django apps that are installed as pluggable packages. A Django package usually contains its own in-app 'templates/' directory. Then we override those templates anyway from our project's main 'templates/' directory in order to add design and styling.
13.2 Template Architecture Patterns
We've found that for our purposes, simple 2-tier or 3-tier template architectures are ideal. The difference in tiers is how many levels of template exteding needs to occur before content in apps is displayed. See the exmples below:
13.2.1 2-Tier Template Architecure Example
With a 2-tier template architecture, all template inheri from a single root base.html file.
EXAMPLE 13.2
templates/
base.html
dashboard.html # extends base.html
profiles/
profile_detail.html # extends base.html
profile_form.html # extends base.html
This is best for sites with a consistent overall layout from app to app.
13.2.2 3-Tier Template Architecture Example
With a 3-tier template architecture:
* Each app has a base_<app_name>.html template. App-level base templates share a common parent base.html template.
* Templates within apps share a common parent base_<app_name>.html template.
* Any template at the same level as base.html inherits base.html.
EXAMPLE 13.3
templates/
base.html
dashboard.html # extends base.html
profiles/
base_profiles.html # extends bse.html
profile_detail.html # extends base_profiles.html
profile_form.html # extends bse_profiles.html
The 3-tier architecture is best for websites where each section requires a distinctive layout. For example, a news site might have a local news section, a classified ads section, and an events section. Each of these sections requires its own custom layout.
This is extremely useful when we want HTML to look or behave differently for a particular section of the site that groups functionality.
13.2.3 Flat Is Better Than Nested
Complex template hierarchies make it exceedingly difficult to debug, modify, and extend HTML pages and tie in CSS styles. When template bock layouts become unnecessarily nested, you end up digging through file after file just to change, say, the width of a box.
Giving your template blocks as shallow an inheritance structure as possible will make your templates easier to work with and more maintainable. If you're working with a designer, your designer will thank you.
That being said, there's a difference between excessively-complex template block hierarchies and templates that use blocks wisely for code reuse. When you have large, multi-line chunks of the same or very similar code in separete templates, refactoring that code into reusable blocks will make your code more maintainale.
Then Zen of Python includes the aphorism "Flat is better than nested" for good reason. Each level of nesting adds mental overhead. Keep that in mind when architecting your Django templates.
TIP: The Zen of Python
At the command line, do the following:
python -c 'import this'
What you'll see is the Zen of Python, an eloquently-expressed set of guiding principles for the design of the Python programming language.
13.3 Limit Processing in Templates
The less processing you try to do in your templates, the better. This is particularly a problem when it comes to queries and iteration performed in the template layer.
Whenever you iterate over a queryset in a template, ask yourself the following questions:
- How large is the queryset? Looping over gigantic querysets in your templates is almost always a bad idea.
- How large are the objects being retrieved? Are all the fileds needed in this template?
- During each iteration of the loop, how much processing occurs?
If any warning bells go off in your head, then there's probably a btter way to rewrite your template code.
WARNGIN: Why Not Just Cache?
Sometimes you an just cache away your template inefficiencies. That's fine, but before you cache, you should first try to attack the root of the problem. You can save yoyurself a lot of work by metally tracing through your template code, doing some quick run time analysis, and refactoring.
Let's now explore some examples of template code that can be rewritten more effieiently.
Suspend your disbelief ofr a moment and pretned that the butty duo behind Two Scoops ran a 30 second commercial during the Super Bowl. "Free pints of ice cream for the first milloion developers who request them! All you have to do is fill out a form to get a voucher redeemable in stores!"
Naturally, we have a "vouchers" app to track the names and eamil addresses of everyone who requested a free pint voucher. Here's what the model for this app looks like:
EXAMPLE 13.4
# vouchers/models.py
from django.core.urlresolvers import reverse
from django.db import models
from .managers import VoucherManager
class Voucher(models.Model):
"""Vouchers for free pints of ice cream."""
name = models.CharField(max_length=100)
email = models.EmailField()
address = models.TextField()
birth_date = models.DateField(blank=True)
sent = models.BooleanField(default=True)
redeemed = models.BooleanField(default=False)
objects = VoucherManager()
This model will be uesd in the following exmaples to illustrate a few "gotchas" that you should avoid.
13.3.1 Gotcha 1: Aggregation in Templates
Since we have birth date information, it would be interesting to diplay a rough breakdown by age range of voucher requests and redemptions.
A very bad way to implement this would be to do all the processing at the template level. To be more specific in the context of this example:
* Don't iterate over the entire voucher list in your template's JavaScript sectin, using JavaScript variables to hold age range counts.
* Don't use the add template filter to sum up the voucher counts.
Those implementations are ways of getting around Django's limitations of logic in templates, but they'll slow down your pages drastically.
The better way is to move this processing out of your temlate and into your Python code. Sticking to our minmal approach of using templates only to display data that has already been processed, our template looks like this:
EXAMPLE 13.5
{# temlates/vouchers/ages.html #}
{% extends "base.html" %}
{% block content %}
<table>
<thread>
<tr>
<th>Age Bracket</th>
<th>Number of vouchers Issued</th>
</tr>
</thread>
<tbody>
{% for age_bracket in age_brackets %}
<tr>
<td>{{ age_bracket.title }}</td>
<td>{{ age_bracket.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content%}
In this example, we can do the processing with a model manager, using the Django ORM's aggregation methods and the handy dateutil in Appendix A: Packages Mentioned In This Book:
EXAMPLE 13.6
# vouchers/managers.py
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from django.db import models
class VoucherManager(model.Manager):
def age_breakdown(self):
"""Returns a dict of age brackets/counts."""
age_brackets = []
now = timezone.now()
delta = now - relativedelta(years=18)
count = self.model.objects.filter(birth_date__gt=delta).count()
age_brackets.append(
{"title": "0-17", "": count}
)
count = self.model.objects.filter(birth_date__lte=delta).count()
age_brackets.append(
{"title": "18+", "count": count}
)
return age_brackets
This method would be called from a view, and the results would be passed to hte template as a context variable.
13.3.2 Gotcha 2: Filtering With Conditionals in Templates
Suppose we want to dispaly a list of all the Greenfelds and the Roys who requested free pint vouchers, so that we could invite them to our family reunion. We want to filter our records on the name field. A very bad wy to implement this would be with giant loops and if statements at the template level.
BAD EXAMPLE 13.2
<h2>Greenfilds Who Want Ice Cream</h2>
<ul>
{% for voucher in voucher_list %}
{# Don't do this: conditional filtering in templates #}
{% if "greenfeld" in voucher.name.lower %}
<li>{{ voucher.name }}</li>
{% endif %}
{% endfor %}
</ul>
<h2>Roys Who Want Ice Cream</h2>
<ul>
{% for voucher in voucher_list %}
{# Don't do this: conditional filtering in templates #}
{% if "roy" in voucher.name.lower %}
<li>{{ voucher.name }}</li>
{% endif %}
{% endfor %}
</ul>
In this bad sinppet, we're looping and checking for various "if" conditions. That's filtering a potentially gigantic list of records in templates, which is not designed for this kind of work, and will cause performance bottlenecks. On the other hand, databases like PostgreSQL and MySQL are great at filtering records, so this should be done at the datbase layer. The Django ORM can help us with this as demonstrated in the next example.
EXAMPLE 13.7
# vouchers/views.py
from django.views.generic import TemplateView
from .models import Voucher
class GreenfildRoyView(TemplateView):
template_name = "vouchers/views_conditional.html"
def get_context_data():
context = super(GreenfeldRoyView, self).get_context_data(**kwargs)
context["greenfelds"] = Voucher.objects.filter(name__icontains="greenfeld")
context["roys"] = Voucher.objects.filter(name_icontains="roy")
return context
Then to call the results, we use the following, simpler template:
EXAMPLE 13.8
<h2>Greenfelds Who Want Ice Cream</h2>
<ul>
{% for voucher in greenfelds %}
<li>{{ voucher.name }}</li>
{% endfor %}
</ul>
<h2>Roys Who Want Ice Cream</h2>
<ul>
{% for voucher in roys %}
<li>{{ voucher.name }}</li>
{% endfor %}
</ul>
It's easy to speed up this template by moving the filtering to a model manager. With this change, we now simply use the template to display the already-filterd data.
The abuve template now follows our preferred minimalist approach.
13.3.3 Gotcha 3: Complex Implied Queries in Templates
Despite the limitations on logic allowed in Django templates, it's all too easy to find ourselves calling unnecessary queries repeatedly in a view. For example, if we list users of our site and all their flavors this way:
BAD EXAMPLE 13.3
Ice Cream Fans and their favorite flavors.
Then calling each user generates a second query. While that might not seem like much, we are certain that if we had enough users and made this mistake frequently enough , our site would have a lot of trouble.
One quick correction is to use the Django ORM's select_related() method:
EXAMPLE 13.9
{% comment %}
List generated via User.objects.all().select_related("flavors")
{% endcomment %}
<h1>Ice Cream Fans and their favorite flavors.</h1>
<ul>
{% for user in user_list %}
<li>
{{ user.name }}
{{ user.flavor.title }}
{{ user.flavor.scoops_remaining }}
</li>
{% endfor %}
}
</ul>
One more thing: If you've embraced using model methods, the same applies. Be cautious putting too much query logic in the model methods called from templates.
13.3.4 Gotcha 4: Hidden CPU Load in Templates
Watch out for innocent-looking calls in templates that result in intensive CPU processing. Although a template might look simple and contain very little code, a single line could be invoking an object method that does a lot of processing.
Common examples are template tags that manipulate images, such as the template tags provides by libraries like sorl-thumbnail. In many cases tools like this work great, but we've had some issues. Sepcifically, the manipulation and the saving of image data to file systems (often across networks) inside a template means there is a choke point within templates.
This is why projects that handle a lot of image or data processing increase the performance of their site by taking the image processing out of templates and into views, models, helper method, or saynchronous messages queues like Celery.
13.3.5 Gotcha 5: Hidden REST API Calls in Templates
You saw in the previous gotcha how easy it is to introduce template loading delays by accessing object method calls. This is ture not just with high-load methods, but also with methods that contain REST API calls. A good example is querying an unfortunately slow maps. API hosted by a third-pirty sevice that your project absolutely requires. Don't do this in the template code by calling a method attached to an object passed into the view's context.
Where should actual REST API consumption occur? We recommend doing this in:
* JavaScript code so after your project serves out its content, the client's browser handles the work. This way you can entertain or distract the client while they wait for data to load.
* The view's Python code where slow processes might be handled in a variety of ways including message queues, additional threads, multiprocesses, or more.
13.4 Don't Bother Making Your Generated HTML Pretty
Bluntly put, no one cares if the HTML generated by your Django project is attractive. In fact, if someone were to look at your rendered HTML, they'd do so through the lens of a browser inspector, which would realign the HTML spacing anyway. Therefore, if you shuffle up the code in your Django templates to render pretty HTML, you are wasting time obfuscating your code for an audience of yourself.
And yet, we've seen code like the following. This evil code snippet generates nicely formatted HTML but itself is an illegible, unmaintainable template mess:
BAD EXAMPLE
{% comment %}Don't do this! This code bunches everything together to generate pretty HTML.
{% endcomment %}
{% if list_type=="unordered" %}<ul>{% else %}<ol>{% endif %}{% for syrup in syrup_list %}<li class="{{ syrup.temperature_type|roomtemp }}"><a href="{% url 'syrup_detail' syrup.slug %}">{% syrup.title %} </a></li>{% endfor %}{% if list_type=="unordered" %}</ul>{% else %} </ol>{% endif %}
A better way of writing the above snippet is to use indentation and one operation per line to create a readable, maintainable template:
EXAMPLE 13.10
{# Use indentation/comments to ensure code quality #}
{# start of list element #}
{% if list_type=='unordered' %}
<ul>
{% else %}
<ol>
{% endif %}
{% for syrup in syrup_list %}
<li class="{{ syrup.temperature_type|roomtemp }}">
<a href="{% url 'syrup_detail' syrup.slug %}">
{% syrup.title %}
</a>
</li>
{% endfor %}
{# end of list elements #}
{% if list_type=='unordered' %}
</ul>
{% else%}
</ol>
{% endif %}
Are you worried about the volume of whitespace generate? Don't be. First of all, experienced developers favor readablility of code over obfuscation for the sake of optimization. Second, there are compression and minification tools that can help more than anything you can do manually here. See chapter 24, Finding and Reducing Bottlenecks, for more details.
13.5 Exploring Template Inheritance
Let's begin with a simple base.html file that we'll inherit from another template:
EXAMPLE 13.11
{# simple base.html #}
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title>
{% block title %}Two Scoops of Django{% endblock title %}
</title>
{% block stylesheets %}
<link rel="stylesheet" type="text/css"
href="{% static "css/project.css" %}">
{% endblock stylesheets %}
</head>
<body>
<div class="content">
{% block content %}
<h1>Two Scoops</h1>
{% endblock content %}
</div>
</body>
</html>
The base.html file contains the following features:
- A title block containing "Two Scoops of Django"
- A stylesheets block containing a link to a prject.css file userd across our site.
- A content block containing
"<h1>Two Scoops</h1>"
Our example relies on just three template tags, which are summarized below:
Template Tag | Purpose |
---|---|
{% load %} | Loads the staticfiles built-in template tag library |
{% block %} | Since base.html is a parent template, these define which child blocks can be filled in by child templates. We place links and scripts inside them so we can override if necessary. |
{% static %} | Resolves the named staic media argument to the static media server |
Table 13.1: Template Tags in base.html
To demonstrate base.html in use, we'll have a simple about.html inherit the following from it:
- A custom title.
- The original stylesheet and an additional stylesheet.
- The original header, a sub header, and paragraph content.
- The use of child block.
- The use of the template variable.
EXAMPLE 13.12
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}About Audrey and Daniel{% endblock title %}
{% block stylesheets %}
{{ block.super }}
<link rel="stylesheet" type="text/css"
href="{% static "css/about.css" %}">
{% endblock stylesheets %}
{% block content %}
{{ block.super }}
<h2>About Audray and Daniel</h2>
<p>They enjoy eating ice cream</p>
{% endblock content %}
When we render this template in a view, it generates the following HTML:
EXAMPLE 13.13
<!DOCTYPE html>
<html>
<head>
<title>
About Audray and Daniel
</title>
<link rel="stylesheet" type="text/css"
href="/static/css/project.css">
<link rel="stylesheet" type="text/css"
href="/static/scc/about.css">
</head>
<body>
<div class="content">
<h1>Two Scoops</h1>
<h2>About Audrey and Daniel</h2>
<p>They enjoy eating ice cream</p>
</div>
</body>
</html>
Notice how the rendered HTML has our custom title, the additional sytlesheet link, and more material in the body? We'll use the table below to review the template tags and variables in the about.html template.
Template Tag | Purpose |
---|---|
{% extend %} | Informs Django that about.html is inheriting or extending from base.html |
{% block %} | Since about.html is a child template, block overrides the content provided by base.html. This means our title will render as |
{% block.super %} | When placed in a child template's block, it ensures that the parent's content is also included in the block. In the content block of the about.html template, this will render <h1>Two Scoops</h1> . |
Table 13.2: Template Objects in about.html
Note that the {% block %} tag is used differently in about.html than in base.html, serving to override content. In blocks where we want to preseve the base.html content, we use {{ block.super }} variable to display the content from the parent block. This brings us to the next topic, {{ block.super }}
13.6 block.super Gives the Power of Control
Let's imagine that we have a template which inherits everything from the base.html but replaces the projects's link to the project.css file with a link to dashboard.css. This use case might occur when you have a project with one design for normal users, and a dashboard with a different design for staff.
If we aren't using , this often involves writing a whole new base file, often named something like base_dashboard.html. For better or for worse, we now have two template architectures to maintain.
If we are using , we don't need a second (or third or fourth) base template. Assuming all templates extend from base.html we use to assume control of our templates. Here are three examples:
Template using both project.css and a custom link:
EXAMPLE 13.14
{% extends "base.html" %}
{% block stylesheets %}
{{ block.super }} {# this brings in project.css #}
<link rel="stylesheet" type="text/css"
href="{% static "css/custom.css" %}">
{% endblock %}
Dashboard template that excludes the project.css link:
EXAMPLE 13.15
{% extends "base.html" %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css"
href="{% static "css/dashboard.css" %}">
{% comment %}
By not using {{ block.super }}, this block overrides the stylesheet block of base.html
{% endcomment %}
{% endblock %}
Template just linking the project.css file:
EXAMPLE 13.16
{% extends "base.html" %}
{% comment %}
By not using {% block stylesheets %}, this template inherits the stylesheets block from the base.html parent, in this case the default project.css link.
{% endcomment%}
These three examples demonstrate the amount of control that provides. The variable serves a good way to reduce template complexity, but can take a little bit of effort to fully comprehend.
TIP: block.super Is Similar but Not the Same as super()
For those coming from an object oriented programming background, it might help to think of the behavior of the variable to be like a very limited version of the Python built-in function, super(). In essence, th variable and the super() function both provide access to the parent. Just remember that they aren't the same. For example, the variable doesn't accept arguments. It's just a nice mnemonic that some developers might find useful.
13.7 Useful Things to Consider
The following are a series of smaller things we keep in mind during template development.
13.7.1 Avoid Coupling Styles Too Tightly to Python Code
Aim to control the styling of all rendered templates entirely via CSS and JS.
Use CSS for styling whenever possible. Never hardcode things like menu bar widths and color choices into your Ptyhon code. Avoid even putting that type of styling into your Django templates.
Here are some tips:
- If you have magic constants in your Python code that are entirely related to visual design layout, you should probably move them to a CSS file.
- The same applies to JavaScript.
13.7.2 Common Conventions
Here are some naming and style conventions that we recommend:
- We prefer underscores voer dashes in template names, block names, and other names in templates. Most Django users seem to follow this convention. Why? Well, because undersores are allowed in names of Python objects but dashes are forbidden.
- We rely on clear, intuitive anmes for blocks. {% block javascript %} is good.
- We include the name of the block tag in the endblock. Never write just {% endblock %}, include the whole {% endblock javascript %}
- Templates called by other templates are prefixed with '_'. This applies to templates called via {% include %}
13.7.3 Location, Location, Location!
Templates should usually go into the root of the Django project, at the same level as the apps. This is the most common convention, and it's an intuitive, easy pattern to follow.
The only exception is when you bundle up an app into a third-party package. That packages template directory should go into app directly. We'll explore this in section 21.9, 'How to Release Your Own Django Packages.'
13.7.4 Use Named Context Objects
When you use generic display CBVs, you have the option of using the generic and in your template. Another option is to use the ones that are named after your model.
For example, if you have a Topping modle, you can and in your templates, instead of and . This means both of the following template examples will work:
{# topping/topping_list.html #}
{# Using implicit names #}
<ol>
</ol>
{# Using explicit names #}
<ol>
}
</ol>
13.7.5 Use URL Names Instead of Hardcoded Paths
A common developer mistake is to hardcode URLs in templates like this:
The problem with this is that if the URL patterns of the site need to change, all the URLs across the site need to be addressed. This impacts HTML, JavaScript, and even RESTful APIs.
Instead, we use the {% url %} tag and references the names in our URLConf files:
EXAMPLE 13.18
<a href="{% url 'flavors_list' %}">
13.7.6 Debugging Complex Templates
A trick recommended by Lennart Regebro is that when templates are complex and it becomes difficult to determine where a variable is failing, you can force more verbose errors through the use of the stirng_if_invalid option in OPTIONS of your TEMPLATES setting:
EXAMPLE 13.19
# settings/local.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS':
'string_if_invalid': 'INVALID EXPRESSION: %s'
}
]
13.8 Error Page Templates
Even the most tested and analyzed site will have a few problems now and then, and that's okay. The problem lies in how you handle those errors. The last thing that you want to do is show an ugly repsonse or a blank web server page back to the end user.
It's standard practice to create at least 404.html and 500.html templates. See the GitHub HTML Styleguide link at the end of this section for other types of error pages that you may want to consider.
We suggest serving your error pages from a static file server (e.g. Nginx or Apache) as entirely self-contained static HTML files. That way, if your entire Django site goes down but your static file server is still up, then your error pages can still be served.
If you're on a PaaS, check the documentation on error pages. For example, Heroku allows users to upload a custom static HTML page to be used for 500 errors.
WARNING: Resist the Temptation to Overcomplicate Your Error Pages
Interesting or amusing error apges can be a draw to your site, but don't get carried away. It's embarrassing when your 404 page has a broken layout or your 500 page can't load the CSS and JavaScript. Worse yet is dynamic 500 error pages that break in the event of a database failure.
GitHub's 404 and 500 error pages are great examples of fancy but entirely static, self-contained error pages:
View he source of either of them and you'll notice that:
- All CSS styles are inline in the head of the same HTML page, eliminating the need ofr a separate stylesheet.
- All images are entirely contained as data within the HTML page. There are no
<img>
links to external URLs. - All JavaScript needed for the page is contained within the HTML page. There are no external links to JavaScript assets.
For more information, see the Github HTML Styleguide:
13.9 Follow a Minimalist Approach
We recommend taking a minimalist approach to your template code. Treate the so-called limitations of Django templates as a blessing in disguise. Use those constraints as inspiration to find simple, elegant ways to put more of your business logic into Python code rather than into templates.
Taking a minimalist approach to templates also makes it much easier to adapt your Django apps to changing format types. When your templates are bulky and full of nested looping, complex conditionals and data processing, it becomes harder to reuse business logic code in templates, not to mention impossible to use the same business logic in template-less views such as API views. Structuring your Django apps for code reuse is especially important as we move forward into the era of increased API development, since APIs and web pages often need to expose idnetical data with different formatting.
To this day, HTML remains a standard expression of content, providing the practices and patterns for this chapter.
13.10 Summary
In this chapter, we covered the following:
- Template inheritance, including the use of .
- Writing legible, maintainable templates.
- Easy methods to optimize template performance.
- Issues with limitations of template processing.
- Error page templates.
- Many other helpful little details about templates.
In the next chapter we'll examine template tags and filters.