Евгений Легоцкой30 апреля 2017 г. 12:17

Django - Урок 023. Like Dislike система с помощью GenericForeignKey

В статье по созданию системы закладок на Django был рассмотрен пример с использованием абстрактной модели для нескольких типов закладок, а именно для статей и комментариев к статьям. Также было акцентировано внимание на том, что поля моделей, который имели внешние ключи на различные модели, должны иметь одинаковые названия, чтобы поддерживать возможность создания единого интерфейса для добавления закладок. Это становится возможным благодаря так называемой "утиной типизации" (Duck typing) , которая подразумевает, что объекты, которые не имеют единой иерархии наследования, могут использоваться в одном и том же сценарии при наличии интерфейсов (методов) имеющих одинаковую сигнатуру.

Дословно принцип утиной типизации звучит следующим образом:

Если это выглядит как утка, плавает как утка и крякает как утка, то это, возможно, и есть утка.

If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck.

То есть имея методы с одной и той же сигнатурой, мы можем использовать объекты, не связанные иерархией наследования, в одном и том же контексте.

В данной же статье рассмотрим вариант, когда для создания системы Like Dislike используется не две различных таблицы для статей и комментариев, и даже не одна, которая будет содержать по внешнему ключу на статью или комментарий (то есть две колонки, и при этом будет заполняться только одна из колонок в зависимости от того, к какому типу контента относится активность пользователя), а одна таблица, которая будет содержать:

  • content_type - тип контента, к которому относится запись
  • object_id - ID записи
  • content_object - генерируемый внешний ключ на запись, по сути объект контента
  • прочие дополнительные поля

Модель данных LikeDislike с GenericForeignKey

Теперь подробнее рассмотрим, как может выглядеть модель данных, которая сможет работать с любым типом контента.

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey


class LikeDislike(models.Model):
    LIKE = 1
    DISLIKE = -1

    VOTES = (
        (DISLIKE, 'Не нравится'),
        (LIKE, 'Нравится')
    )

    vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES)
    user = models.ForeignKey(User, verbose_name=_("Пользователь"))

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()

    objects = LikeDislikeManager()

В данном коде присутствуют как обязательные поля для использования полиморфных связей, так и дополнительные поля, которые уже характеризуют тип активности пользователя.

Система Like Dislike в данном случае строится на принципе +1/-1, что можно будет использовать для голосований с подсчётом суммарного рейтинга. Также имеется внешний ключ на пользователя, который проголосовал за статью или комментарий.

Для учёта типа контента используется модель ContentType , которая применяется в admin панели Django и формирует логи. Фактически при создании моделей данных автоматически создаётся и запись ContentType для этой модели. Таким образом все таблицы учитываются в модели ContentType . На этом основывается система логирования действий администратора в Django. Данный внешний ключ присваивается полю content_type.

object_id содержит ID первичного ключа экземпляра модели, для которой создаётся связь.

content_object содержит поле для связи с любой моделью и он является классом GenericForeignKey . Если два предыдущих поля имеют названия отличные от content_type и object_id , то их необходимо передать в качестве аргументов в GenericForeignKey . Если не отличаются, то GenericForeignKey самостоятельно их определит и будет использовать их для создания полиморфных связей.

Также в модели присутствует поле objects, которому присваивается специальный менеджер модели, который облегчит работу по получению отдельно Like и Dislike счётчиков, а также их суммарного рейтинга.

LikeDislikeManager

Данный менеджер модели позволит забирать отдельно Like и Dislike записи для текущего likedislike_set статьи или комментария.

from django.db import models
from django.db.models import Sum


class LikeDislikeManager(models.Manager):
    use_for_related_fields = True

    def likes(self):
        # Забираем queryset с записями больше 0
        return self.get_queryset().filter(vote__gt=0)

    def dislikes(self):
        # Забираем queryset с записями меньше 0
        return self.get_queryset().filter(vote__lt=0)

    def sum_rating(self):
        # Забираем суммарный рейтинг
        return self.get_queryset().aggregate(Sum('vote')).get('vote__sum') or 0

Добавление связи в модели данных статей и комментариев

Добавление связей осуществляется с помощью класса GenericRelation, который в отличие от полей content_type , object_id , GenericForeignKey, не создают дополнительных миграций базы данных.

