-
Book Overview & Buying
-
Table Of Contents
Django 3 By Example - Third Edition
By :
After implementing your comment system, you need to create a way to tag your posts. You 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. You can take a look at its source code at https://github.com/jazzband/django-taggit.
First, you need to install django-taggit via pip by running the following command:
pip install django_taggit==1.2.0
Then, open the settings.py file of the mysite project and add taggit to your INSTALLED_APPS setting, as follows:
INSTALLED_APPS = [
# ...
'blog.apps.BlogConfig',
'taggit',
]
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
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:
python manage.py makemigrations blog
You should get the following output:
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 to synchronize your model changes:
python manage.py migrate
You will see an output indicating that migrations have been applied, as follows:
Applying taggit.0001_initial... OK
Applying taggit.0002_auto_20150616_2121... OK
Applying taggit.0003_taggeditem_add_unique_index... OK
Applying blog.0003_post_tags... OK
Your database is now ready to use django-taggit models.
Let's explore how to use the tags manager. Open the terminal with the python manage.py shell command and enter the following code. First, you will retrieve one of your posts (the one with the 1 ID):
>>> from blog.models import Post
>>> post = Post.objects.get(id=1)
Then, add some tags to it and retrieve its tags to check whether they were successfully added:
>>> post.tags.add('music', 'jazz', 'django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>, <Tag: django>]>
Finally, remove a tag and check the list of tags again:
>>> post.tags.remove('django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>]>
That 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 administration page with the list of Tag objects of the taggit application:

Figure 2.10: The tag change list view on the Django administration site
Navigate to http://127.0.0.1:8000/admin/blog/post/ and click on a post to edit it. You will see that posts now include a new Tags field, as follows, where you can easily edit tags:

Figure 2.11: The related tags field of a Post object
Now, you need to edit your blog posts to display 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 the same 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 should be able to see the list of tags under each post title:

Figure 2.12: The Post list item, including related tags
Next, you will edit the post_list view to let users list all posts tagged with a specific 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 a tag, as follows:
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 in each page
# ...
The post_list view now works as follows:
tag_slug parameter that has a None default value. This parameter will be passed in the URL.Tag object with the given slug using the get_object_or_404() shortcut.__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. You will learn how to create many-to-many relationships in Chapter 5, Sharing Content on Your Website. You can discover more about many-to-many relationships at https://docs.djangoproject.com/en/3.0/topics/db/examples/many_to_many/.Remember that QuerySets are lazy. The QuerySets to retrieve posts will only be evaluated when you loop over the post list when rendering the template.
Finally, modify the render() function at the bottom of the view to pass the tag variable to the template. The view should 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 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', {'page': page,
'posts': posts,
'tag': tag})
Open the urls.py file of your blog application, comment out the class-based PostListView URL pattern, and uncomment the post_list view, like this:
path('', views.post_list, name='post_list'),
# path('', views.PostListView.as_view(), name='post_list'),
Add the following additional URL pattern to list posts by tag:
path('tag/<slug:tag_slug>/',
views.post_list, name='post_list_by_tag'),
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.
Since you are using the post_list view, edit the blog/post/list.html template and modify the pagination to use 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 a user is accessing the blog, they will see the list of all posts. If they filter by posts tagged with a specific tag, they will see the tag that they are filtering by.
Now, change the way tags are displayed, as follows:
<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>
In the code above, you loop through all the tags of a post displaying a custom link to the URL to filter posts by that tag. You build the URL with {% url "blog:post_list_by_tag" tag.slug %}, using the name of the URL and the slug tag as its parameter. You separate the tags by commas.
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 that tag, like this:

Figure 2.13: A post filtered by the tag "jazz"
Change the font size
Change margin width
Change background colour