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

CS50W Network项目:点赞按钮正常但点赞数无法实时更新

CS50W Project4:实时更新点赞数与解决CSRF问题

问题概述

正在开发CS50W Project4的社交网站,已实现点赞按钮核心功能,但点赞数需刷新页面才会更新。尝试通过重定向页面同步数据时,触发了Forbidden (CSRF token missing.)的403错误,需要实现点赞数实时更新同时解决CSRF问题。

现有代码

前端JavaScript

function load_posts() {
  // 获取帖子数据
  fetch('/all_posts')
  .then(response => response.json())
  .then(posts => {
    // 为每个帖子创建DOM元素
    posts.forEach(post => {
        let div = document.createElement('div');
        div.className = "card post-card";
        div.innerHTML = `
        <div class="card-body">
          <h5 class="card-title">${post['username']}</h5>
          <h6 class="card-subtitle mb-2 text-muted">${post['timestamp']}</h6>
          <p class="card-text">${post['text']}</p>
          <button class="card-text like-button" onclick="like_post(${post.id});"><h3> ♥ </h3></button>
          ${post['likes']}
        </div> 
        `;
        document.querySelector('#posts-view').append(div);
    });
  });
}

function like_post(post_id) {
  fetch('/post/' + post_id, {
    method: 'PUT',
    body: JSON.stringify({
        like: true
    })
  });
}

Django后端视图

@csrf_exempt
@login_required
def post(request, post_id):
    
    # 查询目标帖子
    try:
        post = Post.objects.get(pk=post_id)
        user = User.objects.get(username=request.user)
    except Post.DoesNotExist:
        return JsonResponse({"error": "Post not found."}, status=404)

    # 返回帖子详情
    if request.method == "GET":
        return JsonResponse(post.serialize())

    # 更新点赞状态
    elif request.method == "PUT":
        data = json.loads(request.body)
        if data.get("like") is not None and data.get("like") is True:
            if post.likes.filter(username=user.username).exists():
                post.likes.remove(User.objects.get(username=user.username))
            else: 
                post.likes.add(User.objects.get(username=user.username))
            return HttpResponse(status=204)
        
    # 仅支持GET和PUT请求
    else:
        return JsonResponse({
            "error": "GET or PUT request required."
        }, status=400)

解决方案

核心思路

放弃重定向方案,改为通过AJAX请求获取更新后的点赞数据,直接修改页面DOM实现实时刷新;同时在AJAX请求中携带CSRF token,保留Django的CSRF安全保护。

修改后的代码

前端JavaScript

function load_posts() {
  fetch('/all_posts')
  .then(response => response.json())
  .then(posts => {
    posts.forEach(post => {
        let div = document.createElement('div');
        div.className = "card post-card";
        // 给点赞数添加带post-id的唯一标识,给按钮设置初始点赞状态颜色
        div.innerHTML = `
        <div class="card-body">
          <h5 class="card-title">${post['username']}</h5>
          <h6 class="card-subtitle mb-2 text-muted">${post['timestamp']}</h6>
          <p class="card-text">${post['text']}</p>
          <button class="card-text like-button" onclick="like_post(${post.id});" style="color: ${post.is_liked ? 'red' : 'black'}"><h3> ♥ </h3></button>
          <span class="like-count" data-post-id="${post.id}">${post['likes']}</span>
        </div> 
        `;
        document.querySelector('#posts-view').append(div);
    });
  });
}

function like_post(post_id) {
  // 从页面Cookie中提取CSRF token
  const csrftoken = document.cookie.split('; ').find(row => row.startsWith('csrftoken=')).split('=')[1];
  
  fetch(`/post/${post_id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRFToken': csrftoken
    },
    body: JSON.stringify({
      like: true
    })
  })
  .then(response => response.json())
  .then(data => {
    // 更新对应帖子的点赞数
    const likeCountElement = document.querySelector(`.like-count[data-post-id="${post_id}"]`);
    if (likeCountElement) {
      likeCountElement.textContent = data.likes_count;
    }
    // 更新按钮颜色,标记是否已点赞
    const likeButton = document.querySelector(`.like-button[onclick="like_post(${post_id});"]`);
    if (likeButton) {
      likeButton.style.color = data.is_liked ? 'red' : 'black';
    }
  });
}

Django后端视图

@login_required
def post(request, post_id):
    try:
        post = Post.objects.get(pk=post_id)
        user = request.user  # 直接使用request.user,无需重复查询用户对象
    except Post.DoesNotExist:
        return JsonResponse({"error": "Post not found."}, status=404)

    if request.method == "GET":
        # 补充is_liked字段,供前端判断初始点赞状态
        return JsonResponse({
            **post.serialize(),
            "is_liked": post.likes.filter(id=user.id).exists()
        })

    elif request.method == "PUT":
        data = json.loads(request.body)
        if data.get("like") is not None and data.get("like") is True:
            if post.likes.filter(id=user.id).exists():
                post.likes.remove(user)
            else: 
                post.likes.add(user)
            # 返回更新后的点赞数据,供前端同步UI
            return JsonResponse({
                "likes_count": post.likes.count(),
                "is_liked": post.likes.filter(id=user.id).exists()
            })
        
    else:
        return JsonResponse({
            "error": "GET or PUT request required."
        }, status=400)

关键说明

  1. CSRF问题解决:移除@csrf_exempt装饰器,通过AJAX请求的headers携带CSRF token,既解决403错误,又保留Django的CSRF安全防护。
  2. 实时更新实现:后端PUT请求返回最新的点赞数和用户点赞状态,前端通过DOM选择器定位对应元素,直接修改内容实现实时刷新,无需页面重定向。
  3. 代码优化:简化用户查询逻辑,直接使用request.user;给点赞数添加唯一标识,确保精准更新目标帖子的数据。

内容的提问来源于stack exchange,提问作者Louis Eiden

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 05:47:02