from django.db import models
from django.contrib.contenttypes.fields import GenericRelation


class Article(models.Model):
    votes = GenericRelation(LikeDislike, related_query_name='articles')


class Comment(models.Model):
    votes = GenericRelation(LikeDislike, related_query_name='comments')

В GenericRelation передано два аргумента:

  1. модель с полиморфными связями, как видите она одна и та же для обеих моделей.
  2. related_query_name - наименование модели, по которому можно будет делать реверсивные выборки. По умолчанию GenericForeignKey не сможет их делать.

Это означает, что при наличии related_query_name можно будет забрать голоса конкретного пользователя к статьям, или к комментариям с использованием сортироваки. А для простоты можно реализовать это в LikeDislikeManager, добавив ещё два метода.

    def articles(self):
        return self.get_queryset().filter(content_type__model='article').order_by('-articles__pub_date')

    def comments(self):
        return self.get_queryset().filter(content_type__model='comment').order_by('-comments__pub_date')

Обратите внимание на аргумент order_by. Без указания related_query_name такой аргумент выдал бы ошибку 500. А так можно забрать все лайки с сортировкой по дате публикации статей или комментариев. Для Like Dislike системы это не очень нужно, но для закладок может быть полезно.

views.py

А теперь рассмотрим представление, которое будет добавлять или удалять голос пользователя у статьи или комментария. Реализация голосования будет сделана с использованием AJAX запросов.

Суть в том, что в файле urls.py мы будем задавать для этого View модель данных, за которую голосуем, а также тип голос Like или Dislike.

Передавая запрос мы попытаемся забрать запись и если она существует, то либо установим тип голоса на противоположный, либо удалим голос. Если запись не существует, то добавим запись голоса с текущим типом Like или Dislike.

import json

from django.http import HttpResponse
from django.views import View
from django.contrib.contenttypes.models import ContentType

from ecore.models import LikeDislike


class VotesView(View):
    model = None    # Модель данных - Статьи или Комментарии
    vote_type = None # Тип комментария Like/Dislike

    def post(self, request, pk):
        obj = self.model.objects.get(pk=pk)
        # GenericForeignKey не поддерживает метод get_or_create
        try:
            likedislike = LikeDislike.objects.get(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, user=request.user)
            if likedislike.vote is not self.vote_type:
                likedislike.vote = self.vote_type
                likedislike.save(update_fields=['vote'])
                result = True
            else:
                likedislike.delete()
                result = False
        except LikeDislike.DoesNotExist:
            obj.votes.create(user=request.user, vote=self.vote_type)
            result = True

        return HttpResponse(
            json.dumps({
                "result": result,
                "like_count": obj.votes.likes().count(),
                "dislike_count": obj.votes.dislikes().count(),
                "sum_rating": obj.votes.sum_rating()
            }),
            content_type="application/json"
        )

urls.py

Запись регулярного выражения для URL определяет тип контента, за который голосуем, его первичный ключ и тип голоса. Таким образом получается 4 тип url.

from django.conf.urls import url
from django.contrib.auth.decorators import login_required

from . import views
from .models import LikeDislike
from knowledge.models import Article, Comment

app_name = 'ajax'
urlpatterns = [
    url(r'^article/(?P<pk>\d+)/like/$',
        login_required(views.VotesView.as_view(model=Article, vote_type=LikeDislike.LIKE)),
        name='article_like'),
    url(r'^article/(?P<pk>\d+)/dislike/$',
        login_required(views.VotesView.as_view(model=Article, vote_type=LikeDislike.DISLIKE)),
        name='article_dislike'),
    url(r'^comment/(?P<pk>\d+)/like/$',
        login_required(views.VotesView.as_view(model=Comment, vote_type=LikeDislike.LIKE)),
        name='comment_like'),
    url(r'^comment/(?P<pk>\d+)/dislike/$',
        login_required(views.VotesView.as_view(model=Comment, vote_type=LikeDislike.DISLIKE)),
        name='comment_dislike'),
]

html

html код в моём случае будет выглядеть следующим образом:

