Evgenii Legotckoi
Evgenii LegotckoiApril 30, 2017, 12:17 p.m.

Django - Tutorial 023. Like Dislike system using GenericForeignKey

In the article on creating a system of bookmarks on Django, an example was considered with the use of an abstract model for several types of bookmarks, namely for articles and comments on articles. Attention was also drawn to the fact that the model fields that had foreign keys to different models should have the same names to support the possibility of creating a single interface for adding bookmarks. This is made possible by the so-called "duck typing", which implies that objects that do not have a single inheritance hierarchy can be used in the same scenario if there are interfaces (methods) that have the same signature.

Literally the principle of duck typing sounds like this:

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

That is, having methods with the same signature, we can use objects that are not associated with an inheritance hierarchy, in the same context.

In the same article, let's consider the variant when using the Like Dislike system, not two different tables for articles and comments are used, and not even one that will contain a foreign key for an article or comment (that is, two columns, One of the columns, depending on what type of content the user activity is related to), and one table that will contain:

  • content_type - The content type to which the record belongs
  • object_id - Record ID
  • content_object - The generated foreign key to write, in fact the content object
  • Other additional fields

The LikeDislike Data Model with GenericForeignKey

Now let's take a closer look at what a data model can look like that can work with any type of content.

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, 'Dislike'),
        (LIKE, '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()

In this code there are both mandatory fields for using polymorphic links, and additional fields that already characterize the type of user activity.

The Like Dislike system in this case is based on the principle +1/-1, which can be used for voting with the calculation of the total rating. There is also a foreign key to the user who voted for the article or comment.

To account for the content type, the ContentType model is used, which is used in the Django admin panel and generates logs. In fact, when creating data models, the ContentType entry for this model is automatically created. Thus, all tables are taken into account in the ContentType model. This is the basis for the system for logging administrator actions in Django . This foreign key is assigned to the content_type field.

object_id contains the primary key ID of the model instance for which the relationship is created.

content_object contains a field for communication with any model and it is the GenericForeignKey class. If the two previous fields have names other than content_type and object_id , then they must be passed as arguments to GenericForeignKey . If not different, then GenericForeignKey will independently define them and will use them to create polymorphic relationships.

Also in the model there is a field of objects, which is assigned a special model manager, which will facilitate the work of getting separately Like and Dislike counters, as well as their total rating.

LikeDislikeManager

This model manager will allow you to take separately Like and Dislike entries for the current likedislike_set article or comment.

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


class LikeDislikeManager(models.Manager):
    use_for_related_fields = True

    def likes(self):
        # We take the queryset with records greater than 0
        return self.get_queryset().filter(vote__gt=0)

    def dislikes(self):
        # We take the queryset with records less than 0
        return self.get_queryset().filter(vote__lt=0)

    def sum_rating(self):
        # We take the total rating
        return self.get_queryset().aggregate(Sum('vote')).get('vote__sum') or 0

Adding a link to the article and comment model

Adding links is done using the GenericRelation class, which, unlike the fields content_type , object_id , GenericForeignKey , does not create additional database migrations.

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')

Two arguments are passed to GenericRelation:

  1. Model with polymorphic connections, as you can see it is the same for both models.
  2. related_query_name - The name of the model, according to which it will be possible to do reverse sampling. By default, GenericForeignKey can not do them.

This means that if you have the related_query_name , you can pick up the voices of a particular user to articles, or to comments using sorting. And for simplicity, you can implement it in LikeDislikeManager , adding two more methods.

    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')

Pay attention to the argument order_by . Without specifying the related_query_name , such an argument would give a 500 error. And so you can pick up all the likes with sorting by the date of publication of articles or comments. For a Like Dislike system, this is not very necessary, but for bookmarks it can be useful.

views.py

And now consider a view that will add or remove a user's voice from an article or comment. The implementation of the vote will be made using AJAX requests.

The main point is that in the file urls.py we will set the data model for this View, for which we vote, and also the type of the Like or Dislike voice.

Sending the request, we try to take the record and if it exists, then we will set the voice type to the opposite one, or we will delete the voice. If the entry does not exist, then add a voice record with the current Like or Dislike type.

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    # Data Model - Articles or Comments
    vote_type = None # Vote type Like/Dislike

    def post(self, request, pk):
        obj = self.model.objects.get(pk=pk)
        # GenericForeignKey does not support 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

A regular expression record for a URL determines the type of content for which we vote, its primary key and the type of voice. This produces 4 types of 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

The html code in my case will look like this:

<ul>
    <li data-id="{{ like_obj.id }}" data-type="article" data-action="like" title="Like">
        <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="Dislike">
        <span class="glyphicon glyphicon-thumbs-down"></span>
        <span data-count="dislike">{{ like_obj.votes.dislikes.count }}</span>
    </li>
</ul>

In the previous article , the same principle of forming counters was applied.

  • data-id - Responsible for pk content, which can be added to the bookmarks.
  • data-type - Type of content, the same name appears in the url.
  • data-action - The action to be taken, in this case adding to the bookmarks
  • data-count - Counter that shows how many users have added content to bookmarks

The difference is that to get the amount of Like and Dislike , not the methods of the models of these articles and comments, but the methods of the LikeDislikeManager , which further simplifies further development, since it will not be necessary to keep track of whether all the models have enough methods. It is enough to add the GenericRelation field.

JavaScript

In the previous article on bookmarks on the site, I already told that it is necessary to handle the CSRF token for AJAX-requests. Therefore, I will not duplicate the information.

I will show only the handlers of AJAX-requests. There will be two. One for Like, the second for 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;
}

