Django База [2023]: Счетчик уникальных просмотров для статей 👀 #47
Веб-сайты часто отслеживают количество просмотров своих статей, чтобы измерять популярность контента и принимать решения о его дальнейшем развитии. В этой статье мы рассмотрим, как можно создать уникальный счетчик просмотров статей с помощью модели и миксина в Django.
Если вы хотите выразить благодарность автору сайта, статей и курса по Django, вы можете сделать это по ссылке ниже:
Модель для счетчика уникальных просмотров в Django
Первым шагом в создании счетчика просмотров статей является создание модели, которая будет хранить информацию о каждом просмотре статьи. Модель выглядит следующим образом:
from django.db import models
class ViewCount(models.Model):
"""
Модель просмотров для статей
"""
article = models.ForeignKey('Article', on_delete=models.CASCADE, related_name='views')
ip_address = models.GenericIPAddressField(verbose_name='IP адрес')
viewed_on = models.DateTimeField(auto_now_add=True, verbose_name='Дата просмотра')
class Meta:
ordering = ('-viewed_on',)
indexes = [models.Index(fields=['-viewed_on'])]
verbose_name = 'Просмотр'
verbose_name_plural = 'Просмотры'
def __str__(self):
return self.article.title
В этом коде мы создаем модель ViewCount
с тремя полями: article
, ip_address
и viewed_on
.
article
- это внешний ключ, связывающий просмотр с соответствующей статьей.ip_address
- это поле для хранения IP-адреса пользователя, который просмотрел статью.viewed_on
- это поле для хранения даты и времени просмотра статьи.
Мы также определяем два дополнительных параметра для нашей модели: Meta
и str()
. Параметр Meta
содержит информацию о сортировке и индексировании модели, а также о ее имени и множественном числе для отображения в административном интерфейсе. Параметр str()
определяет строковое представление объекта модели, которое будет отображаться в административном интерфейсе.
Не забываем про создание миграций: py manage.py makemigrations
и затем py manage.py migrate
.
Регистрация ViewCount в админ-панеле
Не забываем для просмотра данных добавить модель ViewCount
в админ-панель, следующим образом:
from django.contrib import admin
from .models import ViewCount
@admin.register(ViewCount)
class ViewCountAdmin(admin.ModelAdmin):
pass
По необходимости вы можете настроить необходимые поля для отображения.
Создание миксина для добавления просмотров к статье
Следующим шагом является создание миксина для создания объекта модели ViewCount
, при просмотре детальной статьи.
Миксин выглядит следующим образом, сам файл с миксинами я создал в modules/blog/mixins.py:
from .models import ViewCount
from modules.services.utils import get_client_ip
class ViewCountMixin:
"""
Миксин для увеличения счетчика просмотров статьи
"""
def get_object(self):
# получаем статью из метода родительского класса
obj = super().get_object()
# получаем IP-адрес пользователя
ip_address = get_client_ip(self.request)
# получаем или создаем запись о просмотре статьи для данного пользователя
ViewCount.objects.get_or_create(article=obj, ip_address=ip_address)
return obj
Данный код представляет миксин ViewCountMixin
, который является частью реализации уникального счетчика просмотров статей в Django.
Метод get_object(self)
переопределен в миксине ViewCountMixin
и используется для получения объекта статьи, на которую происходит просмотр. В данном коде метод get_object
получает объект статьи из родительского класса, используя метод super().get_object()
.
Далее, используя модель ViewCount
, мы получаем или создаем запись о просмотре статьи для данного пользователя, используя метод get_or_create()
. В этом методе мы передаем объект статьи и IP-адрес пользователя, чтобы создать новую запись о просмотре, если она еще не существует. Если же запись уже существует, мы ее получаем.
Наконец, метод get_object
возвращает объект статьи. При этом счетчик просмотров для данной статьи будет увеличен только в том случае, если просматривающий пользователь является уникальным. Если пользователь уже просматривал данную статью, то запись о просмотре не будет создана повторно, и счетчик просмотров не увеличится.
В коде мспользуя функцию get_client_ip
, получаем IP-адрес пользователя, который просматривает статью. Данную функцию мы добавляли в этом уроке.
Но я напомню, что функция у нас находится в папке модуля services, в файле utils.py. Выглядит функция следующим образом:
def get_client_ip(request):
"""
Получение IP адреса
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
Наследуем данный Mixin в представление (View)
Далее нам необходимо добавить данный миксин в представление, чтобы подсчеты начали работать при просмотре полной статьи. Для этого мы добавим миксин в представление ArticleDetailView в файле views.py:
from django.views.generic import DetailView
from .models import Article
from .mixins import ViewCountMixin
class ArticleDetailView(ViewCountMixin, DetailView):
model = Article
template_name = 'blog/articles_detail.html'
context_object_name = 'article'
queryset = model.objects.detail()
def get_similar_articles(self, obj):
article_tags_ids = obj.tags.values_list('id', flat=True)
similar_articles = Article.objects.filter(tags__in=article_tags_ids).exclude(id=obj.id)
similar_articles = similar_articles.annotate(related_tags=Count('tags')).order_by('-related_tags')
similar_articles_list = list(similar_articles.all())
random.shuffle(similar_articles_list)
return similar_articles_list[:6]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.object.title
context['form'] = CommentCreateForm
context['similar_articles'] = self.get_similar_articles(self.object)
return context
Добавление счетчика просмотров статьи
Далее мы должны добавить метод подсчета просмотров в модель Article, для этого мы добавим следующий фрагмент кода:
class Article(models.Model):
"""
Модель постов для сайта
"""
title = models.CharField(verbose_name='Заголовок', max_length=255)
slug = models.SlugField(verbose_name='Альт.название', max_length=255, blank=True, unique=True)
# Другие поля...
# Другие методы...
def get_view_count(self):
"""
Возвращает количество просмотров для данной статьи
"""
return self.views.count()
Таким образом, этот метод выведет количество просмотров к просматриваемой статье, осталось лишь добавить метод в шаблон.
Добавление счетчика просмотров в шаблон
Добавляем следующее поле в HTML разметку нашего шаблона:
{% extends 'main.html' %}
{% load static %}
{% block content %}
{% for article in articles %}
<div class="card mb-3">
<div class="row">
<div class="col-4">
<img src="{{ article.thumbnail.url }}" class="card-img-top" alt="{{ article.title }}">
</div>
<div class="col-8">
<div class="card-body">
<h5 class="card-title"><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h5>
<p class="card-text">{{ article.short_description|safe }}</p>
</hr>
Категория: <a href="{% url 'articles_by_category' article.category.slug %}">{{ article.category.title }}</a>
/ Добавил: {{ article.author.username }} / Просмотры: {{ article.get_view_count }}
</div>
</div>
<div class="rating-buttons">
<button class="btn btn-sm btn-primary" data-article="{{ article.id }}" data-value="1">Лайк</button>
<button class="btn btn-sm btn-secondary" data-article="{{ article.id }}" data-value="-1">Дизлайк</button>
<button class="btn btn-sm btn-secondary rating-sum">{{ article.get_sum_rating }}</button>
</div>
</div>
</div>
{% endfor %}
{% endblock %}
{% block script %}
<script src="{% static 'custom/js/ratings.js' %}"></script>
{% endblock%}
Отлично. Также нам нужно решить проблему N+1 характера.
Убираем N+1 в менеджере для счетчика просмотров
Для того, чтобы избавиться от нежелательных запросов в базу данных, нам необходимо добавить views
в наш кастомный менеджер статей, а именно в метод all()
.
class Article(models.Model):
"""
Модель постов для сайта
"""
class ArticleManager(models.Manager):
"""
Кастомный менеджер для модели статей
"""
def all(self):
"""
Список статей (SQL запрос с фильтрацией для страницы списка статей)
"""
return self.get_queryset().select_related('author', 'category').prefetch_related('ratings', 'views').filter(status='published')
def detail(self):
"""
Детальная статья (SQL запрос с фильтрацией для страницы со статьёй)
"""
return self.get_queryset()\
.select_related('author', 'category')\
.prefetch_related('comments', 'comments__author', 'comments__author__profile', 'tags')\
.filter(status='published')
STATUS_OPTIONS = (
('published', 'Опубликовано'),
('draft', 'Черновик')
)
title = models.CharField(verbose_name='Заголовок', max_length=255)
slug = models.SlugField(verbose_name='Альт.название', max_length=255, blank=True, unique=True)
# Другие поля...
# Другие методы...
def get_view_count(self):
"""
Возвращает количество просмотров для данной статьи
"""
return self.views.count()