Evgenii Legotckoi
Evgenii LegotckoiСәуір 30, 2017, 12:17 Т.Қ.

Django - Оқулық 023. GenericForeignKey көмегімен ұнатпау жүйесі сияқты

Джангодағы бетбелгі жүйесін құру мақаласында бетбелгілердің бірнеше түріне, атап айтқанда мақалалар мен мақалаларға түсініктемелерге арналған дерексіз үлгіні пайдалану мысалында қарастырылды. . Сондай-ақ, бетбелгілерді қосу үшін бірыңғай интерфейсті құру мүмкіндігін сақтау үшін әртүрлі үлгілердің сыртқы кілттері бар үлгілердің өрістері бірдей атауларға ие болуы керек екендігі баса айтылды. Бұл "duck typing" (Duck typing) деп аталатын мүмкіндіктің арқасында мүмкін болды, бұл бір мұрагерлік иерархиясы жоқ нысандарды бірдей сценарийде пайдалануға болатынын білдіреді, егер интерфейстер (әдістер) бар болса. бірдей қолтаңба.

Сөзбе-сөз, үйрек теру принципі келесідей:

> Егер ол үйрекке ұқсайтын болса, үйрек сияқты жүзсе және үйрек сияқты шабылса, онда бұл үйрек болуы мүмкін.
>
> Үйрекке ұқсайтын болса, үйрек сияқты жүзіп, үйрек сияқты дірілдеп тұрса, бұл үйрек шығар.
>
>

Яғни, бірдей қолтаңбасы бар әдістерге ие бола отырып, біз мұра иерархиясымен байланысы жоқ нысандарды бірдей контексте пайдалана аламыз.

Бұл мақалада біз ұнатпауды ұнатпау жүйесін жасау үшін мақалалар мен түсініктемелер үшін екі түрлі кесте пайдаланылмайтын, тіпті мақала немесе түсініктеме үшін сыртқы кілті (яғни, екі баған) пайдаланылмайтын опцияны қарастырамыз. , және пайдаланушы әрекеті қандай мазмұн түріне жататынына байланысты бағандардың біреуі ғана) және мыналарды қамтитын бір кесте:

  • мазмұн_түрі - жазба тиесілі мазмұн түрі
  • object_id - ID жазбалары
  • content_object - жазуға арналған сыртқы кілт, шын мәнінде мазмұн нысаны
  • басқа қосымша өрістер

GenericForeignKey көмегімен LikeDislike деректер үлгісі

Енді мазмұнның кез келген түрімен жұмыс істей алатын деректер үлгісі қандай болуы мүмкін екенін егжей-тегжейлі қарастырайық.

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

Бұл код полиморфты сілтемелерді пайдалану үшін міндетті өрістерді де, сондай-ақ пайдаланушы әрекетінің түрін сипаттайтын қосымша өрістерді де қамтиды.

Бұл жағдайда Ұнатпау жүйесі +1/-1 принципіне негізделген, оны жалпы рейтингті есептеу арқылы дауыс беру үшін пайдалануға болады. Сондай-ақ мақалаға немесе түсініктемеге дауыс берген пайдаланушының сыртқы кілті бар.

Мазмұн түрін есепке алу үшін Django басқару тақтасында қолданылатын және журналдарды жасайтын ContentType үлгісі пайдаланылады. Шындығында, деректер үлгілерін жасау кезінде осы үлгі үшін ContentType жазбасы автоматты түрде жасалады. Осылайша, барлық кестелер ContentType үлгісінде қарастырылады. Бұл Джангоның әкімші журналын тіркеу жүйесінің негізі. Бұл сыртқы кілт мазмұн_түрі. өрісіне тағайындалған.

object_id қатынас құрылып жатқан үлгі дананың негізгі кілт идентификаторын қамтиды.

content_object кез келген үлгіге сілтеме жасайтын өрісті қамтиды және GenericForeignKey сыныбы болып табылады. Алдыңғы екі өрісте мазмұн_түрі және объект_идентификаторы атауларынан басқа атаулар болса, оларды GenericForeignKey параметріне аргумент ретінде беру керек. Егер олар әртүрлі болмаса, GenericForeignKey оларды өздігінен анықтайды және оларды полиморфты қатынастарды құру үшін пайдаланады.

Модельде сондай-ақ арнайы үлгі менеджері тағайындалған нысандар өрісі бар, ол ұнату және ұнатпау есептегіштерін бөлек алу жұмысын, сондай-ақ олардың жалпы рейтингін жеңілдетеді.

LikeDislikeManager

Бұл үлгі менеджері ағымдағы 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

Мақала мен түсініктеме деректер үлгілеріне сілтеме қосу

