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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 04:06:03