Django Blog #26: Adding a Tagging System

by Alex
Django Blog #26: Adding a Tagging System

After creating the comment system, it’s time to implement tags for posts. Let’s do this by integrating a third-party Django application. The django-taggit module is an application consisting of a Tag model and a manager for adding tags to any model. Here’s its source code: https://github.com/jazzband/django-taggit. First you need to install django-taggit with pip using the following command:

pip install django_taggit==0.22.2

Then open the settings.py file of the mysite project and add taggit to the INSTALLED_APPS setting:

INSTALLED_APPS = [ 
   # .. 
   'blog.apps.BlogConfig', 
   'taggit', 
]

Open the models.py file of the blog application and add the TaggableManager from django-taggit to the Post model using the following code:

from taggit.managers import TaggableManager

class Post(models.Model):
   # ...
    tags = TaggableManager()

The tags manager allows you to add, remove, and retrieve tags from Post objects. Use the following command to create a migration for model changes:

python manage.py makemigrations blog

The following output should appear:

Migrations for 'blog':
  blog\migrations\0003_post_tags.py
    - Add field tags to post

Now run the following command to create the required database tables for django-taggit models and synchronize model changes:

python manage.py migrate

A output will appear confirming the migrations applied:

Applying taggit.0001_initial... OK 
Applying taggit.0002_auto_20150616_2121... OK 
Applying blog.0003_post_tags... OK

The database is now ready to use django-taggit models. But first we need to figure out how the tags manager works. Open a terminal with the python manage.py shell command and type the following code. The first thing to do is to get one of the posts (with ID 1):

>>> from blog.models import Post 
>>> post = Post.objects.get(id=1)

Then add some tags and try to return them to see if they were added:

>>> post.tags.add('music', 'jazz', 'django') 
>>> post.tags.all()
<QuerySet[<Tag: jazz>, <Tag: music>, <Tag: django>]>

Finally, remove them and check the list again:

>>> post.tags.remove('django') 
>>> post.tags.all()
<QuerySet[<Tag: jazz>, <Tag: music>]>

That was easy, wasn’t it? Run the python manage.py runserver command to start the development server and open https://127.0.0.1:8000/admin/taggit/tag in your browser. This will display an administrative page with a list of Tag application Taggit objects: Go to https://127.0.0.1:8000/admin/blog/post/ and click on the post to edit it. The posts now include a Tags field that you can use to easily edit them:

Django Blog #26: Adding a Tagging SystemLet’s edit blog posts to display tags. Open the blog/post/list.html template and add the following HTML code under the post title:

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

The join template filter works in the same way as the join() string method to combine elements with the selected string. Open https://127.0.0.1:8000/blog/ in your browser. A list of tags will now appear below each post title:Django Blog #26: Adding a Tagging System Let’s edit the post_list view so that users can see all the posts associated with a particular tag. Open the views.py file of the blog application, import the Tag model from django-taggit and change the post_list view to optionally filter posts by tag:

from taggit.models import Tag 

def post_list(request, tag_slug=None) 
    object_list = post.published.all() 
    tag = None 

   if tag_slug 
        tag = get_object_or_404(Tag, slug=tag_slug) 
        object_list = object_list.filter(tags__in=[tag]) 

    paginator = Paginator(object_list, 3) # 3 posts per page
   # ...

The post_list view works as follows:

  1. It takes the optional tag_slug parameter with default value None. It will be in the URL.
  2. The view creates the first QuerySet, which gets all the published posts. If slug is specified, the Tag object can be retrieved through it with get_object_or_404().
  3. The list is then filtered so that only those that include the tag remain. This is a many-to-many relationship, so filter by the tags in the list, which in this case contains only one item.

It’s worth remembering that QuerySets are lazy. They will only be executed when iterating over the post_list when rendering the template. Finally, you need to modify the render() function at the bottom of the view to pass the tag tag to the template. As a result, the view will look like this:

def post_list(request, tag_slug=None) 
    object_list = post.published.all() 
    tag = None 
  
   if tag_slug  
        tag = get_object_or_404(Tag, slug=tag_slug) 
        object_list = object_list.filter(tags__in=[tag]) 
  
    paginator = Paginator(object_list, 3) # 3 posts on each page  
    page = request.GET.get('page') 
    try 
        posts = paginator.page(page) 
   except PageNotAnInteger  
       # If the page is not an integer, put the first page  
        posts = paginator.page(1) 
   except EmptyPage  
       # If the page is bigger than the maximum, deliver the last page of the results  
        posts = paginator.page(paginator.num_pages) 
   return return(request,  
		 'blog/post/list.html',  
		 {'page': page,  
		  { 'posts': posts,  
		  { 'tag': tag})

Open the blog application’s urls.py file, comment out the PostListView URL template based on the class, and uncomment the post_list view:

path('', views.post_list, name='post_list'), 
# path('', views.PostListView.as_view(), name='post_list'),

Add the following additional URL template to list posts by tag:

path(''tag/<slug:tag_slug>/',
     views.post_list, name='post_list_by_tag'),

Both templates point to the same view, but they are called differently. The first will call the post_list view with no additional parameters, while the second uses tag_slug. Here a slug path converter is used to map the parameter as a lowercase string consisting of ASCII characters, hyphens, and underscores. Because the post_list view is used, you need to edit the blog/post/list.html template and change the pagination so that it uses the posts object:

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

Add the following lines above the {% for %} loop:

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

If the user visits the blog, he will see a list of posts. If he tries to filter content by a specific tag, the tag he is filtering by. Change the way the tags are displayed:

<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>

Now go through all the post tags, displaying a custom link in the URL to filter posts by that tag. The URL will be built with {% url "blog:post_list_by_tag" tag.slug %} with URL and slug as parameters. The tags are separated by commas. Open https://127.0.0.1:8000/blog/ in your browser and click on the tag link. A list of posts with that tag will appear:Django Blog #26: Adding a Tagging System

Related Posts

LEAVE A COMMENT