Adding the Tagging Functionality in the Blog - Django Blog #7

Hello Internet Programmer, today we adding the tagging functionality in the blog to categorize content in a non-hierarchical manner, using simple keywords. We will implement a tagging system, which is a very popular feature for blogs. So, first activate the virtual environment, and let’s code.

Installing django-taggit

We will do this by integrating a third-party Django tagging application into your project. django-taggit is a reusable application that primarily offers you a Tag model and a manager to easily add tags to any model.

First, you need to install django-taggit via pip by running the following command,

pip3 install django-taggit

Then, open the settings.py file of the awwblog project and add taggit to your INSTALLED_APPS setting, as follows,

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'ckeditor', 
    'ckeditor_uploader',
    'taggit', #this
]

TAGGIT_CASE_INSENSITIVE = True

If you want django-taggit to be CASE-INSENSITIVE when looking up existing tags, you’ll have to set the TAGGIT_CASE_INSENSITIVE setting to True (False by default):

Integrate with Post model

Open the models.py file of your blog application and add the TaggableManager manager provided by django-taggit to the Post model using the following code,

from taggit.managers import TaggableManager


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

The tags manager will allow you to add, retrieve, and remove tags from Post objects.

Run the following command to create a migration for your model changes,

python3 manage.py makemigrations

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

python3 manage.py migrate

Your database is now ready to use django-taggit models.

Run the python manage.py runserver command to start the development server again and open http://127.0.0.1:8000/admin/blog/post/ in your browser. And open any post and you notice tag field is there,

django taggit, adding tags in blog posts

tags in post

I added tags here and clicked on save.

Now open http://127.0.0.1:8000/admin/taggit/tag/ and you will see the administration page with the list of Tag objects of the taggit application,

taggit appliaction, Adding the Tagging Functionality in the Blog

taggit appliaction

Displaying Tags for Post

Open views.py of blog application and we gonna edit post_list view to display tags for that post at last.

So, import the Tag model form django-taggit , and change the post_list view to optionally filter posts by a tag, as follows,

from taggit.models import Tag #import at top

def post_list(request, tag_slug=None):
    ...
    ...
    # post tag
    tag = None
    if tag_slug:
        tag = get_object_or_404(Tag, slug=tag_slug)
        posts = posts.filter(tags__in=[tag])
    
    ...
    ...
    
    return render(request,'post_list.html',{'posts':posts, page:'pages', 'tag':tag})

post_list now also takes an optional tag_slug parameter that has a None default value. This parameter will be passed in the URL.

Inside the view, you build the initial QuerySet, retrieving all published posts, and if there is a given tag slug, you get the Tag object with the given slug using the get_object_or_404() shortcut.

Then, you filter the list of posts by the ones that contain the given tag. Since this is a many-to-many relationship, you have to filter posts by tags contained in a given list, which, in your case, contains only one element.

We use the __in field lookup. Many-to-many relationships occur when multiple objects of a model are associated with multiple objects of another model. In your application, a post can have multiple tags and a tag can be related to multiple posts.

Setting URL for Tag

Open the urls.py file of your blog application and add the following additional URL pattern to list posts by tag,

urlpatterns=[
    path('',views.post_list,name="post_list"),
    path('<slug:post>/',views.post_detail,name="post_detail"),
    path('comment/reply/', views.reply_page, name="reply"),
    path('tag/<slug:tag_slug>/',views.post_list, name='post_tag'), #this
]

As you can see, both patterns point to the same view, but you 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. You use a slug path converter to match the parameter as a lowercase string with ASCII letters or numbers, plus the hyphen and underscore characters.

Displaying Tags in Template

Open the post_detail.html template and add the following HTML code at last, just before article tag.

   <p>
        <strong>Tags:</strong>
        {% for tag in post.tags.all %}
            <a href="{% url 'blog:post_tag' tag.slug %}" class="link-light text-decoration-none badge bg-secondary">
                {{tag.name}}
            </a>
            <!-- {% if not forloop.last %}, {% endif %} -->
        {% endfor %}
    </p>

tags in single post

Tags in single post

Also open post_list.html and to list posts with same tag,

{% block content %}
     <!-- add this code -->
    {% if tag %}
        <h2 class="mb-3">Posts tagged with "{{ tag.name }}"</h2>
    {% endif %}

posts with same tag, Adding the Tagging Functionality in the Blog

Posts with same tag

Adding Tags in Menu

Open base.html and edit navigation as following code,

<ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link {% if request.path == '/tag/linux/' %}active{% endif %}" aria-current="page" href="{{ request.scheme }}://{{ request.get_host }}/tag/linux/">Linux</a>
          </li>
          <li class="nav-item">
            <a class="nav-link {% if request.path == '/tag/tips/' %}active{% endif %}" href="{{ request.scheme }}://{{ request.get_host }}/tag/tips/">Tips</a>
          </li>
         
         <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Tutorial
          </a>
          <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
            <li>
              <a class="dropdown-item {% if request.path == '/tag/python/' %}active{% endif %}" href="{{ request.scheme }}://{{ request.get_host }}/tag/python/">Python</a>
            </li>
            
            <li>
              <a class="dropdown-item {% if request.path == '/tag/django/' %}active{% endif %}" href="{{ request.scheme }}://{{ request.get_host }}/tag/django/">Django</a>
            </li>
          
          </ul>
        </li>

        </ul>

We use tag as a menu. Here you can see we write tag url in anchor tag.

Here you notice request.path. It returns the current path of the url for ex. /tag/linux/. So, if current path is matched with path we clicked then active class of the bootstrap will apply.

In href attribute of anchor tag we use request.scheme that string representing the scheme of the request (http or https usually) and also used request.get_host to get the current host (for localhost it is 127.0.0.1:8000). In between we add :// to make a full URL and after this, we added the path of the tag. So final URL look like http://127.0.0.1:8000/tag/linux/.

You can make mechanism to maintain navigation from Django admin by maintaining separate table. But I used to make simple.

That’s it for this tutorial. Share with your friends and stay awesome.

GitHub Link: https://github.com/SoniArpit/awwblog

Previous: Adding Django Threaded Comments in Blog – Django Blog #6

Next: Retrieving Posts by Similarity – Django Blog #8