Как показать и скрыть кнопки Like/Unlike с помощью Javascript в моем веб-приложении?

Я прохожу онлайн-курс CS50W в Гарварде и создаю веб-приложение, похожее на twitter. Когда пользователь видит сообщение, мне нужно показать ему кнопку Like или Unlike в зависимости от того, понравилось оно пользователю или нет. Также есть счетчик, показывающий, скольким пользователям понравилось сообщение на данный момент. Я могу обновить счетчик, если пользователю понравился или не понравился пост, но у меня проблема с кодом, показывающим или скрывающим кнопки.

Вот мой код:

models.py

class Post(models.Model):
    """ Model representing a post. """
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    timestamp = models.DateTimeField(auto_now_add=True)
    no_of_likes = models.IntegerField(default=0)

    def __str__(self):
        return f"Post {self.id} by {self.user.username} on {self.timestamp}"
    
class Like(models.Model):
    """ Model representing a like. """
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.user} likes {self.post}"

urls.py

path("", views.index, name="index"),
path("like/<int:post_id>", views.like, name="like"),
path("unlike/<int:post_id>", views.unlike, name="unlike"),

views.py

def index(request):
    """ Home page. """
    posts = Post.objects.all().order_by('-timestamp')
    paginator = Paginator(posts, 5)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    likes = Like.objects.all()  
    
    # Make a list of liked posts.
    liked_posts = []
    try:
        for like in likes:
            if like.user.id == request.user.id:
                liked_posts.append(like.post.id)
    except:
        liked_posts = []

    return render(request, "network/index.html", {
        "posts": posts,
        "page_obj": page_obj,
        "likes": likes,
        "liked_posts": liked_posts,             
    })

@login_required
def like(request, post_id):
   
    post = Post.objects.get(pk=post_id)
    user = User.objects.get(pk=request.user.id)
    like = Like.objects.create(user=user, post=post)
    like.save()

    # Update no of likes.
    post.no_of_likes = Like.objects.filter(post=post).count()
    post.save()
    return JsonResponse({"message": "successfully liked", "no_of_likes": post.no_of_likes})

@login_required   
def unlike(request, post_id):
    
    post = Post.objects.get(pk=post_id)
    user = User.objects.get(pk=request.user.id)
    like = Like.objects.filter(user=user, post=post)
    like.delete()

    # Update no of likes.
    post.no_of_likes = Like.objects.filter(post=post).count()
    post.save()
    return JsonResponse({"message": "successfully unliked", "no_of_likes": post.no_of_likes})

index.html с Javascript

{% if post.id not in liked_posts %}
    <button type="button" class="btn btn-primary" id="like{{ post.id }}" onclick="like('{{ post.id         }}')">Like</button>
{% else %}
    <button type="button" class="btn btn-primary" id="unlike{{ post.id }}"onclick="unlike('{{ post.id }}')">Unlike</button>
{% endif %}

function like(id) {
        fetch(`/like/${id}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-CSRFToken": "{{ csrf_token }}",
            },
        })
        .then(response => response.json())
        .then(data => {
            document.querySelector(".no-of-likes").innerHTML = data.no_of_likes + " likes";
        })
        .then(() => {
            document.getElementById("unlike" + id).style.display = "block";
            document.getElementById("like" + id).style.display = "none";
        });
    }

function unlike(id) {
        fetch(`/unlike/${id}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-CSRFToken": "{{ csrf_token }}",
            },
        })
        .then(response => response.json())
        .then(data => {
            document.querySelector(".no-of-likes").innerHTML = data.no_of_likes + " likes";
        })
        .then(() => {
            document.getElementById("unlike" + id).style.display = "none";
            document.getElementById("like" + id).style.display = "block";
        });
    }   

Я могу обновить счетчик, не обновляя страницу, но с кнопками дело обстоит иначе. Вот что происходит:

--> Когда я нажимаю кнопку Like, счетчик обновляется. Мне приходится обновлять страницу, чтобы сменить кнопку на Unlike. Согласно спецификации, это должно быть сделано асинхронно (предполагается, что с помощью Javascript!) без перезагрузки страницы.

--> То же самое происходит и с кнопкой Unlike. Когда я нажимаю на нее, счетчик обновляется, но мне приходится перезагружать страницу, чтобы изменить кнопку на Like.

Я хочу менять кнопки без перезагрузки страницы. Пробовал размещать блоки кода в разных местах, пробовал использовать условия 'if else', но все равно застрял. Любая помощь будет оценена по достоинству! Извините за беспорядочный код Javascript, новичок здесь.

Это потому, что вы отображаете только одну кнопку. Добавьте две, одну скройте, другую покажите:

<button type="button" class="btn btn-primary" id="like{{ post.id }}" onclick="like('{{ post.id }}')" style="display: {% if post.id not in liked_posts %}block{% else %}none{% endif %};">Like</button>
<button type="button" class="btn btn-primary" id="unlike{{ post.id }}" onclick="unlike('{{ post.id }}')" style="display: {% if post.id in liked_posts %}block{% else %}none{% endif %};">Unlike</button>

Но я не понимаю, зачем вообще нужны две кнопки: достаточно использовать одну кнопку, которую вы будете использовать как для "нравится", так и для "не нравится" в зависимости от контекста.


Note: Please don't store aggregates in the model: determine aggregates when needed: storing aggregates in the model makes updating and keeping data in sync harder. You can use .annotate(…) [Django-doc] to generate counts, sums, etc. per object when needed.

Вернуться на верх