<ul>
    <li data-id="{{ like_obj.id }}" data-type="article" data-action="like" title="Нравится">
        <span class="glyphicon glyphicon-thumbs-up"></span>
        <span data-count="like">{{ like_obj.votes.likes.count }}</span>
    </li>
    <li data-id="{{ like_obj.id }}" data-type="article" data-action="dislike" title="Не нравится">
        <span class="glyphicon glyphicon-thumbs-down"></span>
        <span data-count="dislike">{{ like_obj.votes.dislikes.count }}</span>
    </li>
</ul>

В предыдущей статье был применён такой же принцип формирования счётчиков.

  • data-id - отвечает за pk контента, который можно добавлять в закладки.
  • data-type - тип контента, это же название фигурирует и в url.
  • data-action - действие, которое нужно совершить, в данном случае добавление в закладки
  • data-count - счётчик, показывающий сколько пользователей добавили контент в закладки

Отличие заключается в том, что для получения количества Like и Dislike используются не методы моделей данных статей и комментариев, а методы менеджера модели LikeDislikeManager , что ещё больше упрощает дальнейшую разработку, поскольку не нужно будет отслеживать, во всех ли моделях хватает методов. Достаточно будет добавить поле GenericRelation .

JavaScript

В предыдущей статье по закладкам на сайте я уже акцентировал внимание, что необходимо обрабатывать CSRF токен для AJAX-запросов. Поэтому не буду дублировать информацию.

Покажу только сами обработчики AJAX-запросов. Их будет 2. Один для Like, второй для Dislike.

function like()
{
    var like = $(this);
    var type = like.data('type');
    var pk = like.data('id');
    var action = like.data('action');
    var dislike = like.next();

    $.ajax({
        url : "/api/" + type +"/" + pk + "/" + action + "/",
        type : 'POST',
        data : { 'obj' : pk },

        success : function (json) {
            like.find("[data-count='like']").text(json.like_count);
            dislike.find("[data-count='dislike']").text(json.dislike_count);
        }
    });

    return false;
}

function dislike()
{
    var dislike = $(this);
    var type = dislike.data('type');
    var pk = dislike.data('id');
    var action = dislike.data('action');
    var like = dislike.prev();

    $.ajax({
        url : "/api/" + type +"/" + pk + "/" + action + "/",
        type : 'POST',
        data : { 'obj' : pk },

        success : function (json) {
            dislike.find("[data-count='dislike']").text(json.dislike_count);
            like.find("[data-count='like']").text(json.like_count);
        }
    });

    return false;
}

// Подключение обработчиков
$(function() {
    $('[data-action="like"]').click(like);
    $('[data-action="dislike"]').click(dislike);
});

Для Django рекомендую VDS-сервера хостера Timeweb .

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Поддержать автора Donate
ИМ

Доброго времени суток Евгений. Не подскажете как сделать иконки (лайк-дизлайк) кликабельными?

День добрый. А в каком смысле кликабельными? Вот у меня на сайте они кликаются например. Что конкретно у вас не работает?

ИМ

В прямом, иконки не кликабельные... просто как декор висят.

А что в консоли отладчика браузера? Может у вас JavaSсript с ошибками отрабатывает?

Ещё один момент, посмотрите вот в этой статье раздел про использование CSRF токена .
Возможно, что он не используется в Cookies, тогда AJAX работать не будет.
ИМ

Дико извиняюсь. Действительно в консоли посмотрел, они кликабельные. Только сыпятся ошбки при клике на $.ajax


  1. Там ошибка 404, напрягает наличие ещё одного слеша в url вашего API
  2. Проверьте, всё ли прописали в urls.py файле. Опять же из-за ошибки 404.
l
  • #
  • 25 июня 2018 г. 4:27

really nice work .thanks.

MU

Hi, works perfect, thank you.

I try to retrieve list of users who like eg. Post, but it don't work. How to do this?
I know how to retrieve list of user names but not name with avatar like on evileg.

I think, you made some mistake. Do you have likes or dislikes in admin panel? Or some more information?

