Django Templates: Best Practices

By Zac Clancy

Contributed by Kite_ (1)

 

Table Of Contents:

1. Introduction to Django Templates

2. Simple Start with Django Templates

3. Project Structure

4. How Inheritance Works for Django Templates

5. Handling Querysets

6. URL Namescaping

7. Takeaways

 

 

Introduction to Django Templates

Django, as a web framework, uses templates as a way of producing static HTML from the output of a Django view. In practice, Django’s templates are simply HTML files, with some special syntax and a set of tools which lets Django render the HTML page on-the-fly for the visiting user. Templates are highly customizable, but are meant to be simple, with most of the “heavy” logic going into the view. Let’s dive deeper and learn some standard ways of dealing with common problems.

 

 

Simple start with Django templates

By default, Django comes with a ton of built-in template tags and filters that help us perform repeatable template tasks throughout our apps. 

Tags: Tags provide arbitrary logic in the rendering process. Django leaves this definition fairly vague, but tags are able to output content, grab content from the database (more on this later), or perform control operations like if statements or for loops.

Examples of tags:




The
firstof tag will output the first provided variable which evaluates to True. This is a good replacement for a large if/elif/elif/elif/elif block that’s just evaluating on truthiness within your Django templates.


<ul>

</ul>



The for tag in Django will loop over each item in a list, making that item (product, in this case) available in the template context before the tag is closed with endfor. This is a widely used pattern when working with lists of Django model instances which have been returned from the view.

Filters: Filters transform the values of variables and arguments. Filters would be used in tasks like rendering a string in uppercase or formatting a date string into a user’s region.

Examples of filters:

 

The date filter will format a date (value, in the example) given a string with some format characters. The example would output the string: Mon 01 Apr 2019.


The slugify filter will convert the spaces of a string into hyphens and convert the string to lowercase, among other things. The output of this example would-look-something-like-this.

 

 

Project Structure

Django, by default, will make some assumptions about the structure of our project when it’s looking for templates. Knowing this, we can set up our project with a template directory and application template directories

Imagine a project, cloudwith the following structure:

cloud/
        accounts/
		urls.py
		models.py
		views.py
		templates/
			accounts/
				login.html
				register.html
	blog/
		urls.py
		views.py
		models.py
		templates/
			blog/
				create.html
				post.html
				list.html
	config/
		settings/
			base.py
			local.py
		urls.py
	manage.py
	templates/
		includes/
			messages.html
			modal.html
		base.html
		logged_in.html

 

 

How Inheritance Works for Django templates

An important aspect of Django’s templating system is template inheritance. Django applications are meant to be reusable, and we can apply the same methodology to our templates by inheriting common HTML from other templates.

A typical pattern is to have a common base template for common aspects of your application, logged-in pages, logged-out pages, or in places where significant changes are made to the underlying HTML. From our example above, base.html would contain most of the core structure that would make up each page, with blocks defined for app or page-specific customizations. 

For example, base.html may contain:


<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, 
initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" type="text/css" media="all"
href="" /> <link rel="stylesheet" type="text/css" media="all"
href="" /> <title></title> </head> <body class=""> <script src=""></script> <script src=""></script> <script type="text/javascript"> window._sharedData = { 'DEBUG': false, } </script> </body> </html>

There are a few things done in this example specifically for the sake of inheritance. Most notably, this base template has blocks defined for nearly every customizable aspect of the underlying HTML. Blocks for including CSS, JavaScript, an HTML title, meta tags, and more are all defined.

We use Django’s autoescape template tag surrounding blocks where we don’t want Django to autoescape our HTML tags or JavaScript, but rather treat the contents of the block literally.

Our shared_data  block allows us to populate a global JavaScript object with variables and data which we may want to share between Django and any running JavaScript on the page (populating React or Vue.js components, for example.)

For example, if we wanted to pass a Django URL to one of our JavaScript files, we could do something like this




  
  'USERS_AUTOCOMPLETE_ENDPOINT': '',


Django loads the page and returns in a JavaScript object that you can then use within the JavaScript files on the page:

<script type="text/javascript">
  window._sharedData = {
'DEBUG': false,
'USERS_AUTOCOMPLETE_ENDPOINT': '/api/users/autocomplete/',
} </script>

The inside of a JS console once the page has loaded:

>> window._sharedData.DEBUG
false
>> window._sharedData.USERS_AUTOCOMPLETE_ENDPOINT '/api/users/autocomplete/'


 

Handling Querysets

Properly handling querysets within your templates can be a performance bottleneck for Django depending on the complexities of your model definitions. 

Django’s templating system is tightly coupled with Django’s object-relational mapping layer which returns us data from the database. Without proper consideration of this coupling you may, inadvertently, cause the number of queries run on each page load to jump to unmaintainable amounts. In some cases, this can cause the database to become too sluggish to operate certain pages on your site, or worse, crash and need to be restarted.