Қатынастар content_type , object_id , GenericForeignKey, өрістерінен айырмашылығы, қосымша дерекқор тасымалдауларын жасамайтын GenericRelation сыныбы арқылы қосылады.

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. қатысты_сұрау_атауы - кері таңдаулар жасауға болатын үлгінің атауы. Әдепкі GenericForeignKey оларды орындай алмайды.

Бұл қатысты_сұрау_атауы болған жағдайда сұрыптау арқылы мақалаларға немесе түсініктемелерге белгілі бір пайдаланушының дауысын алуға болатынын білдіреді. Қарапайым болу үшін мұны тағы екі әдісті қосу арқылы 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')

тапсырыс_боюы. аргументіне назар аударыңыз қатысты_сұрау_атын көрсетпестен, мұндай аргумент 500 қатені тудырады. Әйтпесе, мақалалар немесе пікірлер жарияланған күні бойынша сұрыпталған барлық ұнатуларды ала аласыз. Ұнатпау жүйесі үшін бұл қажет емес, бірақ бетбелгілер үшін бұл пайдалы болуы мүмкін.

рет көру.py

Енді мақалаға немесе түсініктемеге пайдаланушының дауысын қосатын немесе өшіретін көріністі қарастырыңыз. Дауыс беруді іске асыру AJAX сұраулары арқылы орындалады.

Қорытындысы: urls.py файлында біз осы үшін біз дауыс беретін деректер үлгісін, сондай-ақ ұнату немесе ұнатпау дауысының түрін орнатамыз.

Сұрау жіберу арқылы біз жазбаны алуға тырысамыз, егер ол бар болса, дауыс түрін керісінше орнатамыз немесе дауысты өшіреміз. Жазба жоқ болса, ағымдағы ұнату немесе ұнатпау түрі бар дауыстық жазбаны қосыңыз.

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 мазмұнына жауапты.
  • деректер түрі - мазмұн түрі, URL мекенжайында бірдей атау пайда болады.
  • деректер-әрекет - орындалатын әрекет, бұл жағдайда бетбелгі қою
  • деректер саны - қанша пайдаланушы мазмұнды белгілегенін көрсететін есептегіш

Айырмашылық мынада: Ұнату және Ұнатпау сандарын алу үшін мақалалар мен пікірлердің деректер үлгілерінің әдістері емес, LikeDislikeManager үлгі менеджерінің әдістері қолданылады. , бұл әрі қарай дамуды одан әрі жеңілдетеді, өйткені барлық үлгілерде жеткілікті әдістер бар-жоғын қадағалау қажет емес. GenericRelation өрісін қосу жеткілікті болады.

JavaScript

Сайттағы бетбелгілердегі алдыңғы мақалада мен AJAX сұраулары үшін CSRF таңбалауышын өңдеу қажет екенін атап өттім. Сондықтан мен ақпаратты қайталамаймын.

Мен тек AJAX сұрау өңдеушілердің өздерін көрсетемін. Оның ішінде 2 болады.Біреуі лайк басу, екіншісі Дизлайк үшін.

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 үшін Timeweb хостының VDS-сервері ұсынамын.

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

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

ИМ
  • Ақп. 3, 2018, 8:54 Т.Қ.

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

Evgenii Legotckoi
  • Ақп. 4, 2018, 7:22 Т.Ж.

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

ИМ
  • Ақп. 4, 2018, 8:04 Т.Ж.

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

Evgenii Legotckoi
  • Ақп. 4, 2018, 8:09 Т.Ж.

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

Ещё один момент, посмотрите вот в этой статье раздел про использование CSRF токена .
Возможно, что он не используется в Cookies, тогда AJAX работать не будет.
ИМ
  • Ақп. 4, 2018, 8:55 Т.Ж.

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


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

really nice work .thanks.

MU
  • Жел. 25, 2018, 12:13 Т.Қ.

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
  • Жел. 25, 2018, 1:59 Т.Қ.

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.

Руслан Волшебник
  • Қаң. 4, 2019, 9:57 Т.Ж.

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

Evgenii Legotckoi
  • Қаң. 4, 2019, 10:07 Т.Ж.

Добрый день.

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

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

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

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

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

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

Evgenii Legotckoi
  • Қаң. 4, 2019, 11:25 Т.Ж.

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

Руслан Волшебник
  • Қаң. 5, 2019, 6:45 Т.Ж.
  • (өңделген)

Пока что, мне пришло в голову такое решение.
Я добавил в 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
  • Қаң. 5, 2019, 8:02 Т.Ж.
  • (өңделген)

я написал 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

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

Руслан Волшебник
  • Қаң. 8, 2019, 12:09 Т.Қ.

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

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

Не помогло.

Evgenii Legotckoi
  • Қаң. 8, 2019, 2:52 Т.Қ.

Добрый день!

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

Руслан Волшебник
  • Қаң. 9, 2019, 4:45 Т.Ж.

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

Руслан Волшебник
  • Қаң. 9, 2019, 4:50 Т.Ж.
  • (өңделген)

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

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

