Welcome to the first post of Django Package Series. In this tutorial, you will learn how to add tagging functionality to your models. Categories and tags help you organize your web site or blog and help your users find the information they want. A blog category is a topic you address on your blog. Your category list is like the table of contents for your blog. A tag is more specific and addresses items you discuss in a particular blog post. A tag is usually only a word or two and reflects the keywords or points of your article. If categories are your blog’s table of contents, tags are your blog’s index. By tagging an article with relevant key words, user can find the information easily so it makes your blog more professional. is a reusable application that primarily offers you a Tag model, and a manager for easily adding tags to any model. We will create very simple blog app and implement tagging system in it. django-taggit I am assuming that you already created Django project. Let's Start with installing package by following command: pip3 django-taggit install Once its installed, open and include taggit in your INSTALLED_APPS. settings.py INSTALLED_APPS = [ ... ] 'taggit' Create Model Now, open and create Post model: models.py django.db models taggit.managers TaggableManager title = models.CharField(max_length= ) description = models.TextField() published = models.DateField(auto_now_add= ) slug = models.SlugField(unique= , max_length= ) tags = TaggableManager() self.title from import from import : class Post (models.Model) 250 True True 100 : def __str__ (self) return The will show up automatically as a field in a ModelForm or in the admin. Tags input via the form field are parsed as follows: TaggableManager If the input doesn’t contain any commas or double quotes, It is simply treated as a space-delimited list of tag names. If the input does contain either of these character, groups of characters which appear between double quotes take precedence as multi-word tags (so doublequoted tag names may contain commas). An unclosed double quote will be ignored. Otherwise, if there are any unquoted commas in the input, it will be treated as comma-delimited. If not, itwill be treated as space-delimited. Create Form In your : forms.py django forms .models Post model = Post fields = [ , , , ] from import from import : class PostForm (forms.ModelForm) : class Meta 'title' 'description' 'tags' We included tags to our ModelForm but we are not going to render it by Django’s template language. Create Views Let's see : views.py django.shortcuts render, get_object_or_404 django.template.defaultfilters slugify .models Post .forms PostForm taggit.models Tag posts = Post.objects.order_by( ) common_tags = Post.tags.most_common()[: ] form = PostForm(request.POST) form.is_valid(): newpost = form.save(commit= ) newpost.slug = slugify(newpost.title) newpost.save() form.save_m2m() context = { :posts, :common_tags, :form, } render(request, , context) post = get_object_or_404(Post, slug=slug) context = { :post, } render(request, , context) tag = get_object_or_404(Tag, slug=slug) posts = Post.objects.filter(tags=tag) context = { :tag, :posts, } render(request, , context) from import from import from import from import from import : def home_view (request) '-published' # Show most common tags 4 if False # Without this next line the tags won't be saved. 'posts' 'common_tags' 'form' return 'home.html' : def detail_view (request, slug) 'post' return 'detail.html' : def tagged (request, slug) # Filter posts by tag name 'tag' 'posts' return 'home.html' When saving a form, you have to use the option and call on the form after you save the object. commit=False save_m2m() We are using slugify to convert our post title (string) to valid slug. As you see you can filter posts by tag name and display most used tags. Create Template Great! Now we can create our templates. base.html Simple Blog {% block content %}{% endblock content %} < > html < > head < > title </ > title < = > meta charset "utf-8" < = = > meta name "viewport" content "width=device-width, initial-scale=1, shrink-to-fit=no" < = = = = > link rel "stylesheet" href "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin "anonymous" < = = /> link rel "stylesheet" href "/static/css/tagsinput.css" </ > head < > body < = = = > script src "https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin "anonymous" </ > script < = = = > script src "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity "sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin "anonymous" </ > script < = = = > script src "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity "sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin "anonymous" </ > script < = > script src "/static/js/tagsinut.js" </ > script < > script $( ).submit( { e.preventDefault(); }); "#post-form" ( ) function e </ > script </ > body </ > html home.html {% extends 'base.html' %} {% block content %} {% csrf_token %} Title Description Tags Submit Common Tags: {% for mt in common_tags %} {{mt}} {% endfor %} {% for post in posts %} {{post.title}} {% for tag in post.tags.all %} #{{ tag }} {% endfor %} {{post.description}} {{post.published}} {% endfor %} {% endblock content %} < = > div class "container pt-5" < = > form method "POST" < = > div class "form-group" < > label </ > label < = = = = > input type "text" class "form-control" name "title" placeholder "Add title" </ > div < = > div class "form-group" < > label </ > label < = = = = > textarea type "text" class "form-control" name "description" placeholder "Add description" </ > textarea </ > div < = > div class "form-group" < > label </ > label < = = = = > input type "text" data-role "tagsinput" class "form-control" name "tags" </ > div < = = > button type "submit" class "btn btn-primary" </ > button </ > form < > p < = = > a href "#" class "badge badge-success" </ > a </ > p < = > div class "row mb-2 posts" < = > div class "col-md-6" < = > div class "cards" < = > div class "row no-gutters border rounded flex-md-row mb-4 shadow-sm h-md-250" < = > div class "col p-4 d-flex flex-column position-static" < = > h3 class "my-1" < = > a href "{% url 'detail' post.slug %}" </ > a </ > h3 < = > div style "display:flex" < = = > a href "{% url 'tagged' tag.slug %}" class "mr-1 badge badge-info" </ > a </ > div < = > p class "mb-auto" </ > p < = > p class "mb-auto text-muted" </ > p </ > div </ > div </ > div </ > div </ > div </ > div In form tag you can see name attribute and its just our form field name as attribute in HTML Template. We have to override the tag input so that is the main reason why we didn't render it by Django’s template language. We are using jQuery plugin which provides a Twitter Bootstrap user interface for managing tags. This plugin basically will change text inputs to actual tags. Take a look Bootstrap Tags Input Here is the final results: and filter posts by tag: Check documentation of for more. django-taggit You can clone or download the project from my GitHub below: https://github.com/raszidzie/django-taggit-tutorial That's it! Please share it and follow me on social media! :) Also check and as always Stay Connected!🚀 Reverse Python