Chapter 3. Extending Your Blog Application

The previous chapter went through the basics of forms, and you learned how to integrate third-party applications into your project. This chapter will cover the following points:

  • Creating custom template tags and filters
  • Adding a sitemap and a post feed
  • Building a search engine with Solr and Haystack

Creating custom template tags and filters

Django offers a variety of built-in template tags such as {% if %} or {% block %}. You have used several in your templates. You can find a complete reference of built-in template tags and filters at https://docs.djangoproject.com/en/1.8/ref/templates/builtins/.

However, Django also allows you to create your own template tags to perform custom actions. Custom template tags come in very handy when you need to add functionality to your templates that is not covered by the core set of Django template tags.

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
  • assignment_tag: Processes the data and sets a variable in the context

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 name of the file is important. You are going to use the name of this module to load your 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

register = template.Library()

from ..models import Post

@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 your own template tags and filters. Then we are defining a tag called total_posts with a Python function and using @register.simple_tag to define the function as a simple tag and register it. Django will use the function's name as the tag name. If you want to register it with a different name, you can do it by specifying a name attribute like @register.simple_tag(name='my_tag').

Note

After adding a new template tags module, you will need to restart the Django development server in order to use the new template tags and filters.

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/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 staticfiles %}
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}{% endblock %}</title>
  <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
  <div id="content">
    {% block content %}
    {% endblock %}
  </div>
  <div id="sidebar">
    <h2>My blog</h2>
    <p>This is my blog. I've written {% total_posts %} posts so far.</p>
  </div>
</body>
</html>

We 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 this 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, like this:

Creating custom template tags

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 are going to create another tag to display the latest posts in the sidebar of our blog. This time we are going to 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 this code, we register the template tag with @register.inclusion_tag and we specify the template that has to be rendered with the returned values with blog/post/latest_posts.html. Our template tag will accept an optional count parameter that defaults to 5 and allows us to specify the number of comments we want to display. We use this variable to limit the results of the query Post.published.order_by('-publish')[:count]. Notice that the function returns a dictionary of variables instead of a simple value. Inclusion tags have to return a dictionary of values that is used as the context to render the specified template. Inclusion tags return a dictionary. The template tag we just created can be used passing the optional number of comments to display like {% 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>

Here, 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 3 comments. The sidebar block should look like the following:

<div 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 %}
</div>

The template tag is called passing the number of comments to display and the template is rendered in place with the given context.

Now, go back to your browser and refresh the page. The sidebar should now look like this:

Creating custom template tags

Finally, we are going to create an assignment tag. Assignment tags are like simple tags but they store the result in a given variable. We will create an assignment 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.assignment_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(
               total_comments=Count('comments')
           ).order_by('-total_comments')[:count]

This QuerySet uses the annotate() function for query aggregation, using the Count aggregation function. We build a QuerySet aggregating the total number of comments for each post in a total_comments field and we order the QuerySet by this field. We also provide an optional count variable to limit the total number of objects returned to a given value.

In adition 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/1.8/topics/db/aggregation/.

Edit the blog/base.html template and append the following code to the sidebar <div> 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>

The notation for assignment template tags is {% template_tag as variable %}. For our template tag, we use {% get_most_commented_posts as most_commented_posts %}. This way, we are storing the result of the template tag in a new variable named most_commented_posts. Then, we display the returned posts with an unordered list.

Now, open your browser and refresh the page to see the final result. It should look like the following:

Creating custom template tags

You can read more about custom template tags at https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/.

Creating custom template filters

Django has a variety of built-in template filters that allow you to modify variables in templates. These are Python functions that take one or two parameters—the value of the variable it's being applied to, and an optional argument. They return a value that can be displayed or treated by another filter. A filter looks like {{ variable|my_filter }} or passing an argument, it looks like {{ variable|my_filter:"foo" }}. You can apply as many filters as you like to a variable like {{ variable|filter1|filter2 }}, and each of them will be applied to the output generated by the previous filter.

We are going to create a custom filter to be able to use markdown syntax in our blog posts and then convert the post contents to HTML in the templates. Markdown is a plain text formatting syntax that is very simple to use and it's intended to be converted into HTML. You can learn the basics of this format at http://daringfireball.net/projects/markdown/basics.

First, install the Python markdown module via pip using the following command:

pip install Markdown==2.6.2

Then edit the blog_tags.py file and include the following code:

from django.utils.safestring import mark_safe
import markdown

@register.filter(name='markdown')
def markdown_format(text):
    return mark_safe(markdown.markdown(text))

We register template filters in the same way as we do with template tags. To avoid collision between our function name and the markdown module, we name our function markdown_format and name the filter markdown for usage in templates like {{ variable|markdown }}. Django escapes the HTML code generated by filters. We use the mark_safe function provided by Django to mark the result as safe HTML to be rendered in the template. By default, Django will not trust any HTML code and will escape it before placing it into the output. The only exception are variables that are marked safe from escaping. This behavior prevents Django from outputting potentially dangerous HTML and allows you to create exceptions when you know you are returning safe HTML.

Now, load your template tags module in the post list and detail templates. Add the following line at the top of the post/list.html and post/detail.html template after the {% extends %} tag:

{% load blog_tags %}

In the post/detail.html templates, replace the following line:

{{ post.body|linebreaks }}

...with the following one:

{{ post.body|markdown }}

Then, in the post/list.html file, replace this line:

{{ post.body|truncatewords:30|linebreaks }} 

...with the following one:

{{ post.body|markdown|truncatewords_html:30 }}

The truncatewords_html filter truncates a string after a certain number of words, avoiding unclosed HTML tags.

Now, open http://127.0.0.1:8000/admin/blog/post/add/ in your browser and add a post with the following body:

This is a post formatted with markdown
--------------------------------------

*This is emphasized* and **this is more emphasized**.

Here is a list:

* One
* Two
* Three

And a [link to the Django website](https://www.djangoproject.com/)

Open your browser and see how the post is rendered. You should see the following:

Creating custom template filters

As you can see, custom template filters are very useful to customize formatting. You can find more information about custom filters at https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-custom-template-filters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset