Блог на Django #27: Получение похожих постов

Теперь, когда теги реализованы, с ними можно многое делать. С их помощью легко классифицировать посты. Материалы на схожие темы будут содержать общие теги. Создадим функциональность, которая будет отображать похожие посты, отталкиваясь от количества одинаковых тегов. Так, когда пользователь прочтет один материал, ему можно будет порекомендовать другой, связанный с ним тематически.

Для получения похожих постов нужно совершить следующие шаги:

  1. Получить все теги текущего поста.
  2. Получить все посты, к которым проставлены любые из этих же тегов.
  3. Исключить текущий материал, чтобы не рекомендовать его же.
  4. Отсортировать результаты по количеству общих тегов с текущим постом.
  5. Если тегов два или более, рекомендовать самый последний.
  6. Ограничить количество рекомендуемых постов.

Эти шаги превращаются в сложный QuerySet, который будет включать представление post_detail. Откройте файл views.py приложения блога и добавьте следующий импорт в верхней его части:

from django.db.models import Count

Это функция агрегации Count из ORM Django. Она позволяет осуществлять совокупный подсчет тегов. django.db.models включает следующие функции агрегации:

  • Avg: среднее значение
  • Max: максимальное значение
  • Min: минимальное значение
  • Count: подсчет объектов

Узнать больше об агрегации можно здесь: https://docs.djangoproject.com/en/2.0/topics/db/aggregation/.

Добавьте следующие строки в представление post_detail перед функцией render() со следующим уровнем отступа:

# Список похожих постов  
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]

Этот код выполняет следующее:

  1. Получаем список Python с ID тегов текущего поста. QuerySet values_list() возвращает кортеж со значениями для заданных полей. Передаем flat=True, чтобы получить список в формате [1, 2, 3, ...].
  2. Получаем все посты с одним из этих тегов, не включая текущий.
  3. Используем функцию агрегации Count для генерации поля same_tags, которое содержит количество общих тегов.
  4. Сортируем результаты по количество общих тегов (в порядке убывания) по publish, чтобы отображать последние [по дате публикации] посты в числе первых, если у нескольких одинаковое количество общих тегов. Обрезаем результаты, чтобы получить только первые четыре поста.

Добавьте объект similar_posts в словарь контекста для функции render():

return render(request,  
	      'blog/post/detail.html',  
	      {'post': post,  
	      'comments': comments,  
	      'new_comment': new_comment,  
	      'comment_form': comment_form,  
	      'similar_posts': similar_posts})

Теперь нужно отредактировать шаблон blog/post/detail.html и добавить следующий код перед списком комментариев к посту:

<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 %}

Теперь страница поста должна выглядеть так:

Рекомендация постов в django

Теперь можно рекомендовать похожие посты пользователям. django-taggit также включает менеджер similar_objects(), который можно использовать для получения постов с общими тегами. Посмотреть на все менеджеры django-taggit можно здесь: https://django-taggit.readthedocs.io/en/latest/api.html.

Также можно добавить список тегов на страницу поста по принципу шаблона blog/post/list.html.