Evgenii Legotckoi
  • Қаң. 9, 2019, 4:57 Т.Ж.
  • (өңделген)

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

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

Evgenii Legotckoi
  • Қаң. 9, 2019, 4:59 Т.Ж.

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

Руслан Волшебник
  • Қаң. 9, 2019, 5:07 Т.Ж.

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

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

Руслан Волшебник
  • Қаң. 9, 2019, 5:08 Т.Ж.

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

Evgenii Legotckoi
  • Қаң. 9, 2019, 5:15 Т.Ж.

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

Руслан Волшебник
  • Қаң. 9, 2019, 5:37 Т.Ж.
  • (өңделген)

Видимо всё из-за 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>

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

Evgenii Legotckoi
  • Қаң. 9, 2019, 5:48 Т.Ж.

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

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, что очевидно вы и делаете, то тогда лайк дислайк удаляется.

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

Руслан Волшебник
  • Қаң. 9, 2019, 6:01 Т.Ж.

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

Руслан Волшебник
  • Қаң. 9, 2019, 6:02 Т.Ж.

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

Evgenii Legotckoi
  • Қаң. 9, 2019, 6:05 Т.Ж.
  • (өңделген)

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

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

Руслан Волшебник
  • Қаң. 9, 2019, 11:28 Т.Ж.

Я перекопал все, что мог. В итоге пока пришел к тому, чтобы не использовать 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()

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

Evgenii Legotckoi
  • Қаң. 9, 2019, 2:35 Т.Қ.

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

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

Руслан Волшебник
  • Қаң. 10, 2019, 3:16 Т.Ж.

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

MU
  • Наурыз 7, 2019, 1:58 Т.Қ.

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

Evgenii Legotckoi
  • Наурыз 8, 2019, 3:35 Т.Ж.
  • (өңделген)

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
  • Наурыз 20, 2019, 11:43 Т.Ж.

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

Evgenii Legotckoi
  • Наурыз 21, 2019, 4:12 Т.Ж.

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
  • Қыр. 10, 2019, 4:10 Т.Қ.

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

Evgenii Legotckoi
  • Қыр. 10, 2019, 4:38 Т.Қ.

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
  • Қыр. 14, 2019, 5:08 Т.Қ.

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

Evgenii Legotckoi
  • Қыр. 17, 2019, 3:23 Т.Ж.
  • (өңделген)

Добрый день.

Да, я тоже читал ту статью в своё время и согласен с тем, что внешние ключи гораздо лучше, чем 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
  • Қыр. 17, 2019, 4:50 Т.Ж.

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

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

Misha Lebedev
  • Қыр. 17, 2019, 6:07 Т.Ж.

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

Владислав Меленчук
  • Мамыр 29, 2020, 12:43 Т.Қ.

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

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

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

{% for vote in post.votes %}
  {{ vote.user.username }}
{% endfor %}
Владислав Меленчук
  • Мамыр 30, 2020, 2:38 Т.Ж.

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

s
  • Там. 4, 2020, 7:09 Т.Ж.
  • (өңделген)

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

$(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
  • Там. 4, 2020, 7:40 Т.Ж.

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

Владислав Меленчук
  • Мамыр 17, 2021, 11:30 Т.Ж.
  • (өңделген)

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

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

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

Evgenii Legotckoi
  • Шілде 2, 2021, 4:51 Т.Ж.

Наверное да

Evgenii Legotckoi
  • Шілде 2, 2021, 4:51 Т.Ж.

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

Владислав Меленчук
  • Шілде 2, 2021, 4:53 Т.Ж.

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

NSProject
  • Сәуір 2, 2022, 2:06 Т.Қ.
  • (өңделген)

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

СС
  • Сәуір 12, 2022, 7:36 Т.Ж.

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

NSProject
  • Сәуір 13, 2022, 5:21 Т.Ж.

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

Evgenii Legotckoi
  • Сәуір 17, 2022, 5:34 Т.Қ.
  • (өңделген)

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

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

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

NSProject
  • Сәуір 17, 2022, 9:54 Т.Қ.

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

Evgenii Legotckoi
  • Мамыр 17, 2022, 5:48 Т.Ж.

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

NSProject
  • Мамыр 27, 2022, 6:29 Т.Ж.

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

Evgenii Legotckoi
  • Мамыр 27, 2022, 8:37 Т.Ж.

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

NSProject
  • Мамыр 27, 2022, 10:58 Т.Ж.

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

Y
  • Там. 21, 2023, 6:38 Т.Қ.

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

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

Evgenii Legotckoi
  • Там. 22, 2023, 3:03 Т.Ж.

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

NSProject
  • Там. 24, 2023, 1:40 Т.Қ.
  • (өңделген)

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

from django.utils.translation import gettext_lazy as _

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

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

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

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз