Извлечение постов по сходству
Теперь, когда мы реализовали тегирование постов блога, с помощью тегов можно делать много интересных вещей. Теги позволяют классифицировать посты неиерархическим образом. Посты на схожие темы будут иметь несколько общих тегов. Мы разработаем функциональность отображения схожих постов по числу имеющихся у них общих тегов. При таком подходе при чтении пользователем поста ему можно будет предлагать почитать другие родственные посты.
Для того чтобы получить посты, схожие с конкретным постом, необходимо выполнить следующие действия:
- извлечь все теги текущего поста;
- получить все посты, помеченные любым из этих тегов;
- исключить текущий пост из этого списка, чтобы не рекомендовать тот же самый пост;
- упорядочить результаты по числу общих тегов, которое есть у текущего поста;
- в случае двух или более постов с одинаковым числом тегов рекомендовать самый последний пост;
- ограничить запрос числом постов, которые вы хотите рекомендовать.
Эти шаги транслируются в сложный набор запросов QuerySet, который вставляется в представление post_detail.
Откройте файл views.py приложения blog и добавьте следующую ниже инструкцию импорта в верхнюю его часть:
from django.db.models import Count
Это функция агрегирования Count из Django ORM-преобразователя. Данная функция позволит выполнять агрегированный подсчет тегов. Модуль django.db.models содержит следующие ниже функции агрегирования:
Avg: среднее значение;Max: максимальное значение;Min: минимальное значение;Count: общее количество объектов.
Об агрегировании можно узнать на странице https://docs.djangoproject.com/en/5.0/topics/db/aggregation/.
Откройте файл views.py приложения blog и добавьте следующие ниже строки в представление post_detail:
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post,
status=Post.Status.PUBLISHED,
slug=post,
publish__year=year,
publish__month=month,
publish__day=day)
# Список активных комментариев к этому посту
comments = post.comments.filter(active=True)
# Форма для комментариев пользователей
form = CommentForm()
# Список схожих постов
post_tags_ids = post.tags.values_list('id', flat=True)
similar_posts = Post.published.filter(tags__in=post_tags_ids) \
.exclude(id=post.id)
similar_posts = similar_posts.annotate(same_tags=Count('tags')) \
.order_by('-same_tags', '-publish')[:4]
return render(request,
'blog/post/detail.html',
{'post': post,
'comments': comments,
'form': form,
'similar_posts': similar_posts})
Приведенный выше исходный код делает следующее:
-
извлекается Python’овский список идентификаторов тегов текущего поста. Набор запросов
QuerySet values_list()возвращает кортежи со значениями заданных полей. Ему передается параметрflat=True, чтобы получить одиночные значения, такие как[1, 2, 3, ...], а не одноэлементые кортежи, такие как[(1,), (2,), (3,), ...]; -
берутся все посты, содержащие любой из этих тегов, за исключением текущего поста. В качестве примера, если мы берем нашу запись
Test post 6с 2 тегами, то на выходе мы получаем что-то вроде:<QuerySet [<Post: Test post 5>, <Post: Test post 5>, <Post: Test post 4>, <Post: Test post 3>, <Post: Test post 2>, <Post: Test Post>, <Post: Test Post>]>Где у поста 5 и 1 по два общих тега, а у постов 2, 3, 4 только по одному совпадению.
-
далее применяется функция агрегирования
Count. Ее работа – генерировать вычисляемое поле –same_tags, – которое содержит число тегов, общих со всеми запрошенными тегами. В данном случае мы считаем общее количество одинаковых объектов, и мы можем использовать любое поле модели. В качестве примера, если мы выполним запрос:similar_posts.values('title').annotate(same_tags=Count('tags'))То мы видим, что мы посчитали количество общих тегов, по каждому объекту:
<QuerySet [{'title': 'Test Post', 'same_tags': 2}, {'title': 'Test post 2', 'same_tags': 1}, {'title': 'Test post 3', 'same_tags': 1}, {'title': 'Test post 4', 'same_tags': 1}, {'title': 'Test post 5', 'same_tags': 2}]> -
и в итоге результат упорядочивается по числу общих тегов (
same_tags, в убывающем порядке) и поpublish, чтобы сначала отображать последние посты для постов с одинаковым числом общих тегов. Результат нарезается, чтобы получить только первые четыре поста; -
объект
similar_postsпередается в контекстный словарь для функцииrender().
Теперь отредактируйте шаблон blog/post/detail.html, добавив следующий ниже исходный код:
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|linebreaks }}
<p>
<a href="{% url 'blog:post_share' post.id %}">
Share this post
</a>
</p>
<h2>Similar posts</h2>
{% for post in similar_posts %}
<p>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</p>
{% empty %}
There are no similar posts yet.
{% endfor %}
{% with comments.count as total_comments %}
<h2>
{{ total_comments }} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}
{% for comment in comments %}
<div class="comment">
<p class="info">
Comment {{ forloop.counter }} by {{ comment.name }}
{{ comment.created }}
</p>
{{ comment.body|linebreaks }}
</div>
{% empty %}
<p>There are no comments.</p>
{% endfor %}
{% include "blog/post/includes/comment_form.html" %}
{% endblock %}
Страница детальной информации о посте должна выглядеть, как показано ниже:
Пройдите по URL-адресу http://127.0.0.1:8000/admin/blog/post/ в своем браузере, отредактируйте пост, у которого нет тегов, добавив теги music и jazz, как показано ниже:
Отредактируйте еще один пост, добавив тег jazz, как показано ниже.
Теперь страница детальной информации о первом посте должна выглядеть, как показано ниже:
Посты, рекомендованные в разделе Similar posts (Схожие посты), появляются в убывающем порядке в зависимости от числа общих тегов с изначальным постом.
Теперь появилась возможность успешно рекомендовать читателям схожие посты. Приложение django-taggit также содержит менеджер similar_objects(), который можно использовать для извлечения объектов на основе общих тегов.
Со всеми менеджерами приложения django-taggit можно ознакомиться на странице: https://django-taggit.readthedocs.io/en/latest/api.html.
Кроме того, список тегов можно добавить и в шаблон детальной информации о посте, точно таким же образом, как это было сделано в шаблоне blog/post/list.html.