На предыдущем уроке был реализован функционал комментариев. Теперь улучшим внешний вид формы и вывода комментариев, добавив систему «лайки/дизлайки».
Сложность реализации
Основная сложность — отсутствие авторизации и регистрации пользователей. Без идентификации пользователей, один и тот же клиент может многократно ставить лайки/дизлайки с одного IP-адреса, искажая статистику. Поэтому ограничим возможность ставить лайк/дизлайк с одного IP-адреса до одного раза. Для этого будем использовать IP-адрес клиента.
Модель для хранения лайков
Создадим модель Like для хранения информации о лайках. Она будет содержать IP-адрес пользователя и связь с конкретным постом.
class Like(models.Model):
"""Лайки"""
ip = models.CharField(max_length=100, verbose_name='IP адрес')
post = models.ForeignKey('Post', on_delete=models.CASCADE, verbose_name='Публикация')
Модель наследуется от models.Model. Атрибут ip хранит IP-адрес (тип CharField с ограничением длины). post — связь с моделью Post (используется ForeignKey с on_delete=models.CASCADE для каскадного удаления лайков при удалении поста). Модель не будет отображаться в админ-панели, так как просмотр IP-адресов не обязателен в этом проекте.
После создания модели выполним миграции:
python manage.py makemigrations
python manage.py migrate
Получение IP-адреса клиента
Функция get_client_ip получает IP-адрес клиента из запроса. Получение IP-адреса не всегда тривиально, так как может использоваться прокси-сервер. Функция пытается получить реальный IP-адрес из заголовка HTTP_X_FORWARDED_FOR, а в случае неудачи — из заголовка REMOTE_ADDR.
def get_client_ip(request):
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
Представления для лайков и дизлайков
Создадим представления LikeView и DislikeView для обработки лайков и дизлайков. Они получают IP-адрес клиента и ID поста. Если лайк от данного IP-адреса для данного поста уже существует, происходит редирект на ту же страницу. В противном случае, лайк добавляется в базу данных, и также происходит редирект. Для дизлайков, если запись существует, она удаляется; иначе, происходит редирект.
class LikeView(View):
def get(self, request, pk):
client_ip = get_client_ip(request)
try:
like = Like.objects.get(ip=client_ip, post_id=pk)
return redirect(reverse('post_detail', args=[pk]))
except Like.DoesNotExist:
Like.objects.create(ip=client_ip, post_id=pk)
return redirect(reverse('post_detail', args=[pk]))
class DislikeView(View):
def get(self, request, pk):
client_ip = get_client_ip(request)
try:
like = Like.objects.get(ip=client_ip, post_id=pk)
like.delete()
return redirect(reverse('post_detail', args=[pk]))
except Like.DoesNotExist:
return redirect(reverse('post_detail', args=[pk]))
Настройка URL
Добавим URL-паттерны для новых представлений:
path('<int:pk>/like/', LikeView.as_view(), name='add_like'),
path('<int:pk>/dislike/', DislikeView.as_view(), name='del_like'),
Изменение шаблона
В шаблоне post_detail.html добавим блок для отображения количества лайков и кнопок «нравится» и «не нравится»:
<div>
<br>
<p>Понравилось: {{ post.likes.count }}</p>
<br>
<a href="{% url 'add_like' post.pk %}">Нравится</a>
<a href="{% url 'del_like' post.pk %}">Не нравится</a>
</div>
Добавление стилей CSS
Для улучшения внешнего вида формы и комментариев добавим стили CSS в файл styles.css. Примеры стилей приведены [в источнике/видео].
В этом уроке реализован функционал лайков/дизлайков с ограничением по одному лайку/дизлайку с одного IP-адреса. Создана модель для хранения лайков, написаны представления для обработки запросов, внесены изменения в шаблон и CSS для улучшения внешнего вида.