Добавление функциональности тегирования

⏱ 20 мин · 🧩 заданий: 6

Зачем теги

Теги — простой способ классифицировать посты по темам: «django», «python», «деплой». Пост может иметь несколько тегов, тег — много постов (связь «многие ко многим»). Можно написать модель Tag самим, но удобнее взять готовое решение — пакет django-taggit.

Установка

pip install django-taggit
INSTALLED_APPS = [
    ...
    "taggit",
    "blog.apps.BlogConfig",
]

Поле tags

from taggit.managers import TaggableManager


class Post(models.Model):
    ...
    tags = TaggableManager()
python manage.py makemigrations blog
python manage.py migrate

django-taggit создаёт свои таблицы: taggit_tag (сами теги) и taggit_taggeditem (связи тегов с объектами любой модели — через ContentTypes).

Работа с тегами в shell

>>> from blog.models import Post
>>> post = Post.objects.get(id=1)
>>> post.tags.add("django", "python", "веб")
>>> post.tags.all()
<QuerySet [<Tag: django>, <Tag: python>, <Tag: веб>]>
>>> post.tags.names()
['django', 'python', 'веб']
>>> post.tags.remove("веб")
>>> Post.objects.filter(tags__name__in=["django"])

В админке у поста появилось поле «Tags» — теги вводятся через запятую.

Вывод тегов

В list.html и detail.html:

<p class="tags">Теги: {{ post.tags.all|join:", " }}</p>

Лучше — ссылками на список постов по тегу:

<p class="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>

Фильтрация постов по тегу

Изменим post_list (функцию с пагинацией из урока про пагинацию):

from taggit.models import Tag


def post_list(request, tag_slug=None):
    post_list = Post.published.all()
    tag = None
    if tag_slug:
        tag = get_object_or_404(Tag, slug=tag_slug)
        post_list = post_list.filter(tags__in=[tag])

    paginator = Paginator(post_list, 3)
    posts = paginator.get_page(request.GET.get("page", 1))
    return render(request, "blog/post/list.html", {"posts": posts, "tag": tag})

Если вы перешли на PostListView, верните функцию в маршрут (или реализуйте фильтр в get_queryset() через self.kwargs.get("tag_slug")).

Два маршрута на одну view:

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path("tag/<slug:tag_slug>/", views.post_list, name="post_list_by_tag"),
    ...
]

В шаблоне списка покажем, что включён фильтр:

{% if tag %}
  <h2>Посты с тегом «{{ tag.name }}»</h2>
{% endif %}

Не забудьте поправить пагинацию: {% include "pagination.html" with page=posts %}.

Слаги тегов и кириллица

django-taggit по умолчанию строит слаг через slugify, который выбрасывает кириллицу: тег «веб» получит пустой или числовой слаг. Решения: использовать латинские теги, указать TAGGIT_STRIP_UNICODE_WHEN_SLUGIFYING = True вместе с пакетом unidecode, или собственную функцию транслитерации — разберём в проекте «Сайт статей».

Итог

  • django-taggit добавляет теги одной строкой: tags = TaggableManager().
  • post.tags.add/remove/all/names — управление тегами.
  • Фильтр по тегу: filter(tags__in=[tag]), два маршрута на одну view с необязательным параметром.

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Ввод ответа

Какой командой установить пакет django-taggit?

Задание 2 Написать код

Добавьте в модель Post поле tags — менеджер тегов TaggableManager из taggit.managers.

Задание 3 Написать код

Выведите теги поста через запятую с помощью фильтра join: Теги: django, python. Используйте post.tags.all.

Задание 4 Написать код

Допишите фильтрацию в post_list(request, tag_slug=None): если передан tag_slug, найдите тег (get_object_or_404(Tag, slug=tag_slug)) и оставьте посты, у которых есть этот тег (tags__in=[tag]).

Задание 5 Один вариант ответа

Как подключить одну view post_list и к списку всех постов, и к списку по тегу?

Задание 6 Несколько вариантов

Какие операции поддерживает менеджер post.tags из django-taggit?

Войдите, чтобы отмечать пройденные уроки.