Thankfully, Django provides mechanisms and patterns which we can use to make sure our templates are running as fast as possible and we’re not killing the database server. 

Consider this common Django pattern:

accounts/views.py

class UserListView(ListView): template_name = 'accounts/list.html' model = User paginate_by = 25 context_object_name = 'users' queryset = User.objects.all()

accounts/templates/accounts/list.html

...
<table>
  <thead>
  <tr>
    <th>Username</th>
    <th>Email</th>
    <th>Profile photo URL</th>
    <th>Joined</th>
  </tr>
  </thead>
  <tbody>
  
  </tbody>
</table>
...
<td></td>

To resolve this, we’ll change one line in our view, accounts/views.py, to select related objects when we’re running our original query for users:

class UserListView(ListView):
    template_name = 'accounts/list.html'
    model = User
    paginate_by = 25
    context_object_name = 'users'
    queryset = User.objects.select_related( 'profile')

By replacing our User.objects.all() with User.objects.select_related(‘profile’), we’re telling Django to include related profile instances when it’s performing its query for our users. This will include the Profile model on each User instance, preventing Django from needing to run an extra query each time we ask for information from the profile within the template.

Django’s select_related functionality does not work with many-to-many model relationships, or with many-to-one relationships. For this, we’d want to use Django’s prefetch_related method.

Unlike select_related, prefetch_related does its magic in Python, as opposed to SQL select statements, by joining related objects into instances which can be accessed in templates as we’ve done above. It doesn’t perform things in a single query like select_related is able to, but it’s much more efficient than running a query each time you request a related attribute.

A prefetch for related projects and organizations and one-to-many relationships off of the User model would look like this:

class UserListView(ListView):
    template_name = 'accounts/list.html'
    model = User
    paginate_by = 25
    context_object_name = 'users'
    queryset = User.objects.prefecth_related( 'profile', 'organizations')

 
You can use tools like django-debug-toolbar to investigate templates and views in your Django application which may benefit from using select_related and prefetch_related. Once installed, django-debug-toolbar is able to show which queries are run when a view is executed and a template is loaded. This is incredibly useful for debugging slow pages, a template you’ve written may be running hundreds of queries.

 

 

URL Namescaping

While not technically a Django templating system-specific best practice, using namespaces with your Django URLs makes developing inside templates much simpler. 

I find example 2 (below) to be much easier to quickly understand than example 1.

Example 1

<a href="">2020 Archive</a></li>
 
<a href="">2020 Archive</a></li>
 

URL namespaces allow us to have unique URL names, even if another application uses the same URL name (Create, detail, and edit, for example.) Without using URL namespaces, a Django project couldn’t have two URLs named create. With namespacing, we’re able to name and reference our URLs simply, without needing long complex names for each URL in our application.

A URL named blog-article-createwould become blog:articles:create, or users: profile:create since create is no longer reserved by a single application in our project. Setting this up is fairly straightforward. 

A urls.py file for example 1 (above) would look something like this:

blog/urls.py



from
django.urls import path


from . import views
urlpatterns = [ #... path('articles//', views.year_archive, name='news-year-archive'), #... ]


If we introduce namespacing, we’ll end up with a project setup like this:

blog/urls.py

 
 
from django.urls import path

from . import views

archive_patterns = [
path('/', views.year_archive, name='year'),
#...
]

urlpatterns = [
#...
path('', include(archive_patterns, namespace='archive')),
#...
]



from django.urls import include, path

urlpatterns = [
    path('articles/', include('blog.urls', namespace='blog')),
#...
]


This allows us to traverse from the blog namespace of URLs, into the archive namespace where we can designate URLs which will behave just inside the articles/ path, inside the blog application. Rendering the URL using the url template tag is also just as easy and intuitive (seen in example 2 above.) 

 

 

Takeaways

Django templates aren’t difficult to work with, but as we’ve seen above, there are a number of ways we can make working in Django templates even easier:

  • Learning and knowing which tags and filters are built-in and can help us out
  • Structuring our template folders in ways which are predictable for Django and other                     developers
  • To optimize page speeds and database connections, select and prefetch related objects               before accessing attributes that span relationships
  • Namespacing URLs makes referencing them in your templates predictable and accessible for       people developing on your application or project

See More Posts About:

                                                                  Django

Posted by Zac Clancy

Zac Clancy is a senior software engineer and technical advisor with broad experience ranging from technology startups to forward deployment into the immediate aftermath of international disasters. Zac led small US-based technology teams while in Haiti following the 2010 earthquake, helping to automate certain aspects of the emergency medical supply chain with software. Since Haiti, Zac has led initiatives, advised decision-makers, and implemented solutions during international disasters, or other critical incidents in some of the world’s most challenging locations.

LinkedIn Website