Enhancing Your Blog with Advanced Features

Antonio Melé

September 2015

In this article by Antonio Melé, the author of the Django by Example book shows how to use the Django forms, and ModelForms. You will let your users share posts by e-mail, and you will be able to extend your blog application with a comment system. You will also learn how to integrate third-party applications into your project, and build complex QuerySets to get useful information from your models.

In this article, you will learn how to add tagging functionality using a third-party application.

(For more resources related to this topic, see here.)

Adding tagging functionality

After implementing our comment system, we are going to create a system for adding tags to our posts. We are going to do this by integrating in our project a third-party Django tagging application. django-taggit is a reusable application that primarily offers you a Tag model, and a manager for easily adding tags to any model. You can take a look at its source code at https://github.com/alex/django-taggit.

First, you need install django-taggit via pip by running the pip install django-taggit command. Then, open the settings.py file of the project, and add taggit to your INSTALLED_APPS setting as the following:

INSTALLED_APPS = (

   # ...

   'mysite.blog',

   'taggit',

)

Then, open the models.py file of your blog application, and add to the Post model the TaggableManager manager, provided by django-taggit as the following:

from taggit.managers import TaggableManager

# ...

 

class Post(models.Model):

   # ...

   tags = TaggableManager()

You just added tags for this model. The tags manager will allow you to add, retrieve, and remove tags from the Post objects.

Run the python manage.py makemigrations blog command to create a migration for your model changes. You will get the following output:

Migrations for 'blog':

0003_post_tags.py:

   Add field tags to post

Now, run the python manage.py migrate command to create the required database tables for django-taggit models and synchronize your model changes. You will see an output indicating that the migrations have been applied:

Operations to perform:

Apply all migrations: taggit, admin, blog, contenttypes, sessions, auth

Running migrations:

Applying taggit.0001_initial... OK

Applying blog.0003_post_tags... OK

Your database is now ready to use django-taggit models. Open the terminal with the python manage.py shell command, and learn how to use the tags manager. First, we retrieve one of our posts (the one with the ID as 1):

>>> from mysite.blog.models import Post

>>> post = Post.objects.get(id=1)

Then, add some tags to it and retrieve its tags back to check that they were successfully added:

>>> post.tags.add('music', 'jazz', 'django')

>>> post.tags.all()

[<Tag: jazz>, <Tag: django>, <Tag: music>]

Finally, remove a tag and check the list of tags again:

>>> post.tags.remove('django')

>>> post.tags.all()

[<Tag: jazz>, <Tag: music>]

This was easy, right? Run the python manage.py runserver command to start the development server again, and open http://127.0.0.1:8000/admin/taggit/tag/ in your browser. You will see the admin page with the list of the Tag objects of the taggit application:

Django by Example

Navigate to http://127.0.0.1:8000/admin/blog/post/ and click on a post to edit it. You will see that the posts now include a new Tags field as the following one where you can easily edit tags:

Django by Example

Now, we are going to edit our blog posts to display the tags. Open the blog/post/list.html template and add the following HTML code below the post title:

<p class="tags">Tags: {{ post.tags.all|join:", " }}</p>

The join template filter works as the Python string join method to concatenate elements with the given string. Open http://127.0.0.1:8000/blog/ in your browser. You will see the list of tags under each post title:

Django by Example

Now, we are going to edit our post_list view to let users see all posts tagged with a tag. Open the views.py file of your blog application, import the Tag model form django-taggit, and change the post_list view to optionally filter posts by tag as the following:

from taggit.models import Tag

 

def post_list(request, tag_slug=None):

   post_list = Post.published.all()

   if tag_slug:

       tag = get_object_or_404(Tag, slug=tag_slug)

       post_list = post_list.filter(tags__in=[tag])

     # ...

The view now takes an optional tag_slug parameter that has a None default value. This parameter will come in the URL. Inside the view, we build the initial QuerySet, retrieving all the published posts. If there is a given tag slug, we get the Tag object with the given slug using the get_object_or_404 shortcut. Then, we filter the list of posts by the ones which tags are contained in a given list composed only by the tag we are interested in. Remember that QuerySets are lazy. The QuerySet for retrieving posts will only be evaluated when we loop over the post list to render the template. Now, change the render function at the bottom of the view to pass all the local variables to the template using locals(). The view will finally look as the following:

def post_list(request, tag_slug=None):

   post_list = Post.published.all()

 

   if tag_slug:

       tag = get_object_or_404(Tag, slug=tag_slug)

       post_list = post_list.filter(tags__in=[tag])

 

   paginator = Paginator(post_list, 3) # 3 posts in each page

   page = request.GET.get('page')

   try:

       posts = paginator.page(page)

   except PageNotAnInteger:

       # If page is not an integer deliver the first page

       posts = paginator.page(1)

   except EmptyPage:

       # If page is out of range deliver last page of results

       posts = paginator.page(paginator.num_pages)

   return render(request, 'blog/post/list.html', locals())

Now, open the urls.py file of your blog application, and make sure you are using the following URL pattern for the post_list view:

url(r'^$', post_list, name='post_list'),

Now, add another URL pattern as the following one for listing posts by tag:

url(r'^tag/(?P<tag_slug>[-\w]+)/$', post_list, name='post_list_by_tag'),

As you can see, both the patterns point to the same view, but we are naming them differently. The first pattern will call the post_list view without any optional parameters, whereas the second pattern will call the view with the tag_slug parameter.

Let’s change our post list template to display posts tagged with a specific tag, and also link the tags to the list of posts filtered by this tag. Open blog/post/list.html and add the following lines before the for loop of posts:

{% if tag %}

   <h2>Posts tagged with "{{ tag.name }}"</h2>

{% endif %}

If the user is accessing the blog, he will the list of all posts. If he is filtering by posts tagged with a specific tag, he will see this information. Now, change the way the tags are displayed into the following:

<p class="tags">

   Tags:

   {% for tag in post.tags.all %}

       <a href="{% url "blog:post_list_by_tag" tag.slug %}">{{ tag.name }}</a>

       {% if not forloop.last %}, {% endif %}

   {% endfor %}

</p>

Notice that now we are looping through all the tags of a post, and displaying a custom link to the URL for listing posts tagged with this tag. We build the link with {% url "blog:post_list_by_tag" tag.slug %} using the name that we gave to the URL, and the tag slug as parameter. We separate the tags by commas. The complete code of your template will look like the following:

{% extends "blog/base.html" %}

 

{% block title %}My Blog{% endblock %}

 

{% block content %}

   <h1>My Blog</h1>

   {% if tag %}

       <h2>Posts tagged with "{{ tag.name }}"</h2>

   {% endif %}

   {% for post in posts %}

       <h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>

       <p class="tags">

           Tags:

           {% for tag in post.tags.all %}

               <a href="{% url "blog:post_list_by_tag" tag.slug %}">{{ tag.name }}</a>

               {% if not forloop.last %}, {% endif %}

           {% endfor %}

       </p>

       <p class="date">Published {{ post.publish }} by {{ post.author }}</p>

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

   {% endfor %}

 

   {% include "pagination.html" with page=posts %}

{% endblock %}

Open http://127.0.0.1:8000/blog/ in your browser, and click on any tag link. You will see the list of posts filtered by this tag as the following:

Django by Example

Summary

In this article, you added tagging to your blog posts by integrating a reusable application.

The book Django By Example, hands-on-guide will also show you how to integrate other popular technologies with Django in a fun and practical way.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Django By Example

Explore Title
comments powered by Disqus