// Connecting Handlers
$(function() {
    $('[data-action="like"]').click(like);
    $('[data-action="dislike"]').click(dislike);
});

For Django I recommend VDS-server of Timeweb hoster .

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

ИМ
  • Feb. 3, 2018, 8:54 p.m.

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

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

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

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

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

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


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

really nice work .thanks.

MU
  • Dec. 25, 2018, 12:13 p.m.

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.

Evgenii Legotckoi
  • Dec. 25, 2018, 1:59 p.m.

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.

Руслан Волшебник
  • Jan. 4, 2019, 9:57 a.m.

Здравствуйте. Ваша система очень хорошо работает.Спасибо.
Но у меня есть вопрос. Допустим, проголосовал пользователь или нет, можно проверить по условию "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 %}.

Evgenii Legotckoi
  • Jan. 5, 2019, 8:02 a.m.
  • (edited)

я написал 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, то есть это будет влиять только при удалении самого типа контента статей, а не конкретной статьи.

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

Руслан Волшебник
  • Jan. 9, 2019, 4:50 a.m.
  • (edited)

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

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

Evgenii Legotckoi
  • Jan. 9, 2019, 4:57 a.m.
  • (edited)

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

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

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

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

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

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

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

Руслан Волшебник
  • Jan. 9, 2019, 5:37 a.m.
  • (edited)

Видимо всё из-за 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, а без этого поля не удаляется.

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

Evgenii Legotckoi
  • Jan. 9, 2019, 6:05 a.m.
  • (edited)

Тогда я после работы посмотрю, как там обстоят дела. Дело в том, что в 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
  • March 7, 2019, 1:58 p.m.

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

Evgenii Legotckoi
  • March 8, 2019, 3:35 a.m.
  • (edited)

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
  • March 20, 2019, 11:43 a.m.

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

Evgenii Legotckoi
  • March 21, 2019, 4:12 a.m.

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
  • Sept. 10, 2019, 4:10 p.m.

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

Evgenii Legotckoi
  • Sept. 10, 2019, 4:38 p.m.

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 соответственно заменить в том коде. Но это сплошная копипаста получается.

Misha Lebedev
  • Sept. 14, 2019, 5:08 p.m.

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

Evgenii Legotckoi
  • Sept. 17, 2019, 3:23 a.m.
  • (edited)

Добрый день.

Да, я тоже читал ту статью в своё время и согласен с тем, что внешние ключи гораздо лучше, чем 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. Поэтому н могу сказать, что я испытывал проблмы с этим.

Misha Lebedev
  • Sept. 17, 2019, 4:50 a.m.

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

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

Misha Lebedev
  • Sept. 17, 2019, 6:07 a.m.

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

Владислав Меленчук
  • May 29, 2020, 12:43 p.m.

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

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

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

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

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

s
  • Aug. 4, 2020, 7:09 a.m.
  • (edited)

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

$(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
  • Aug. 4, 2020, 7:40 a.m.

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

Владислав Меленчук
  • May 17, 2021, 11:30 a.m.
  • (edited)

А так для общей суммы пойдёт?

    @property
    def total_rate(self):
        return sum([rating.vote for rating in self.rating.all()])
Владислав Меленчук
  • May 24, 2021, 11:16 a.m.

У меня ещё вопросик, а не подскажите, как выводить окошко авторизоваться, если вы не авторизованы и хотите поставить лайк? Как его вообще подхватит?

Наверное да

У меня разный рендеринг для авторизованного и не авторизованного пользователя. Определяется при запросе страницы.

благодарю за ответ

NSProject
  • April 2, 2022, 2:06 p.m.
  • (edited)

Меня интересует только один вопрос в плате SQL запросов. Их почему то плодится очень много с системой лайков и дизлайков

СС
  • April 12, 2022, 7:36 a.m.

Решили вопрос? Тоже интересует оптимизация запросов к БД

NSProject
  • April 13, 2022, 5:21 a.m.

Нет я пока что не разбирался с этим. Решил заняться оптимизацией после того как закончу проэкт. Но мне бы всё же хотелось узнать мнение автора.

Evgenii Legotckoi
  • April 17, 2022, 5:34 p.m.
  • (edited)

В случае Generic ключей имеется проблема в том, что ключ формируется по id и названию таблицы, в итоге у Django меньше возможностей для оптимизированного запроса. ЭТо один из недостатков GenericForeignKey.

Саму по себе проблему оптимизации можно попытаться решить следующими методами описанными в этой статье

Но всё равно могут остаться некоторые дополнительные запросы к базе данных. Поэтому можно кэшировать. Но стандартные средства кэширования не подойдут. Лично я разработал для этого специальный декоратор. Об этом можно почитать в этой статье EVILEG-CORE. Кэширование свойств объектов моделей с помощью model_cached_property

NSProject
  • April 17, 2022, 9:54 p.m.

Я пытался решить всё с помощью вашей статьи про оптимизацию. Увы не работает. Всё так же на 5 записей 10 запросов к бд. Причём если пытатся оптимизировать с помощью Prefetch запросов становится ещё больше.

Evgenii Legotckoi
  • May 17, 2022, 5:48 a.m.

Недостаток GenericForeignKey в том, что они достаточно плохо оптимизируются, поэтому я решил это с помощью кэширования.
Собственно говоря, эти проблемы и послужили возникновению специального декоратора для кэширования свойств объектов моделей.
Можете почитать здесь Кэширование свойств объектов моделей с помощью model_cached_property

NSProject
  • May 27, 2022, 6:29 a.m.

Спасибо. Я подумаю над таким решением. Если не ошибаюсь EVILEG_CORE на github выложен?

Evgenii Legotckoi
  • May 27, 2022, 8:37 a.m.

Да, можете выдрать сам декоратор оттуда, а то у меня руки не доходят переписать его для актуальной версии Django, там есть deprecated вещи

NSProject
  • May 27, 2022, 10:58 a.m.

Хорошо. Большое спасибо. Посмотрю что из этого получится.

Y
  • Aug. 21, 2023, 6:38 p.m.

у меня ошибка почемуто в модели

models.py", line 70, in LikeDislike
vote = models.SmallIntegerField(verbose_name= ("Голос"), choices=VOTES)
NameError: name '
' is not defined
Подскажите пожалуйсто как исправить?

Evgenii Legotckoi
  • Aug. 22, 2023, 3:03 a.m.

Ошибка скорее всего на другой строке, но интерпретатор понял этого так. Может где-то лишний пробел добавили или кавычка не та. Во всяком случае из этого лога не ясно, в чём ошибка.

NSProject
  • Aug. 24, 2023, 1:40 p.m.
  • (edited)

Ваша ошибка связана с gettext

from django.utils.translation import gettext_lazy as _

Поле должно выглядеть так

vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES) 

Так как вы просто забыли _ и по этому вышла ошибка

Comments

Only authorized users can post comments.
Please, Log in or Sign up
d
  • dsfs
  • April 26, 2024, 2:56 p.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
d
  • dsfs
  • April 26, 2024, 2:45 p.m.

C++ - Test 002. Constants

  • Result:50points,
  • Rating points-4
d
  • dsfs
  • April 26, 2024, 2:35 p.m.

C++ - Test 001. The first program and data types

  • Result:73points,
  • Rating points1
Last comments
k
kmssrFeb. 9, 2024, 5:43 a.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 9:30 p.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 7:38 p.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 19, 2023, 8:01 a.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
G
GarApril 22, 2024, 3:46 p.m.
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil AcademicsApril 20, 2024, 5:45 p.m.
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasovApril 14, 2024, 4:41 p.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел ДорофеевApril 14, 2024, 12:35 p.m.
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrexApril 4, 2024, 2:47 p.m.
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Follow us in social networks