Djnago post "like" functionality issue

105 Views Asked by At

I built posts "like" functionality in posts list page (where posts of a single user are displayed). Using examples form the book "Django by examples", I did ajax like button bellow every post. But it works incorrectly. In that example like button was made for single post page, and I tried to fit it for posts list page (many post in one page). When push the like button in the database everything is fine - I got plus one like for particular post. But in the front end stange things happen - likes number for all the posts is changing, as if all the posts are connected. And when I do Like and unlike, numbers of likes for all the post are changing to some big values. I think that happens becouse in this case Ajax uses the same class selector (instead of id) for all posts. I'm still not so good at Django and Ajax and can't find a way to make this work correctly. A spent long time of triying to fix this and in googling with no result. Any help appriciated.

Bellow is the code.

The post's model with the likes fields:

class Posts(models.Model):
    author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, blank=True)
    content = models.TextField()
    image = models.ImageField(upload_to="posts/%Y/%m/%d", null=True,blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    users_like = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                        related_name='images_liked',
                                        blank=True)
    total_likes = models.PositiveIntegerField(db_index=True,
                                              default=0)

    def __str__(self):
        return self.title

     
    def get_absolute_url(self):
        return reverse("post_detail", kwargs={"slug": self.slug})


def slug_generator(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = unique_slug_generator(instance)


pre_save.connect(slug_generator, sender=Posts)

The post_like function in user's view.py:

@ajax_required
@login_required
@require_POST
def post_like(request):
    post_id = request.POST.get('id')
    action = request.POST.get('action')
    if post_id and action:
        try:
            post = Posts.objects.get(id=post_id)
            if action == 'like':
                post.users_like.add(request.user)
                create_action(request.user, 'likes', post)
            else:
                post.users_like.remove(request.user)
            return JsonResponse({'status':'ok'})
        except:
            pass
    return JsonResponse({'status':'ok'})

The code in urls.py for this:

urlpatterns = [
    
    path('like/', views.post_like, name='like'),
...another urls...
]

The html for like button and counting likes:

{% with total_likes=post.users_like.count users_like=post.users_like.all %}
    <div class="image-info">
      <div>
        <span class="count">
         <span class="total">{{ total_likes }}</span>
         like{{ total_likes|pluralize }}
        </span>
        
        <a href="#" data-id="{{ post.id }}" data-action="{% if request.user in users_like %}un{% endif %}like" class="like button">
          {% if request.user not in users_like %}
            Like
          {% else %}
            Unlike
          {% endif %}
        </a>
      </div>
      
    </div>
    <div class="image-likes">
     
     
    </div>
  {% endwith %}

And in the same html file, where button is, in the bottom of it is the ajax code for likes functionality:

{% block domready %}
$('a.like').click(function(e){
    e.preventDefault();
    
    $.post('/like/',
      {
        id: $(this).data('id'),
        action: $(this).data('action')
      },
      function(data){
        if (data['status'] == 'ok')
        {
          var previous_action = $('a.like').data('action');

          // toggle data-action
          $('a.like').data('action', previous_action == 'like' ? 'unlike' : 'like');
          // toggle link text
          $('a.like').text(previous_action == 'like' ? 'Unlike' : 'Like');

          // update total likes
          var previous_likes = parseInt($('span.count .total').text());
          $('span.count .total').text(previous_action == 'like' ? previous_likes + 2 : previous_likes - 2);
          
        }
      }
    );
    
  });
{% endblock %}
1

There are 1 best solutions below

5
alstr On BEST ANSWER

You'll only want to modify the like button that has been clicked by getting it inside your click function. You could tidy up your code a bit, but to keep it similar you could do something like:

$('a.like').click(function(e){
    e.preventDefault();
    // Get the clicked button.
    const $clickedButton = $( this );
    
    $.post('/like/',
        {
            id: $clickedButton.data('id'),
            action: $clickedButton.data('action')
        }, 
        function(data) {
            if (data['status'] == 'ok') {
                // Update the clicked button only.
                var previous_action = $clickedButton.data('action');
                
                // toggle data-action
                $clickedButton.data('action', previous_action == 'like' ? 'unlike' : 'like');
                // toggle link text
                $clickedButton.text(previous_action == 'like' ? 'Unlike' : 'Like');

                // update total likes
                const $total = $clickedButton.prev('span.count').children('.total');
                var previous_likes = parseInt($total.text());
                $total.text(previous_action == 'like' ? previous_likes + 2 : previous_likes - 2);
            }
        }
    );
});

I'm not 100% sure why you're adding/subtracting 2 from the total rather than 1, but I don't have enough context.