You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Django与JavaScript优化点赞按钮并修复现有问题?

Django异步点赞功能问题修复方案

需求说明

  • 实现带图标的「点赞(Like)」和「取消点赞(Unlike)」按钮,用户点击任意帖子按钮可切换点赞状态
  • 通过JavaScript异步调用fetch通知服务器更新点赞数,无需刷新页面即可更新页面上的帖子点赞数

现有问题

  • 处于取消点赞图标状态时,无法切换回点赞图标
  • 点赞某一帖子后,所有帖子的点赞数都会同步更新
  • 当前代码较为初级,不够规范

现有代码

Django模板HTML文件

{% if post.id in postLiked  %}
<button class="btn btn-primary fa fa-thumbs-down" id="{{ post.id }}" onclick="likeOrUnlike('{{ post.id }}', '{{ postLiked }}')"></button>
{% else %}
<button class="btn btn-primary fa fa-thumbs-up" id="{{ post.id }}" onclick="likeOrUnlike('{{ post.id }}', '{{ postLiked }}')"></button>
{% endif %}
<div class="container">
    <div class="row justify-content-center">
        <h3 class="col-4">Like: {{ likes.count  }}</h3>
    </div>
</div>

models.py文件

class LikeOrUnlike(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='likeByUser')
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='postLiked')

    def __str__(self):
        return f'{self.user} liked this {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):
    likes = LikeOrUnlike.objects.all()
    postLiked = []
    try:
        for like in likes:
            if like.user.id == request.user.id:
                postLiked.append(like.post.id)
    except:
        postLiked = [] 
    return render(request, "network/index.html",{
        'likes':likes,
        'postLiked': postLiked
    })

# 点赞功能
def like(request, post_id):
    post = Post.objects.get(pk=post_id)
    user = User.objects.get(pk=request.user.id)
    likePost = LikeOrUnlike(user=user, post=post)
    likePost.save()
    return JsonResponse({"message": "Liked"})

# 取消点赞功能
def unlike(request, post_id):
    post = Post.objects.get(pk=post_id)
    user = User.objects.get(pk=request.user.id)
    unlikePost = LikeOrUnlike.objects.filter(user=user, post=post)
    unlikePost.delete()
    return JsonResponse({"message": "Unliked"})

JavaScript代码

// 点赞/取消点赞逻辑
function likeOrUnlike(id, postLiked){
    const btn = document.getElementById(`${id}`);

    btn.classList.remove('fa-thumbs-up')
    btn.classList.remove('fa-thumbs-down')
    
    if(postLiked.indexOf(id) >= 0){
        var liked = true;
    }else{
        var liked = false;
    }

    if(liked === true){
        fetch(`/unlike/${id}`)
        .then(response => response.json)
        .then(result => {
            btn.classList.add("fa-thumbs-up")
        })
    }else{
        fetch(`/like/${id}`)
        .then(response => response.json)
        .then(result => {
            btn.classList.add("fa-thumbs-down")
        })
    }
    liked = !liked
}

问题修复方案

1. 解决图标无法切换的问题

问题出在:

  • 模板里传递的postLiked是列表转成的字符串,indexOf无法正确判断状态
  • JavaScript中response.json未加括号调用,无法获取返回数据
  • 切换状态后未更新本地点赞记录,下次判断仍用旧值

解决办法:

  • 模板用json_script传递postLiked,避免字符串解析问题:
    {{ postLiked|json_script:"post-liked-data" }}
    
    按钮去掉内联的postLiked参数,只传帖子ID:
    {% if post.id in postLiked %}
    <button class="btn btn-primary fa fa-thumbs-down" data-post-id="{{ post.id }}"></button>
    {% else %}
    <button class="btn btn-primary fa fa-thumbs-up" data-post-id="{{ post.id }}"></button>
    {% endif %}
    
  • 重写JavaScript逻辑,用事件委托处理点击,修复fetch错误:
    document.addEventListener('DOMContentLoaded', () => {
        const postLiked = JSON.parse(document.getElementById('post-liked-data').textContent);
    
        document.body.addEventListener('click', (e) => {
            const btn = e.target.closest('[data-post-id]');
            if (!btn) return;
    
            const postId = btn.dataset.postId;
            const isLiked = postLiked.includes(postId);
    
            btn.classList.remove('fa-thumbs-up', 'fa-thumbs-down');
    
            fetch(isLiked ? `/unlike/${postId}` : `/like/${postId}`, {
                method: 'POST',
                headers: {
                    'X-CSRFToken': getCookie('csrftoken') // Django必须传CSRF令牌
                }
            })
            .then(res => res.json())
            .then(() => {
                if (isLiked) {
                    postLiked.splice(postLiked.indexOf(postId), 1);
                    btn.classList.add('fa-thumbs-up');
                } else {
                    postLiked.push(postId);
                    btn.classList.add('fa-thumbs-down');
                }
                updateLikeCount(postId);
            })
            .catch(err => console.error('操作失败:', err));
        });
    
        // 获取CSRF令牌
        function getCookie(name) {
            let cookieValue = null;
            if (document.cookie) {
                const cookies = document.cookie.split(';');
                for (const cookie of cookies) {
                    const [key, value] = cookie.trim().split('=');
                    if (key === name) {
                        cookieValue = decodeURIComponent(value);
                        break;
                    }
                }
            }
            return cookieValue;
        }
    });
    

2. 解决所有帖子点赞数同步更新的问题

问题出在模板里用了{{ likes.count }},这是全局所有点赞的总数,不是单帖的点赞数。

解决办法:

  • 模板里显示单帖的点赞数,利用模型的related_name获取:
    <h3 class="col-4 like-count" data-post-id="{{ post.id }}">Like: {{ post.postLiked.count }}</h3>
    
  • 新增获取单帖点赞数的接口:
    urls.py添加路由:
    path('like-count/<int:post_id>', views.like_count, name='like-count'),
    
    views.py添加视图:
    def like_count(request, post_id):
        post = get_object_or_404(Post, pk=post_id)
        return JsonResponse({"count": post.postLiked.count()})
    
  • JavaScript添加更新点赞数的函数:
    function updateLikeCount(postId) {
        fetch(`/like-count/${postId}`)
        .then(res => res.json())
        .then(data => {
            const countEl = document.querySelector(`.like-count[data-post-id="${postId}"]`);
            countEl.textContent = `Like: ${data.count}`;
        });
    }
    

3. 代码规范优化

视图层优化

  • 直接用request.user获取当前用户,避免重复查询
  • 用values_list批量获取点赞的帖子ID,提升效率
  • 用get_object_or_404处理不存在的帖子,避免报错
  • 增加登录校验,防止未登录用户操作:
    def index(request):
        postLiked = []
        if request.user.is_authenticated:
            # 直接查询当前用户点赞的所有帖子ID
            postLiked = list(LikeOrUnlike.objects.filter(user=request.user).values_list('post_id', flat=True))
        posts = Post.objects.all()
        return render(request, "network/index.html", {
            'posts': posts,
            'postLiked': postLiked
        })
    
    def like(request, post_id):
        if not request.user.is_authenticated:
            return JsonResponse({"error": "请先登录"}, status=401)
        post = get_object_or_404(Post, pk=post_id)
        # 避免重复点赞
        if not LikeOrUnlike.objects.filter(user=request.user, post=post).exists():
            LikeOrUnlike.objects.create(user=request.user, post=post)
        return JsonResponse({"message": "点赞成功"})
    
    def unlike(request, post_id):
        if not request.user.is_authenticated:
            return JsonResponse({"error": "请先登录"}, status=401)
        post = get_object_or_404(Post, pk=post_id)
        LikeOrUnlike.objects.filter(user=request.user, post=post).delete()
        return JsonResponse({"message": "取消点赞成功"})
    

模板层优化

  • 用循环渲染帖子,避免重复代码,给每个帖子添加容器:
    {% for post in posts %}
    <div class="post-item">
        <!-- 帖子内容区域 -->
        <p>{{ post.content }}</p>
        <!-- 点赞按钮 -->
        {% if post.id in postLiked %}
        <button class="btn btn-primary fa fa-thumbs-down" data-post-id="{{ post.id }}"></button>
        {% else %}
        <button class="btn btn-primary fa fa-thumbs-up" data-post-id="{{ post.id }}"></button>
        {% endif %}
        <!-- 点赞数 -->
        <div class="container">
            <div class="row justify-content-center">
                <h3 class="col-4 like-count" data-post-id="{{ post.id }}">Like: {{ post.postLiked.count }}</h3>
            </div>
        </div>
    </div>
    {% endfor %}
    <!-- 传递点赞记录 -->
    {{ postLiked|json_script:"post-liked-data" }}
    

JavaScript优化

  • 用事件委托代替内联onclick,代码更解耦,新增帖子无需重新绑定事件
  • 用箭头函数简化代码,逻辑更清晰
  • 统一错误处理,方便调试

内容的提问来源于stack exchange,提问作者Sherpa Coutinho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 00:52:02