Creating custom template tags
Django provides the following helper functions that allow you to create your own template tags in an easy manner:
- simple_tag: Processes the data and returns a string
- inclusion_tag: Processes the data and returns a rendered template
Template tags must live inside Django applications.
Inside your blog application directory, create a new directory, name it templatetags, and add an empty __init__.py file to it. Create another file in the same folder and name it blog_tags.py. The file structure of the blog application should look like the following:
blog/
__init__.py
models.py
...
templatetags/
__init__.py
blog_tags.py
The way you name the file is important. You will use the name of this module to load tags in templates.
We will start by creating a simple tag to retrieve the total posts published in the blog. Edit the blog_tags.py file you just created and add the following code:
from django import template
from ..models import Post
register = template.Library()
@register.simple_tag
def total_posts():
return Post.published.count()
We have created a simple template tag that returns the number of posts published so far. Each template tags module needs to contain a variable called register to be a valid tag library. This variable is an instance of template.Library, and it's used to register our own template tags and filters. Then, we define a tag called total_posts with a Python function and use the @register.simple_tag decorator to register the function as a simple tag. Django will use the function's name as the tag name. If you want to register it using a different name, you can do it by specifying a name attribute, such as @register.simple_tag(name='my_tag').
After adding a new template tags module, you will need to restart the Django development server in order to use the new tags and filters in templates.
Before using custom template tags, you have to make them available for the template using the {% load %} tag. As mentioned before, you need to use the name of the Python module containing your template tags and filters. Open the blog/templates/base.html template and add {% load blog_tags %} at the top of it to load your template tags module. Then, use the tag you created to display your total posts. Just add {% total_posts %} to your template. The template should finally look like this:
{% load blog_tags %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
<p id="content">
{% block content %}
{% endblock %}
</p>
<p id="sidebar">
<h2>My blog</h2>
<p>This is my blog. I've written {% total_posts %} posts so far.</p>
</p>
</body>
</html>
We will need to restart the server to keep track of the new files added to the project. Stop the development server with Ctrl + C and run it again using the following command:
python manage.py runserver
Open http://127.0.0.1:8000/blog/ in your browser. You should see the number of total posts in the sidebar of the site, as follows:
The power of custom template tags is that you can process any data and add it to any template regardless of the view executed. You can perform QuerySets or process any data to display results in your templates.
Now, we will create another tag to display the latest posts in the sidebar of our blog. This time, we will use an inclusion tag. Using an inclusion tag, you can render a template with context variables returned by your template tag. Edit the blog_tags.py file and add the following code:
@register.inclusion_tag('blog/post/latest_posts.html')
def show_latest_posts(count=5):
latest_posts = Post.published.order_by('-publish')[:count]
return {'latest_posts': latest_posts}
In the preceding code, we register the template tag using @register.inclusion_tag and specify the template that has to be rendered with the returned values using blog/post/latest_posts.html. Our template tag will accept an optional count parameter that defaults to 5. This parameter allows us to specify the number of posts we want to display. We use this variable to limit the results of the query Post.published.order_by('-publish')[:count]. Note that the function returns a dictionary of variables instead of a simple value. Inclusion tags have to return a dictionary of values, which is used as the context to render the specified template. The template tag we just created allows you to specify the optional number of posts to display as {% show_latest_posts 3 %}.
Now, create a new template file under blog/post/ and name it latest_posts.html. Add the following code to it:
<ul>
{% for post in latest_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
In the preceding code, we display an unordered list of posts using the latest_posts variable returned by our template tag. Now, edit the blog/base.html template and add the new template tag to display the last three posts. The sidebar code should look like the following:
<p id="sidebar">
<h2>My blog</h2>
<p>This is my blog. I've written {% total_posts %} posts so far.</p>
<h3>Latest posts</h3>
{% show_latest_posts 3 %}
</p>
The template tag is called, passing the number of posts to display, and the template is rendered in place with the given context.
Now, return to your browser and refresh the page. The sidebar should now look like this:
Finally, we will create a simple template tag that stores the result in a variable that can be reused rather than directly outputting it. We will create a tag to display the most commented posts. Edit the blog_tags.py file and add the following import and template tag in it:
from django.db.models import Count
@register.simple_tag
def get_most_commented_posts(count=5):
return Post.published.annotate(
total_comments=Count('comments')
).order_by('-total_comments')[:count]
In the preceding template tag, we build a QuerySet using the annotate() function to aggregate the total number of comments for each post. We use the Count aggregation function to store the number of comments in the computed field total_comments for each Post object. We order the QuerySet by the computed field in descending order. We also provide an optional count variable to limit the total number of objects returned.
In addition to Count, Django offers the aggregation functions Avg, Max, Min, and Sum. You can read more about aggregation functions at https://docs.djangoproject.com/en/2.0/topics/db/aggregation/.
Edit the blog/base.html template and append the following code to the sidebar <p> element:
<h3>Most commented posts</h3>
{% get_most_commented_posts as most_commented_posts %}
<ul>
{% for post in most_commented_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
We store the result in a custom variable using the as argument followed by the variable name. For our template tag, we use {% get_most_commented_posts as most_commented_posts %} to store the result of the template tag in a new variable named most_commented_posts. Then, we display the returned posts using an unordered list.
Now, open your browser and refresh the page to see the final result. It should look like the following:
You have now a clear idea about how to build custom template tags. You can read more about them at https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/.