ИМ
Nov. 30, 2018, 5:42 p.m.

Like Dislike система с помощью GenericForeignKey

django, Like, Dislike, Боль

Доброго времени суток форум. Год назад начал ваять проект под свои нужды и так вышло что забил на него немного. Вот решил продолжить и остановился на лайках.

Имею такие модели:

  1. class LikeDislikeManager(models.Manager):
  2. use_for_related_fields = True
  3. def likes(self):
  4. return self.get_queryset().filter(vote__gt=0)
  5. def dislikes(self):
  6. return self.get_queryset().filter(vote__lt=0)
  7. def sum_rating(self):
  8. return self.get_queryset().aggregate(Sum('vote')).get('vote__sum') or 0
  9.  
  10. def movies(self):
  11. return self.get_queryset().filter(content_type__model='movie').order_by('-movies__pub_date')
  12.  
  13. class LikeDislike(models.Model):
  14. class Meta():
  15. db_table = 'LikeDislike'
  16.  
  17. LIKE = 1
  18. DISLIKE = -1
  19. VOTES = (
  20. (DISLIKE, 'Не нравится'),
  21. (LIKE, 'Нравится')
  22. )
  23.  
  24. vote = models.SmallIntegerField('Голос', choices=VOTES)
  25. user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Пользователь")
  26. content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
  27. object_id = models.PositiveIntegerField()
  28. content_object = GenericForeignKey()
  29. objects = LikeDislikeManager()

Урлы:

  1. path('movie/<pk>/like/',
  2. login_required(views.VotesView.as_view(model=Movie, vote_type=LikeDislike.LIKE)),
  3. name='movie_like'),
  4. path('movie/<pk>/dislike/',
  5. login_required(views.VotesView.as_view(model=Movie, vote_type=LikeDislike.DISLIKE)),
  6. name='movie_dislike'),

Вьюха:

  1. class VotesView(View):
  2. model = None # Модель данных - Статьи или Комментарии
  3. vote_type = None # Тип комментария Like/Dislike
  4.  
  5. def movie(self, request, pk):
  6. obj = self.model.objects.get(pk=pk)
  7. # GenericForeignKey не поддерживает метод get_or_create
  8. try:
  9. likedislike = LikeDislike.objects.get(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, user=request.email)
  10. if likedislike.vote is not self.vote_type:
  11. likedislike.vote = self.vote_type
  12. likedislike.save(update_fields=['vote'])
  13. result = True
  14. else:
  15. likedislike.delete()
  16. result = False
  17. except LikeDislike.DoesNotExist:
  18. obj.votes.create(user=request.email, vote=self.vote_type)
  19. result = True
  20.  
  21. return HttpResponse(
  22. json.dumps({
  23. "result": result,
  24. "like_count": obj.votes.likes().count(),
  25. "dislike_count": obj.votes.dislikes().count(),
  26. "sum_rating": obj.votes.sum_rating()
  27. }),
  28. content_type="application/json"
  29. )

Шаблон:

  1. <ul>
  2. <li data-id="{{ like_obj.id }}" data-type="movie" data-action="like" title="Нравится">
  3. <span style="cursor: pointer" class="fa fa-thumbs-up"></span>
  4. <span data-count="like">{{ like_obj.votes.likes.count }}</span>
  5. </li>
  6. <li data-id="{{ like_obj.id }}" data-type="movie" data-action="dislike" title="Не нравится">
  7. <span style="cursor: pointer" class="fa fa-thumbs-down"></span>
  8. <span data-count="dislike">{{ like_obj.votes.dislikes.count }}</span>
  9. </li>
  10. </ul>

Ну и javascript:

  1. // Получение переменной cookie по имени
  2. function getCookie(name) {
  3. var cookieValue = null;
  4. if (document.cookie && document.cookie !== '') {
  5. var cookies = document.cookie.split(';');
  6. for (var i = 0; i < cookies.length; i++) {
  7. var cookie = jQuery.trim(cookies[i]);
  8. // Does this cookie string begin with the name we want?
  9. if (cookie.substring(0, name.length + 1) === (name + '=')) {
  10. cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
  11. break;
  12. }
  13. }
  14. }
  15. return cookieValue;
  16. }
  17.  
  18. // Настройка AJAX
  19. $(function () {
  20. $.ajaxSetup({
  21. headers: { "X-CSRFToken": getCookie("csrftoken") }
  22. });
  23. });
  24.  
  25.  
  26. function like()
  27. {
  28. var like = $(this);
  29. var type = like.data('type');
  30. var pk = like.data('id');
  31. var action = like.data('action');
  32. var dislike = like.next();
  33.  
  34. $.ajax({
  35. url : "/" + type +"/" + pk + "/" + action + "/",
  36. type : 'POST',
  37. data : { 'obj' : pk },
  38.  
  39. success : function (json) {
  40. like.find("[data-count='like']").text(json.like_count);
  41. dislike.find("[data-count='dislike']").text(json.dislike_count);
  42. }
  43. });
  44.  
  45. return false;
  46. }
  47.  
  48. function dislike()
  49. {
  50. var dislike = $(this);
  51. var type = dislike.data('type');
  52. var pk = dislike.data('id');
  53. var action = dislike.data('action');
  54. var like = dislike.prev();
  55.  
  56. $.ajax({
  57. url : "/" + type +"/" + pk + "/" + action + "/",
  58. type : 'POST',
  59. data : { 'obj' : pk },
  60.  
  61. success : function (json) {
  62. dislike.find("[data-count='dislike']").text(json.dislike_count);
  63. like.find("[data-count='like']").text(json.like_count);
  64. }
  65. });
  66.  
  67. return false;
  68. }
  69.  
  70. // Подключение обработчиков
  71. $(function() {
  72. $('[data-action="like"]').click(like);
  73. $('[data-action="dislike"]').click(dislike);
  74. });

При нажатии на лайк-дизлайк в консоле браузера получаю:

Прошу помощи в настройке сабжа.


2
The question is asked by the articleDjango - Tutorial 023. Like Dislike system using GenericForeignKey

Do you like it? Share on social networks!

11
Evgenii Legotckoi
  • Nov. 30, 2018, 5:52 p.m.

Добрый день!

У вас id объекта не подставляется в data-id.

Покажите полностью шаблон, где добавляете лайки и дислайки


    movie.html:

    1. {% extends 'base/base.html' %}
    2. {% block content %}
    3. <article>
    4. <h3>{{ movie.name }}</h3>
    5. <h6 class="text-secondary">{{ movie.orig_name }}</h6>
    6.  
    7. <div class="boxer">
    8. <div class="box-row">
    9. <div class="poster-movie"><img src="{{ movie.poster.url }}"></div>
    10. <div class="player">
    11. <video poster="/path/to/poster.jpg" controls>
    12. <source src="{{ movie.video.url }}" type="video/mp4">
    13. <!-- Captions are optional -->
    14. <track kind="captions" label="English captions" src="/path/to/captions.vtt" srclang="en" default>
    15. </video>
    16. </div>
    17. </div>
    18.  
    19. <div class="box-row">
    20. <div class="info">
    21. <div class="votes">
    22. <ul>
    23. <li data-id="{{ like_obj.id }}" data-type="movie" data-action="like" title="Нравится">
    24. <span style="cursor: pointer" class="fa fa-thumbs-up"></span>
    25. <span data-count="like">{{ like_obj.votes.likes.count }}</span>
    26. </li>
    27. <li data-id="{{ like_obj.id }}" data-type="movie" data-action="dislike" title="Не нравится">
    28. <span style="cursor: pointer" class="fa fa-thumbs-down"></span>
    29. <span data-count="dislike">{{ like_obj.votes.dislikes.count }}</span>
    30. </li>
    31. </ul>
    32. </div>
    33. <p><b>Год выхода:</b> {{ movie.year | date:"Y" }}</p>
    34. <p><b>Страна:</b> {{ movie.country }}</p>
    35. <p><b>Перевод:</b> {{ movie.translate }}</p>
    36.  
    37. </div>
    38. <div class="descr">
    39. <div class="discription">
    40. <h5>Описание:</h5>
    41. {{ movie.description|safe }}
    42. </div>
    43. </div>
    44. </div>
    45. </div>
    46.  
    47.  
    48.  
    49. </article>
    50. {% if popular_list %}
    51. <ul class="list-group">
    52. <li class="list-group-item active"><strong>Популярные публикации за неделю</strong></li>
    53. {% for popular_movie in popular_list %}
    54. <li class="list-group-item">
    55. <a href="{% url 'movie:movie_detail' popular_movie.movie_id %}">{{ popular_movie.movie__name }}</a>
    56. </li>
    57. {% endfor %}
    58. </ul>
    59. {% endif %}
    60.  
    61. <h2>Комментарии</h2>
    62. {% for comment in comments %}
    63. <a name="comment-{{ comment.id }}"></a>
    64. <div class="row" id="{{ comment.id }}">
    65. <div class="col-md-{{ comment.get_col }} col-md-offset-{{ comment.get_offset }}">
    66. <div class="panel panel-default">
    67. <div class="panel-heading">
    68. <strong>{{ comment.author_id.get_email|default:comment.author_id.username }}</strong>  
    69. {{ comment.pub_date }}
    70. <a href="#comment-{{ comment.id }}">#</a>
    71. </div>
    72. <div class="panel-body">
    73. <div>{{ comment.content|safe }}</div>
    74. {% if form %}<a class="btn btn-default btn-xs pull-right"
    75. onclick="return show_comments_form({{ comment.id }})">
    76.   Ответить</a>
    77. {% endif %}
    78. </div>
    79. </div>
    80. </div>
    81. </div>
    82. {% endfor %}
    83.  
    84. {% if form %}
    85. <h3 id="write_comment"><a onclick="return show_comments_form('write_comment')">Написать комментарий</a></h3>
    86. <form id="comment_form" action="{% url 'movie:add_comment' movie.id %}" method="post">
    87. {% csrf_token %}
    88. {{ form }}
    89. <button type="submit" class="btn btn-secondary">Комментировать</button>
    90. </form>
    91. {% else %}
    92. <div class="panel panel-warning">
    93. <div class="panel-heading">
    94. <h3 class="panel-title">Комментарии</h3>
    95. </div>
    96. <div class="panel-body">
    97. Только авторизованные пользователи могут оставлять комментарии.<br />
    98. </div>
    99. </div>
    100. {% endif %}
    101.  
    102. {% endblock %}
      Evgenii Legotckoi
      • Nov. 30, 2018, 6:02 p.m.
      • The answer was marked as a solution.

      Замените like_obj на movie

        Теперь id передается но ошибка осталась

        1. http://127.0.0.1:8000/movie/1/dislike/ 404 (Not Found)
          ИМ
          • Nov. 30, 2018, 6:28 p.m.

          Немного была путаница в урлах, сейчас оини приобрели такой вид:

          1. path('<pk>/like/',
          2. login_required(views.VotesView.as_view(model=Movie, vote_type=LikeDislike.LIKE)),
          3. name='movie_like'),
          4. path('<pk>/dislike/',
          5. login_required(views.VotesView.as_view(model=Movie, vote_type=LikeDislike.DISLIKE)),
          6. name='movie_dislike'),

          Сейчас ошибка в консоле:

          1. http://127.0.0.1:8000/movie/1/like/ 405 (Method Not Allowed)



            Вот это

            1. def movie(self, request, pk):

            поменять на это

            1. def post(self, request, pk):

              Ситация не изменилась. Пробовал пройтись по той же ссылке:

              1. http://127.0.0.1:8000/movie/1/like/
              1. Таже ошибка HTTP ERROR 405


              Даже когда меняю на несуществующий id




                сдаётся мне, что вы рано поменяли url, там правильно было, у вас в последнем вариант по ходу теперь отсутствует movie, либо urls для лайков и дислайков подключается в каком-то месте, где добавляются впереди url ещё какие-то параметры, которые там не должны быть.

                  Сменил обратно урлы только не вижу теперь логики.

                  1. user/
                  2. compilation/
                  3. movie/ <movie_id>/ [name='movie_detail']
                  4. movie/ comment/<movie_id>/ [name='add_comment']
                  5. movie/ <pk>/bookmark/ [name='movie_bookmark']
                  6. movie/ movie/<pk>/like/ [name='movie_like']
                  7. movie/ movie/<pk>/dislike/ [name='movie_dislike']
                  8. admin/
                  9. [name='index']
                  10. ^media\/(?P<path>.*)$
                  11. The current path, movie/1/like/, didn't match any of these.

                  По идее ссылка должна формироваться вида:

                  1. /приложение/id/like/

                  У меня же сейчас:

                  1. /приложение/приложение/id/like/

                  И даже при таком раскладе ошибка 405.


                    Evgenii Legotckoi
                    • Nov. 30, 2018, 10:11 p.m.

                    так. теперь больше информации. значит всё-таки внутри приложения.

                    Как будто, у вас запрос улетает не в ту вьшку, ошибка 405 обычно выскакивает в том случае, если не был записан get или post метод, но при этом выполняется get или post запрос.

                    Поэтому вот этот код был априори неправильный

                    1. class VotesView(View):
                    2. model = None # Модель данных - Статьи или Комментарии
                    3. vote_type = None # Тип комментария Like/Dislike
                    4. def movie(self, request, pk):

                    Нужно было написать так

                    1. class VotesView(View):
                    2. model = None # Модель данных - Статьи или Комментарии
                    3. vote_type = None # Тип комментария Like/Dislike
                    4. def post(self, request, pk):

                    Но почему при последнем вашем изменении и данном исправлении всё ещё вылетает ошибка 405, я ума не приложу, единственное что может быть в этом случае, так это неправильная маршрутизация. Попробуйте поизменять порядок объявления маршрутов. Я с таким поведением Django уже сталкивался. Нужно самые длинные маршруты объявлять первыми, иначе может уйти в неправильную вьюшку.

                    А вообще API для лайков дислайков и прочего у меня реализовано в отдельном приложении. Поскольку при подключении мультиязычности на сайте появляются проблемы с редиректами запроса и определения языка через javascript для правильного запроса, в общем много лишней мороки, лучше отдельное приложения для подобного.



                      Вернул в зад урлы. И просто перезагрузил wsgi сервер и все заработало. Спасибо вам Евгений за то что помогли разобраться.

                        Comments

                        Only authorized users can post comments.
                        Please, Log in or Sign up
                        • Last comments
                        • AK
                          April 1, 2025, 11:41 a.m.
                          Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
                        • Evgenii Legotckoi
                          March 9, 2025, 9:02 p.m.
                          К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
                        • VP
                          March 9, 2025, 4:14 p.m.
                          Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
                        • ИМ
                          Nov. 22, 2024, 9:51 p.m.
                          Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
                        • Evgenii Legotckoi
                          Oct. 31, 2024, 11:37 p.m.
                          Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup