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

如何在Django默认管理页面实现博客自动保存功能?

Django Admin 博客自动保存最优实现方案

核心思路

在现有博客模型中添加草稿存储字段,结合自定义Admin视图+前端输入触发的AJAX请求,实现自动保存草稿功能,同时适配新文章和已有文章场景。

步骤1:扩展博客模型

新增草稿内容和上次保存时间字段,专门存储自动保存的内容:

# models.py
from django.db import models
from django.utils import timezone

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    # 自动保存草稿字段
    draft_content = models.TextField(blank=True, null=True)
    last_saved = models.DateTimeField(blank=True, null=True)

    def save(self, *args, **kwargs):
        # 正式发布时同步草稿内容到主内容(可选)
        if self.draft_content and not self.content:
            self.content = self.draft_content
        super().save(*args, **kwargs)

步骤2:自定义Admin草稿保存视图

在Admin中添加处理草稿保存的视图,自动处理权限验证和CSRF:

# admin.py
from django.contrib import admin
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
from .models import BlogPost
from django.utils import timezone
from django.urls import path

class BlogPostDraftSaveView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def post(self, request, pk=None):
        # 权限验证由admin_view自动处理
        if pk:
            post = BlogPost.objects.get(pk=pk)
        else:
            # 新文章自动创建草稿实例
            post = BlogPost.objects.create(title="未命名草稿")
        
        post.draft_content = request.POST.get('content', '')
        post.last_saved = timezone.now()
        post.save()
        return JsonResponse({
            'status': 'success',
            'last_saved': post.last_saved.strftime('%Y-%m-%d %H:%M:%S'),
            'post_id': post.id
        })

class BlogPostAdmin(admin.ModelAdmin):
    # 指定自定义模板注入JS
    change_form_template = 'admin/blog_post_change_form.html'

    def get_urls(self):
        urls = super().get_urls()
        custom_urls = [
            path('<int:pk>/save-draft/', self.admin_site.admin_view(BlogPostDraftSaveView.as_view()), name='blog-post-save-draft'),
            path('save-draft/', self.admin_site.admin_view(BlogPostDraftSaveView.as_view()), name='blog-post-save-draft-new'),
        ]
        return custom_urls + urls

admin.site.register(BlogPost, BlogPostAdmin)

步骤3:前端实现自动保存逻辑

创建自定义Admin模板,注入JavaScript实现输入停止后自动保存:

<!-- templates/admin/blog_post_change_form.html -->
{% extends "admin/change_form.html" %}

{% block extrahead %}
    {{ block.super }}
    <script>
        const autoSaveDelay = 5000; // 输入停止后5秒触发保存
        let postId = '{{ object_id }}';
        let saveTimeout;

        function saveDraft() {
            const contentField = document.getElementById('id_content');
            if (!contentField) return;

            const formData = new FormData();
            formData.append('content', contentField.value);
            formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]').value);

            const url = postId 
                ? `{% url 'admin:blog-post-save-draft' object_id %}`
                : `{% url 'admin:blog-post-save-draft-new' %}`;

            fetch(url, { method: 'POST', body: formData })
                .then(res => res.json())
                .then(data => {
                    if (data.status === 'success') {
                        // 更新自动保存提示
                        let notice = document.getElementById('auto-save-notice');
                        if (!notice) {
                            notice = document.createElement('div');
                            notice.id = 'auto-save-notice';
                            notice.style = 'margin:10px 0; padding:8px; background:#e6f7ff; border:1px solid #91d5ff; border-radius:4px;';
                            contentField.parentElement.appendChild(notice);
                        }
                        notice.textContent = `自动保存成功:${data.last_saved}`;

                        // 新文章更新ID,避免丢失草稿
                        if (!postId) {
                            postId = data.post_id;
                            window.history.replaceState({}, '', window.location.pathname.replace('/add/', `/change/${data.post_id}/`));
                        }
                    }
                })
                .catch(err => console.error('自动保存失败', err));
        }

        // 页面加载后绑定事件
        document.addEventListener('DOMContentLoaded', () => {
            const contentField = document.getElementById('id_content');
            if (contentField) {
                // 输入停止后触发保存
                contentField.addEventListener('input', () => {
                    clearTimeout(saveTimeout);
                    saveTimeout = setTimeout(saveDraft, autoSaveDelay);
                });
                // 页面加载时自动保存一次(可选)
                setTimeout(saveDraft, 1000);
            }
        });
    </script>
{% endblock %}

针对你之前Ajax失败的关键修复

  • CSRF令牌处理:通过FormData携带csrfmiddlewaretoken,避免请求被Django拦截
  • 新文章实例创建:针对未发布的新文章,自动创建草稿实例并返回ID,解决无实例可保存的问题
  • 权限验证:使用admin_site.admin_view包装视图,确保只有有权限的用户能访问
  • 内容获取准确性:直接通过Admin默认字段ID(id_content)获取内容,避免选择器错误

内容的提问来源于stack exchange,提问作者Franklyn Etu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:23:22