在Django/Wagtail中实现无需登录的博客点赞/点踩按钮咨询
Wagtail 博客无登录点赞/点踩功能实现步骤
第一步:修改 BlogEntry 模型
首先调整你现有的BlogEntry模型代码,添加路由能力、完善点赞点踩逻辑:
# 先导入依赖 from django.http import JsonResponse from django.views.decorators.http import require_POST from django.utils.decorators import method_decorator from django.db.models import F from wagtail.contrib.routable_page.models import RoutablePageMixin, route # 注意要先继承RoutablePageMixin,再继承Page class BlogEntry(RoutablePageMixin, Page): # 你原来的字段定义保持不变,这里省略 date = models.DateField("Post date") intro = models.CharField(max_length=250, blank=False) # ... 其余原有字段不变 # 修改原有__str__方法,原方法返回数字不利于后台展示,建议改为: def __str__(self): return self.title # 优化点赞方法,使用F表达式避免竞态条件,加save保存到数据库 def likePost(self): self.total_likes = F('total_likes') + 1 self.save(update_fields=['total_likes']) # 优化点踩方法,限制最低为0 def dislikePost(self): self.total_likes = F('total_likes') - 1 self.save(update_fields=['total_likes']) self.refresh_from_db() if self.total_likes < 0: self.total_likes = 0 self.save(update_fields=['total_likes']) # 新增点赞接口路由 @method_decorator(require_POST, name='dispatch') @route(r'^like/$', name='like_post') def like_post(self, request): self.likePost() self.refresh_from_db() return JsonResponse({'success': True, 'total_likes': self.total_likes}) # 新增点踩接口路由 @method_decorator(require_POST, name='dispatch') @route(r'^dislike/$', name='dislike_post') def dislike_post(self, request): self.dislikePost() self.refresh_from_db() return JsonResponse({'success': True, 'total_likes': self.total_likes})
第二步:添加模板代码
在你的博客详情页模板(一般命名为blog_entry.html)中添加按钮和交互逻辑:
首先在模板顶部加载路由标签:
{% load wagtailroutablepage_tags %}
然后添加按钮和JS逻辑:
<!-- 投票按钮区域 --> <div class="vote-area"> <button id="likeBtn" data-url="{% pageurl page route_name='like_post' %}"> 点赞 (<span id="likeCount">{{ page.total_likes }}</span>) </button> <button id="dislikeBtn" data-url="{% pageurl page route_name='dislike_post' %}"> 点踩 </button> <p id="voteTip" style="display:none;color:#666;margin-top:8px;"></p> </div> <script> // 工具方法:获取CSRF Token function getCsrfToken() { let value = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.startsWith('csrftoken=')) { value = decodeURIComponent(cookie.substring('csrftoken='.length)); break; } } } return value; } document.addEventListener('DOMContentLoaded', () => { const likeBtn = document.getElementById('likeBtn'); const dislikeBtn = document.getElementById('dislikeBtn'); const likeCount = document.getElementById('likeCount'); const voteTip = document.getElementById('voteTip'); const postId = '{{ page.id }}'; // 从本地存储判断是否已经投过票,避免重复提交 const voted = localStorage.getItem(`voted_post_${postId}`); if (voted) { likeBtn.disabled = true; dislikeBtn.disabled = true; voteTip.textContent = '你已经对这篇文章投过票了'; voteTip.style.display = 'block'; } // 绑定点赞请求 likeBtn.addEventListener('click', () => sendVote(likeBtn.dataset.url, '点赞成功')); // 绑定点踩请求 dislikeBtn.addEventListener('click', () => sendVote(dislikeBtn.dataset.url, '点踩成功')); function sendVote(url, successMsg) { fetch(url, { method: 'POST', headers: { 'X-CSRFToken': getCsrfToken(), } }) .then(res => res.json()) .then(data => { if (data.success) { likeCount.textContent = data.total_likes; likeBtn.disabled = true; dislikeBtn.disabled = true; voteTip.textContent = successMsg; voteTip.style.display = 'block'; // 标记已投票,避免重复提交 localStorage.setItem(`voted_post_${postId}`, true); } }) .catch(() => { voteTip.textContent = '投票失败,请稍后重试'; voteTip.style.display = 'block'; }); } }); </script>
可选优化
- 若需要支持用户切换投票,可以修改本地存储逻辑,记录用户投票类型,点击时取消原有投票再提交新的
- 若需要更高的防刷能力,可以在后端新增投票记录表,存储IP+UA信息,限制同一IP在指定时间内只能对同一文章投一次
- 若不需要无刷新体验,可以去掉JS逻辑,用普通表单提交,提交后重定向回文章详情页即可
内容的提问来源于stack exchange,提问作者user3874738
相关产品推荐
相关产品推荐

