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)
关键说明
- CSRF问题解决:移除
@csrf_exempt装饰器,通过AJAX请求的headers携带CSRF token,既解决403错误,又保留Django的CSRF安全防护。 - 实时更新实现:后端PUT请求返回最新的点赞数和用户点赞状态,前端通过DOM选择器定位对应元素,直接修改内容实现实时刷新,无需页面重定向。
- 代码优化:简化用户查询逻辑,直接使用
request.user;给点赞数添加唯一标识,确保精准更新目标帖子的数据。
内容的提问来源于stack exchange,提问作者Louis Eiden
相关产品推荐
相关产品推荐

