'Can not get comment id with AJAX in django

I'm fighting with this problem during several days and can not find the solution for my case. I'm trying to make system of likes without refreshing the page. In synchronous mode system of likes and dislikes works fine, but when I'm trying to add AJAX, I'm getting 405 and only one last comment is working for one click, I understand problem that Ajax doesn't understand django urls with id or pk like my variant {% url 'store:like' comment.pk %} , but how it can be solved?

There is this part from the template:

{% for comment in comments %}
       <h6 class="card-header">
       {{ comment.author }}<small> добавлен {{ comment.created_at|date:'M d, Y H:i' }} </small>
       </h6>
        <div class="card-body">
        <h4>{{ comment }}</h4>
        <form id="like-{{comment.pk}}" method="POST" action="{% url 'store:add_like' comment.pk %}">
                    {% csrf_token %}

                    <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
                        <a class="btn btn-success" id="like-count-{{comment.pk}}"> Likes {{ comment.likes.all.count }}</a>
                    </button>
                </form>
        

        </div>
        {% empty %}
        <p>Для данного товара  ещё нет комментариев.</p>

        {% endfor %}

my ajax call in the same template:

<script type="text/javascript">
    $(document).ready(function(){

        

  $('[id^="like-"]').submit(function(e){

            e.preventDefault();
            var endpoint = $(this).attr('action');
            var serializedData = $(this).serializeArray();
      $.ajax({
               type: 'POST',
               url: endpoint,
               data: serializedData,
               success: function(response) {
                     $( "#like-count-"+response["id"].toString()).text("Likes "+response["like_count"]);

                },
                error: function(rs, e) {
                       alert(rs.responseText);
                }
          });
    })

This part from urls:

path('products/<int:pk>/like/', addlike, name='like'),

View for like:

@api_view(['POST'])
def addlike(request, pk, *args, **kwargs):

        is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
        if request.method == 'POST' and is_ajax:
            
            post = Comment.objects.get(pk=pk)


            is_dislike = False

            for dislike in post.dislikes.all():
                if dislike == request.user:
                    is_dislike = True
                    break

            if is_dislike:
                post.dislikes.remove(request.user)

            is_like = False

            for like in post.likes.all():
                if like == request.user:
                    is_like = True
                    break

            if not is_like:
                post.likes.add(request.user)

            if is_like:
                post.likes.remove(request.user)

            all_post_likes = post.total_likes() (function from models)
            return JsonResponse({"success": True, "like_count": all_post_likes, "id": pk}, status=200)
        else:

            return JsonResponse({"success": False}, status=400)

How to force AJAX to call in pk what I need? (Finally I found the solution, updated the final version of the code)



Solution 1:[1]

Remove the ?data-url? and set the action attribute because when you click the submit button, by default this POST request will be sent to the current URL and you will receive the 405 status code, but if you set action this POST request will be sent to the like url:

<form id="like" method="POST" action="{% url 'store:like' comment.pk %}">
    {% csrf_token %}
     <input type="hidden" value="{{comment.pk}}" name="id">
     <input type="hidden" name="next" value="{{ request.path }}">
     <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
     <a class="btn btn-success" id="like"> Likes {{ comment.likes.all.count }}</a>
     </button>
</form>
        

And in js you can get the URL like this

var endpoint = $(this).attr('action');

Update

All forms have the same "ID" and therefore only the first one is executed and the rest of the forms are not managed by Ajax, to solve this problem you can give different ID to the forms(This condition also applies to a tags):

<form id="like-{{comment.pk}}" method="POST" action="{% url 'store:like' comment.pk %}">
     {% csrf_token %}
     ........
     .....
     <a class="btn btn-success" id="likes-count-{{comment.pk}}"> Likes {{ comment.likes.all.count }}</a>

And you can receive events this way and update the number of likes.

$(document).ready(function(){
  $('[id^="like-"]').submit(function(e){
    e.preventDefault();
    var endpoint = $(this).attr('action');
    var serializedData = $(this).serializeArray();
     
    $.ajax({
       url: endpoint,
       method: "POST",
       data: serializedData,
       success: function(response){
         $("#likes-count-" + serializedData[1].value).text("Likes "+response["like_count"]);
       }
    });
  })
});

in the view you should return the new number of the likes :

return JsonResponse({"success": True, "like_count": new_like_count}, status=200)

And in the success function you can access to the new number by the response and now we change the text value of a tag to change the like count number.

^= means: selects elements that have the specified attribute with a value beginning exactly with a given string. attributeStartsWith1

Solution 2:[2]

I think you should send the csrf_token with the ajax request add headers: { "X-CSRFToken": token } to your ajax request, "token is the csrf_token" or add @csrf_exempt decorator to your function but it will keep your view unsafe against CSRF attacks.

you can find more info here https://docs.djangoproject.com/en/4.0/ref/csrf/

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2