После создания системы комментариев пришло время реализовать теги для постов. Сделаем это с помощью интеграции стороннего приложения Django. Модуль django-taggit — это приложение, состоящее из модели Tag и менеджера для добавления тегов к любой модели. Вот его исходный код: https://github.com/jazzband/django-taggit.
Сперва нужно установить django-taggit с помощью pip, воспользовавшись следующей командой:
pip install django_taggit==0.22.2
Затем откройте файл settings.py проекта mysite и добавьте taggit к настройке INSTALLED_APPS:
INSTALLED_APPS = [
# ...
'blog.apps.BlogConfig',
'taggit',
]
Откройте файл models.py приложения blog и добавьте менеджер TaggableManager из django-taggit к модели Post с помощью следующего кода:
from taggit.managers import TaggableManager
class Post(models.Model):
# ...
tags = TaggableManager()
Менеджер tags позволяет добавлять, удалять и получать теги от объектов Post.
Используйте следующую команду для создания миграции для изменений модели:
python manage.py makemigrations blog
Должен появиться следующий вывод:
Migrations for 'blog':
blog\migrations\0003_post_tags.py
- Add field tags to post
Теперь запустите следующую команду для создания требуемых таблиц базы данных для моделей django-taggit и синхронизации изменений модели:
python manage.py migrate
Появится вывод, подтверждающий примененные миграции:
Applying taggit.0001_initial... OK
Applying taggit.0002_auto_20150616_2121... OK
Applying blog.0003_post_tags... OK
База данных теперь готова использовать модели django-taggit. Но сперва нужно разобраться, как работает менеджер tags. Откройте терминал с помощью команды python manage.py shell и введите следующий код. В первую очередь нужно получить один из постов (с ID 1):
>>> from blog.models import Post
>>> post = Post.objects.get(id=1)
Затем добавьте некоторые теги и попробуйте вернуть их, чтобы проверить, были ли они добавлены:
>>> post.tags.add('music', 'jazz', 'django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>, <Tag: django>]>
Наконец, удалите их и проверьте список еще раз:
>>> post.tags.remove('django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>]>
Это было легко, не так ли? Запустите команду python manage.py runserver для запуска сервера разработки и откройте https://127.0.0.1:8000/admin/taggit/tag в браузере. Отобразится административная страница со списком объектов Tag приложения taggit:
Перейдите на https://127.0.0.1:8000/admin/blog/post/ и кликните по посту, чтобы отредактировать его. Посты теперь включают поле Tags, с помощью которого можно легко их редактировать:
Отредактируем посты в блоге для отображения тегов. Откройте шаблон blog/post/list.html и добавьте следующий код HTML под названием поста:
<p class="tags">Tags: {{ post.tags.all|join:", " }}</p>
Фильтр шаблона join работает так же, как и метод строки join() для объединения элементов с выбранной строкой. Откройте https://127.0.0.1:8000/blog/ в браузере. Теперь под каждым названием поста будет отображаться список тегов:
Отредактируем представление post_list, чтобы пользователи могли посмотреть все посты, связанные с конкретным тегом. Откройте файл views.py приложения blog, импортируйте модель Tag из django-taggit и измените представление post_list, чтобы опционально фильтровать посты по тегу:
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 поста на каждой странице
# ...
Представление post_list работает следующим образом:
- Оно принимает опциональный параметр
tag_slugсо значением по умолчаниюNone. Он будет в URL. - В представлении создается первый QuerySet, который получает все опубликованные посты. Если
slugуказан, то объектTagможно получить через него с помощьюget_object_or_404(). - Затем список фильтруется, чтобы остались только те, что включают тег. Это отношение многое-ко-многим, поэтому фильтровать нужно по тегам в списке, который в этом случае содержит всего один элемент.
Стоит напомнить, что QuerySet ленивы. Они будут выполнены только при итерации по списку постов при рендеринге шаблона.
Наконец, нужно изменить функцию render() в нижней части представления для передачи тега 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 поста на каждой странице
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
# Если страница не является целым числом, поставим первую страницу
posts = paginator.page(1)
except EmptyPage:
# Если страница больше максимальной, доставить последнюю страницу результатов
posts = paginator.page(paginator.num_pages)
return render(request,
'blog/post/list.html',
{'page': page,
'posts': posts,
'tag': tag})
Откройте файл urls.py приложения blog, закомментируйте URL-шаблон PostListView, основанный на классе и раскомментируйте представление post_list:
path('', views.post_list, name='post_list'),
# path('', views.PostListView.as_view(), name='post_list'),
Добавьте следующий дополнительный URL-шаблон для перечисления постов по тегу:
path('tag/<slug:tag_slug>/',
views.post_list, name='post_list_by_tag'),
Оба шаблона указывают на одно представление, но называются они по-разному. Первое будет вызывать представление post_list без дополнительных параметров, а второй использует tag_slug. Здесь используется конвертер пути slug для сопоставления параметра в качестве строки в нижнем регистре, состоящей из символов ASCII, дефиса и нижнего подчеркивания.
Поскольку используется представление post_list, нужно отредактировать шаблон blog/post/list.html и изменить пагинацию так, чтобы она использовала объект posts:
{% include "../pagination.html" with page=posts %}
Добавьте следующие строки над циклом {% for %}:
{% if tag %}
<h2>Posts tagged with "{{ tag.name }}"</h2>
{% endif %}
Если пользователь будет заходить в блог, он увидит список постов. Если попробует отфильтровать материалы по конкретному тегу — тег, по которому проходит фильтрация. Измените способ отображения тегов:
<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>
Теперь переберите все теги поста, отображая кастомную ссылку в URL для фильтра постов по этому тегу. URL будет построен с помощью {% url "blog:post_list_by_tag" tag.slug %} с URL и slug в качестве параметров. Теги разделяются запятыми.
Откройте https://127.0.0.1:8000/blog/ в браузере и нажмите на ссылку тега. Появится список постов с этим тегом:









