Recent Comments

Elasticsearch & Django Tutorial -- Template Tags And You

In the first two parts of this series, we created the scaffolding of a simple blog engine. The first part covered the "models" used, allowing us to store and retrieve data from Elasticsearch. The second part covered the "views" which determine /what/ gets rendered for a specified URL.

This time, we're going to cover the /how/ of this get rendered. Now, to make our templates simple and straight-forward (and also easier for a non-developer to work with), we'll be making use of a few "advanced" Django features, such as custom template tags.

Template tags aren't as scary as you may be led to believe, especially compared to some frameworks. We're going to use them to help with separation of concerns, namely so that our view functions (from views.py) can be short and straight-forward, but also to keep logic out of the templates themselves. Our template tags are going to be "inclusion tags" that have takes_context=True. Inclusion tags simply built up another context and then render a specified template using this additional context. We're going to be making calls to the functions we defined in models.py here, to build up lists of blog posts, comments, or whatever.

@r.inclusion_tag('simple/tags/blog_detail.html', takes_context=True)
def blog_detail(context):
    return dict(blog_post=context['object'],
                comments=get_comments(reference_to=get_reference_id(context['object'], 'author'), is_spam=False),
                user=context['user'])


@r.inclusion_tag('simple/tags/blog_summary.html', takes_context=True)
def blog_summary(context):
    return dict(blog_post=context['object'],
                comment_count=get_comment_count(reference_to=get_reference_id(context['object'], 'author'), is_spam=False),
                user=context['user'])

@r.inclusion_tag('simple/tags/recent_posts.html', takes_context=True)
def recent_posts(context):
    blog_posts = get_posts('-created_on')
    return dict(blog_posts=blog_posts[:5],
                user=context['user'])

@r.inclusion_tag('simple/tags/recent_comments.html', takes_context=True)
def recent_comments(context):
    rcomments = get_comments('-created_on', is_spam=False)
    return dict(comments=rcomments[:5],
                user=context['user'])

@r.inclusion_tag('simple/tags/user_actions.html', takes_context=True)
def user_actions(context):
    return dict(user=context['user'])

@r.inclusion_tag('simple/tag/prolific_authors.html', takes_context=True)
def prolific_authors(context):
    authors = get_top_authors()
    return dict(authors=authors[:5],
                user=context['user'])

These template tags are very direct – they just built up some extra bit of context and then pass it along to another template file to render. Take the prolific_author tag as an example. It calls the get_top_authors function from models.py and then builds up a new dictionary with those results and then passes that all along to get rendered in the "prolific_authors.html" template file.

All the details of handling and processing whatever you want to consider as a "prolific author" for your site are now handled in these separate functions, instead of having the code and HTML spread out all over the place.

@r.inclusion_tag('simple/tags/comments.html', takes_context=True)
def comments(context, reference_to=None, depth=0):
    blog_post = context.get('object')
    if not reference_to:
        reference_id = get_reference_id(blog_post, 'author')
        reference_type = 'post'
    else:
        reference_id = get_reference_id(reference_to)
        reference_type = 'comment'
        comment_list = get_comments(reference_to=reference_id, is_spam=False)
        return dict(comments=comment_list,
                    blog_post=blog_post,
                    form=CommentForm(reference_type=reference_type, reference_to=reference_id),
                    user=context['user'],
                    depth=depth,
                    next_depth=depth+1,
                    span=12-depth)

The comments tag is the only template tag that breaks the simple nature of the ones like prolific_authors. It gets the relevant details to make a CommentForm object, taking care to keep track of what comment or post this new comment will refer to, and then passes it along to the comments.html template. The only complex part is building up the form. Now, we have a nice little tag that we can use in other parts of the app easily, like if we want to create an author's profile page and let people comment on authors. All that can be done with the {% comment %} tag and some minor code change to the comments function to support the concept of commenting on things other than posts.

Comments