Because, I don`t know, what you did in your code.

I suggest you create theme on forum in django section , and we can discuss your problem.

Здравствуйте. Ваша система очень хорошо работает.Спасибо.
Но у меня есть вопрос. Допустим, проголосовал пользователь или нет, можно проверить по условию "if post.votes.user == request.user". А как опредилить какой голос пользователь поставил (лайк или дизлайк)?

Добрый день.

Если у вас уже выбран объект голоса для конкретного пользователя, то можно проверить так.

if like_dislike_object.vote == LikeDislike.LIKE:
    # ToDo Something by Like
else:
    # ToDo Something by Dislike

Ну или проверить, лайкнул ли пользователь из всех лайков так

obj.votes.likes().filter(user=request.user)

Спасибо. А не подскажите как это в шаблоне проверить?

Лучше я, наверное, более подробно опишу. Есть несколько постов на странице и мне нужно проверить какой голос поставил пользователь. Если стоит лайк, то закрасить кнопку лайк, а если дизлайк, то кнопку дизлайк. Как это решить?

Я понял. Я вам позже отвечу. У меня это реализовано, но нужно посмотреть исходники сайта.

Пока что, мне пришло в голову такое решение.
Я добавил в LikeDislikeManager следующий метод:

def likers(self):
  like_dislike = self.get_queryset()
  users = []
  for item in like_dislike:
    if item.vote == 1:
      users.append(item.user)
  return users

И в шаблоне проверяю таким образом: {% if request.user in post.votes.likers %}.

я написал template tag, фильтр, через который делаю проверку прямо в шаблоне

@register.filter
def user_in(objects, user):
    if user.is_authenticated:
        return objects.filter(user=user).exists()
    return False

Для этого подгружаю модуль в шаблоне и проверяю наличие пользователя в queryset

{% load users_extras %}
<span class="mdi mdi-star mr-1 {% if obj.likes.all|user_in:user %}text-success{% endif %}"></span>

Плюс в том, что могу делать такую проверку для какой угодно модели данных, у которой поле пользователя называется user. И нет никакой зависимости на ModelManager

Спасибо большое.

Снова здравствуйте). Есть ещё вопрос. Как сохранить объект LikeDislike, при удалении статьи? Пробовал

content_type = models.ForeignKey(ContentType, null=True, blank=True, on_delete=models.SET_NULL)

Не помогло.

Добрый день!

А зачем его вообще сохранять, если статья удалена? Вообще объект LikeDislike как раз сохраняется... в этом-то и проблема связей GenericForeignKey, я бы как раз удалял это добро. А content_type - это внешний ключ на модель ContentType, то есть это будет влиять только при удалении самого типа контента статей, а не конкретной статьи.

Спасибо за помощь.

-"А зачем его вообще сохранять, если статья удалена?".

Мне нужно знать сколько лайков/дизлайков получал пользователь за все свои статьи, даже если он удалил какую-нибудь статью.

вообще GenericForeignKey не удаляется, если только вы там что-то не нахимичили. Проблема, как раз в обратном, чтобы его удалять и не было битых отношений.

Просто удалите статью с лайком и посмотрите, что там в админ панели, только учтите, что если имеете обращение к самой статье через лайк, то получите ошибку 500. Нужно обрабатывать эту проблему дополнительно.

Я бы ещё добавил тогда поле target_user, куда будет добавляться пользователь, который получил лайк, тогда при удалении статьи лайк будет соотноситься с сами пользователем.

-"вообще GenericForeignKey не удаляется, если только вы там что-то не нахимичили."

Если использовать ваш код, то при удалении статьи объект LikeDislike не будет удалятся?
Я правильно понял?

Просто, если это так, то очень странно. Потому что у меня LikeDislike удаляется.

да? я перепроверю. Но у меня вроде бы были проблемы с этим делом. Во всяком случае кое-какие объекты, например, теги оставляют битый мусор. Возможно в последних верссиях Джанго дело обстоит иначе уже, я начинал с Джанго 1.10.

Видимо всё из-за GenericRelation. Если его убрать со статьи, то объект LikeDislike не удаляется. Но если его убрать, то я не смогу обращаться к LikeDislike через статью. Допустим ваш template tag

@register.filter
def user_in(objects, user):
    if user.is_authenticated:
        return objects.filter(user=user).exists()
    return False
{% load users_extras %}
<span class="mdi mdi-star mr-1 {% if obj.likes.all|user_in:user %}text-success{% endif %}"></span>

не будет работать. Пока не понятно, как быть в такой ситуации.

Понятно тогда почему, вот что пишут в документации

Note also, that if you delete an object that has a GenericRelation, any objects which have a GenericForeignKey pointing at it will be deleted as well. In the example above, this means that if a Bookmark object were deleted, any TaggedItem objects pointing at it would be deleted at the same time.
Unlike ForeignKey, GenericForeignKey does not accept an on_delete argument to customize this behavior; if desired, you can avoid the cascade-deletion simply by not using GenericRelation, and alternate behavior can be provided via the pre_delete signal.

Это означает, что если просто удалить объект, например, так

Article.objects.filter(id=tearget_id).delete()

То лайк, дислайк останется. Аналогичное поведение будет в том случае, если удалить статью через админку.

Если же удалять через GenericRelation, что очевидно вы и делаете, то тогда лайк дислайк удаляется.

У меня сейчас нет возможности проверить, но попробуйте сделать удаление статьи через админ панель, а не как вы это делаете сейчас, полагаю, что у вас есть какой-то специальный функционал для этого.

Как раз таки я через админку и удалял. У меня был pre_delete signal, но я его закомментировал и попробовал снова. Результат такой же. При удалении статьи с полем GenericRelation, удаляется LikeDislike, а без этого поля не удаляется.

Видимо, придется писать какую-нибудь функцию для удаления.

Тогда я после работы посмотрю, как там обстоят дела. Дело в том, что в django-tagging как раз наоборот TaggedItem объекты остаются.

Можете пока тоже глянуть в исходники django-tagging, возможно это натолкнёт вас на идею. Я не раньше, чем часов через 8 смогу посмотреть туда.

Я перекопал все, что мог. В итоге пока пришел к тому, чтобы не использовать GenericRelation. Зато теперь все стало муторно.

Во вьюхе VoteView пришлось передалать:

LikeDislike.objects.create(content_type=content_type, vote=self.vote_type, object_id=obj.id, user=request.user, target_user=obj.user)
like_count = LikeDislike.objects.filter(content_type=content_type, object_id=obj.id, vote=LikeDislike.LIKE).count()
dislike_count = LikeDislike.objects.filter(content_type=content_type, object_id=obj.id, vote=LikeDislike.DISLIKE).count()

И пришлось ещё создать дополнительные template tag. Типо:

@register.filter
def like_in(obj, user):
    if user.is_authenticated:
        try:
            likedislike = LikeDislike.objects.get(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, user=user)
            if likedislike.vote == 1:
                return True
            return False
        except LikeDislike.DoesNotExist:
            return False
    return False

Вместо вашего user_in.

И для вывода количества лайков на статье.

@register.simple_tag
def like_count(obj):
    likedislike = LikeDislike.objects.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, vote=LikeDislike.LIKE)
    return likedislike.count()

Естественно ещё и для дизлайка пришлось добавить.

Да. Знаете, я там ошибался немного по поводу GenericRelation. В Tagging это не используется, поэтому и TaggedItem остаются.

Вам придётся отказаться от GenericRelation, если вы хотите получить тот функционал, который вам нужен.

Большое спасибо вам за помощь в решении моих вопросов.

MU

Hi, its possible to count likes and dislikes in this app something like reddit count?

Yes, it is possible.

For summary count you have here method sum_rating() in LikeDislikeManager

obj.votes.sum_rating()

Just, need to rewrite javascript methods. Just write something like this in JS

success : function (json) {
    summary_counter.find("[data-count='counter']").text(json.sum_rating);
}
MU

It's possible to simply add vote option for non logged users?

Yes. You can use IP Address of user instead of Foreign Key of logged user.

In this article you will see, how to get IP Address from request. For field you can use GenericIPAddressField

OK

тут view написан в class based view, если честно ничего не могу разобрать. Как это всё переписать в function view?

function view для модели Article и LikeDislike.LIKE будет выглядеть так

def like(request, pk):
    obj = Article.objects.get(pk=pk)
    try:
        likedislike = LikeDislike.objects.get(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, user=request.user)
        if likedislike.vote is not LikeDislike.LIKE:
            likedislike.vote = LikeDislike.LIKE
            likedislike.save(update_fields=['vote'])
            result = True
        else:
            likedislike.delete()
            result = False
    except LikeDislike.DoesNotExist:
        obj.votes.create(user=request.user, vote=LikeDislike.LIKE)
        result = True

    return HttpResponse(
        json.dumps({
            "result": result,
            "like_count": obj.votes.likes().count(),
            "dislike_count": obj.votes.dislikes().count(),
            "sum_rating": obj.votes.sum_rating()
        }),
        content_type="application/json"
    )

Для LikeDislike.DISLIKE соответственно заменить в том коде. Но это сплошная копипаста получается.

Приветствую вас Евгений , давно наблюда за развитием вашего замечательного портала, много полезно тут нашел , переодически зачитываюсь.
Теперь по сушеству, делаю портал и там идеально ложиться также по лайкам полиморфная модель (comments ,post,image,...)ну вот наткнулся на такую статью https://djbook.ru/examples/88/ Буду очень признателен , если скажете своё мнение.

Добрый день.

Да, я тоже читал ту статью в своё время и согласен с тем, что внешние ключи гораздо лучше, чем GenericForeignKey. Выборки в ряде случае работают быстрее.
Но лично мне проще для закладок, лайков и дислайков, уведомлений и прочего использовать GenericForeignKey в силу того, что в текущем состоянии проекта мне приходится совершать гораздо меньше телодвижений для внедрения новых связей к тем же самым лайкам и дислайкам, когда появляется новая модель данных. У меня просто есть соответствующий миксин с GenericRelation, который сразу разруливает все имена и устанавливает нужные связи. А с использованием ContentType очень большое количество кода удалось переписать в обобщённый код. Лично для меня это является более поддерживаемым решением, поскольку программным кодом портала я занимаюсь в одиночку. И так хватает мест, где есть риск что-то забыть или недописать.

В любом случае при росте проекта и добавлении новых моделей придётся добавлять новую колонку в полиморфную модель, как написано в той статье. Вот и посчитаем, например, для моего сайта, сколько колонок нужно было бы для одних только лайков и дислайков:

  • Section, Article, Comments
  • ForumSection, ForumTopic, ForumComment
  • SocialBookmarkSection, SocialBookmark, SocialBookmarkComment
  • TestSection, Test
  • IdeaSection, Idea, IdeaComment
  • Vacancy
  • Company

Получается по меньшей мере 16 колонок в полиморфной модели, в которой как минимум 15 всегда будут NULL. Я не уверен в том, как это будет влиять на размер баз данных, но мне кажется, что при большом проекте это может стать несколько избыточным, хранить большое количество NULL полей ради того, чтобы больше заботиться о целостности базы данных. На самом деле GenericRelation решает проблему целостности базы данных автоматически и при удалении контента также удаляет записи с GenericForeignKey. Поэтому н могу сказать, что я испытывал проблмы с этим.

Доброго времени суток.

Спасибо за хороший ответ, У меня ситуация така что в галлереи будет несколько миллионов фотографий с фильтрами и тегами , и я опасаюсь за производительност . Это основное что останавливает меня пере GenericForeignKey , пока остановился на первом способе в той статье(Альтернатива 1 - NULL поля в исходной таблице) , к сожалению не нашёл практического бенчмарка какого либо.

Кстати интересные темы нашёл тут https://emacsway.github.io/ru/django-framework/#django-models Может что полезного тоже Евгений найдёте

А как получить имя пользователя, который поставил лайк?
Думал так, но похоже что нет. {{ post.votes.likes.user.username }}

Думал так, но похоже что нет. {{ post.votes.likes.user.username }}

Это же QuerySet будет, а не отдельный единственный объект

{% for vote in post.votes %}
  {{ vote.user.username }}
{% endfor %}

Спасибо большое:)

s

Доброго времени суток!) Я случайно набрел на вашу статью, и она помогла мне решить некоторые мои трудности, я прошел за вами по шагам, в попытках адаптировать это под себя, и возник вопрос. У вас тут не рендениться шаблон, и как выводятся свежие данные по лайкам дизлайкам? в моем варианте, они начинают отображаться только после клика по кнопке. и дизлайк ведет себя, так же как лайк добавля или убирая лайк с того же юзера, при том не увеличивая дизлайки, я где-то ошибся?

$(function () {
$('.dislike').click(function(){
 const csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
});
    var dislike = $(this);
    var type = dislike.data('type');
    var pk = dislike.data('id');
    var action = dislike.data('action');
    var like = dislike.prev();

    $.ajax({
        url : "/votes/" + $(this).data('id') + "/dislike/",
        type : 'POST',
        data : { 'obj' : pk },

        success : function (json) {
            dislike.find("[data-count='dislike']").text(json.dislike_count);
            like.find("[data-count='like']").text(json.like_count);
        }
    });

    return false;
});
});

как вы понимаете, я новичок и в питон и джанго и в js. не рубите с плеча

<div class="col-md-3">
        <div class="card" style="width: 18rem; text-align: center;">
            <img src="{% static 'Thonks.png' %}" class="card-img-top">
            <div class="card-body">
                <h5 class="card-title">{{obj.name}}</h5>
            </div>
            <div class="btn-group col-md-12" role="group" aria-label="Basic example">
                <button type="button" class="btn btn-secondary like col-md-6"
                        data-id="{{ obj.id }}" data-type="article" data-action="like" title="Нравится">Like
                    <span data-count="like">{{ like_obj.votes.likes.count }}</span>
                </button>
                <button type="button" class="btn btn-secondary dislike col-md-6"
                        data-id="{{ obj.id }}" data-type="article" data-action="dislike" title="Не нравится">Dislike
                 <span data-count="dislike">{{ like_obj.votes.dislikes.count }}</span>
                </button>
            </div>
        </div>
    </div>

мой шаблон html

s

все, я со всем разобрался!) Извините!)

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
k
  • knobu
  • 23 сентября 2020 г. 2:34

C++ - Тест 006. Перечисления

  • Результат:60баллов,
  • Очки рейтинга-1
k
  • knobu
  • 23 сентября 2020 г. 2:21

C++ - Тест 005. Структуры и Классы

  • Результат:91баллов,
  • Очки рейтинга8
k
  • knobu
  • 23 сентября 2020 г. 2:16

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

QSqlTableModel выполняет ряд стандартных операций для одной таблицы из базы данных. Поэтому там и реализован функционал по удалению и редактированию. QSqlQueryModel позволяет выполнить запр…
VB

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

Добрый день. Хотел спросить вот что. Создал проект на основе QAbstractTableModel. В MainWindow cоответственно создал модель и связал с представлением. Поиск веду по списку элементов модели,…

QCheckBox в качестве делегата QTableView

До тех пор, пока у вас проект содержит только одну таблицу, или несколько то может быть. Когда их будет 1000 и чекбоксы в разных колонках, то без делегатов и переопределения возвращаемых ре…
D
  • Damir
  • 20 сентября 2020 г. 15:34

QCheckBox в качестве делегата QTableView

bool Node::setData(const QModelIndex& index, const QVariant& value, int role){ switch (index.column()) { case 0: switch (role) { case Qt::CheckStateRole:// <- т…
VB

Qt/C++ - Урок 004. QSqlTableModel или Как представить таблицу из БД в Qt?

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

Как в qml работать с динамически созданными потомками?

В QML есть сборщик мусора, он может удалять объекты не сразу а по ппрошествии времени. Попробуйте при удалении вызывать сборщик мусора принудительно через gc()
VB

Как запустить программу с базой данных PostgreSQL на другом компьютере

Не требует никакую библиотеку, запускается на других компьютерах, где не установлена PostgreSQL, но создать элемент невозможно, тем более отредактировать или удалить.
p
  • prod1s
  • 24 сентября 2020 г. 7:12

через QT не могу открыть файл SQLite

Вирішення знайшов. Вказав замість назви БД об'єкт класу QSqlDataBase для QSqlQuery. QSqlQuery m_query = QSqlQuery(qSqlDataBase); Після двох днів пошуку рішення, все-таки знайшов…
U

как скрыть елемент с копии виджета

Дело в том, что ui класса находится в private-секции... И из-вне доступ получить, не нарушая канонов - не получится) Можно конечно сделать что-то в духе #define private public, но это для истинн…

MyForm(forms.Form): - непонятка

лично я бы сделал так: в get_context_data я бы генерил основной контекст, в а методах get и post я бы уже добавлял туда формы, правильно инициализированные. Т.е. class MySettingsCha…
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB