Создание сайта на Django: Урок 35, добавление системы тегов в Django
avatar
7 | (offline)
❤️‍🔥Notehunter Developer
Добавлено:
Категория: Руководства «Django»
Комментариев: 0

Для системы тегов мы будем использовать уже отличный созданный модуль 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 %}

Проверим вид на сайте:

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

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

Комментарии к статье 0
Комментариев нет
Форма добавления комментария (необходима регистрация)