Для системы тегов мы будем использовать уже отличный созданный модуль django-taggit.
Установка django-taggit:
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> pip install django-taggit
Collecting django-taggit
Using cached django_taggit-3.0.0-py3-none-any.whl (59 kB)
Requirement already satisfied: Django>=3.2 in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from django-taggit) (4.1)
Requirement already satisfied: asgiref<4,>=3.5.2 in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from Django>=3.2->django-taggit) (3.5.2)
Requirement already satisfied: tzdata in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from Django>=3.2->django-taggit) (2022.2)
Requirement already satisfied: sqlparse>=0.2.2 in c:\users\razilator\desktop\courses\app\venv\lib\site-packages (from Django>=3.2->django-taggit) (0.4.2)
Installing collected packages: django-taggit
Successfully installed django-taggit-3.0.0
Добавим модуль в INSTALLED_APPS:
backend/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'mptt',
'debug_toolbar',
'taggit',
'captcha',
'modules.blog.apps.BlogConfig',
'modules.system.apps.SystemConfig',
]
И теперь давайте добавим теги в модель статей:
modules/blog/models/articles.py
from django.contrib.auth.models import User
from django.core.validators import FileExtensionValidator
from django.db import models
from django.template.defaultfilters import striptags, truncatewords_html
from django.urls import reverse
from mptt.fields import TreeForeignKey
from taggit.managers import TaggableManager
from modules.blog.managers import ArticleManager
from modules.system.models import AbstractBaseMeta
from modules.system.services.utils import ImageDirectorySave, unique_slugify, get_meta_keywords
class Article(AbstractBaseMeta):
title = models.CharField(verbose_name='Заголовок', max_length=255)
slug = models.SlugField(verbose_name='URL', max_length=255, blank=True)
category = TreeForeignKey('Category', on_delete=models.PROTECT, related_name='articles', verbose_name='Категория')
short_description = models.TextField(max_length=300, verbose_name='Краткое описание')
full_description = models.TextField(verbose_name='Описание')
author = models.ForeignKey(
User, verbose_name='Автор материала',
on_delete=models.PROTECT,
related_name='article_author'
)
updated_by = models.ForeignKey(User,
verbose_name='Автор обновления',
on_delete=models.PROTECT,
related_name='article_updated_by',
blank=True,
null=True
)
created_at = models.DateTimeField(verbose_name='Дата добавления', auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(verbose_name='Дата обновления', auto_now=True, db_index=True)
reason = models.CharField(verbose_name='Причина обновления', blank=True, max_length=100)
is_fixed = models.BooleanField(verbose_name='Зафиксировано', default=False, db_index=True)
is_published = models.BooleanField(verbose_name='Опубликовано', default=True)
thumbnail = models.ImageField(
verbose_name='Превью поста',
blank=True,
upload_to= ImageDirectorySave('images/thumbnails/'),
validators=[FileExtensionValidator(
allowed_extensions=('png', 'jpg', 'webp', 'jpeg', 'gif'))
]
)
tags = TaggableManager() #tags
objects = models.Manager()
custom = ArticleManager()
class Meta:
"""
Сортировка, название модели в админ панели, таблица в данными
"""
ordering = ('-is_fixed', '-created_at')
verbose_name = 'Статья'
verbose_name_plural = 'Статьи'
db_table = 'app_articles'
def save(self, *args, **kwargs):
"""
Сохранение полей модели при их отсутствии заполнения
"""
if not self.slug:
self.slug = unique_slugify(self, self.title)
if not self.meta_title:
self.meta_title = self.title
if not self.meta_description:
self.meta_description = striptags(truncatewords_html(self.short_description, 300))
if not self.meta_keywords:
self.meta_keywords = get_meta_keywords(self.full_description)
super().save(*args, **kwargs)
def __str__(self):
"""
Возвращение строки в виде заголовка статьи
"""
return self.title
@property
def get_thumbnail(self):
"""
Получение аватара при отсутствии загруженного
"""
if not self.thumbnail:
return '/media/images/placeholder.png'
return self.thumbnail.url
def get_absolute_url(self):
"""
Ссылка на статью
"""
return reverse('article-detail', kwargs={'slug': self.slug})
Пояснения:
- В нашу модель я добавил следующий фрагмент: tags = TaggableManager() импортировав из from taggit.managers import TaggableManager
Отлично. Давайте проведем миграции:
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py makemigrations
Migrations for 'blog':
modules\blog\migrations\0005_article_tags.py
- Add field tags to article
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions, sites, system, taggit
Running migrations:
Applying taggit.0001_initial... OK
Applying taggit.0002_auto_20150616_2121... OK
Applying taggit.0003_taggeditem_add_unique_index... OK
Applying taggit.0004_alter_taggeditem_content_type_alter_taggeditem_tag... OK
Applying taggit.0005_auto_20220424_2025... OK
Applying blog.0005_article_tags... OK
Проверим в админке:

Строка появилась. Давайте создадим представление и выведем возможность вывода по тегу, а также добавим теги в шаблон:
modules/blog/views/articles.py
from taggit.models import Tag
class ArticleByTagListView(ListView):
model = Article
template_name = 'modules/blog/articles/article-list.html'
context_object_name = 'articles'
paginate_by = 10
tag = None
def get_queryset(self):
self.tag = Tag.objects.get(slug=self.kwargs['tag'])
queryset = Article.objects.all().filter(tags__slug=self.tag.slug)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = f'Статьи по тегу: {self.tag.name}'
return context
Пояснения:
- Импортируем модель Tag из нашего модуля.
- Наследуемся от ListView
- Сортируем qs по слагу тега.
Редактируем urls.py для возможности сортировки по тегам
modules/blog/urls.py
from django.urls import path, include
from modules.blog.views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, ArticleCreateView, \
ArticleUpdateView, ArticleDeleteView, CommentCreateView, ArticleByTagListView
urlpatterns = [
path('', ArticleListView.as_view(), name='home'),
path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
path('articles/', include([
path('', ArticleListView.as_view(), name='article-list'),
path('create/', ArticleCreateView.as_view(), name='article-create-view'),
path('tags/<str:tag>/', ArticleByTagListView.as_view(), name='article-list-by-tags'), #tags
path('<int:pk>/comments/create/', CommentCreateView.as_view(), name='comment-create-view'),
path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
path('<str:slug>/update/', ArticleUpdateView.as_view(), name='article-update-view'),
path('<str:slug>/delete/', ArticleDeleteView.as_view(), name='article-delete-view'),
])),
]
Теперь отредактируем article-detail.html
templates/modules/blog/articles/article-detail.html
{% extends 'main.html' %}
{% load static %}
{% block content %}
<div class="card border-0 mb-3">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<figure>
<img src="{{ article.get_thumbnail }}" width="200" alt="{{ article.title }}">
</figure>
</div>
<div class="col-md-8">
<h5 class="card-title">
{{ article.title }}
</h5>
<small class="card-subtitle">
{{ article.created_at }} / {{ article.category }}
</small>
<div class="card-text">
{{ article.full_description }}
</div>
<hr/>
Добавил: <img src="{{ article.author.profile.get_avatar }}" class="rounded-circle" width="26" height="26"/> {{ article.author }}
<hr/>
<strong>Теги записи</strong>: {% for tag in article.tags.all %} <a href="{% url 'article-list-by-tags' tag.slug %}">{{ tag }}</a>, {% endfor %}
<hr/>
{% if article.reason %}{{ article.reason }} / обновил: {{ article.updated_by }}{% endif %}
</div>
</div>
</div>
</div>
<div class="card border-0">
<div class="card-body">
<h5 class="card-title">
Комментарии
</h5>
{% include 'modules/blog/comments/comments-list.html' %}
</div>
</div>
{% endblock %}
{% block script %}
<script src="{% static 'custom/js/article-detail.js' %}"></script>
{% endblock %}
Проверим вид на сайте:

Перейдем в один из тегов:

Отлично, таким образом мы добавили быстро систему тегов.