После создания системы комментариев пришло время реализовать теги для постов. Сделаем это с помощью интеграции стороннего приложения 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/ в браузере и нажмите на ссылку тега. Появится список постов с этим тегом: