在 Django 项目里接入 django_ckeditor_5 并不难,但如果只是“装上就用”,很快就会遇到工具栏太简陋、图片目录混乱、生产环境图片打不开、正文里混杂外链图片等问题。本文结合实战,讲清楚如何把 CKEditor 5 配置成真正适合技术博客和内容后台使用的版本,包括自定义上传视图、按年月日归档图片、生产环境 /media/ 配置,以及一套可复用到其它 Django 项目的完整方案。
如果你正在用 Django 做博客、CMS 或内容后台,django_ckeditor_5 基本是一个绕不开的选择。
它能比较快地把 CKEditor 5 接进项目里,让后台拥有一个现代化的富文本编辑器。但真正在项目里用一段时间后,你通常会发现:“能用”和“好用”之间,其实还差很多事情。
最常见的问题通常有这些:
- 工具栏太保守,不适合长期写文章
- 图片虽然能上传,但文件名和目录很乱
- 粘贴截图后,不确定图片是否真的上传成功
- 开发环境没问题,生产环境图片却打不开
- 正文里既有本站图片,也有外链图片,后期越来越难维护
所以这篇文章不讲“最小安装”,而是直接讲一套更适合长期使用的方案:编辑器增强、上传视图接管、图片按日期归档、生产环境 /media/ 正确服务。
一、为什么默认接入通常不够用
django_ckeditor_5 默认上传逻辑的核心思路其实很简单:接收文件、保存文件、返回 URL。它能工作,但不负责目录治理,也不负责文件命名策略。
这会导致一个很现实的问题:项目刚开始的时候一切都还好,几个月后,media/ 目录里可能已经是这样的状态:
image.pngimage_1.png截图 2026-03-19 12.33.01.pngxxx.webp
目录越来越乱,文件名不可控,后期清理、迁移、接 CDN 都会变麻烦。更糟糕的是,生产环境里就算编辑器已经返回了 /media/...,如果 Nginx 没有明确服务 /media/,浏览器照样打不开。
所以默认接法的问题不在于“不能上传”,而在于:它不适合长期维护。
二、更适合博客后台的整体方案
如果你的后台是拿来持续写文章的,我更建议把这件事分成四层去做:
1. 编辑器层
使用 CKEditor5Field,并把工具栏配置成适合长期写内容的版本,比如保留:
- 标题、加粗、斜体、链接
- 列表、待办、引用
- 代码、代码块、表格、分隔线
- 图片、媒体嵌入
- 查找替换、显示块、源码模式
2. 上传层
不要直接依赖包默认上传视图,而是自己接管上传入口。这样你可以复用包已有的权限校验和表单校验,同时自己控制上传路径和命名规则。
3. 存储层
把正文图片统一保存到:
media/uploads/YYYY/MM/DD/
比如:
media/uploads/2026/03/19/editor-shot_a1b2c3d4.png
这样目录清晰、便于清理、便于备份,也更适合后续接 CDN。
4. 访问层
开发环境里 Django 可以在 DEBUG=True 时兜底服务 /media/,但生产环境一定要交给 Nginx 显式处理。这个步骤很多人一开始都会忽略。
三、在 Django 项目里接入 django_ckeditor_5
先安装包:
pip install django-ckeditor-5
然后在 INSTALLED_APPS 里加入:
INSTALLED_APPS = [
...
"django_ckeditor_5",
]
模型字段改成:
from django_ckeditor_5.fields import CKEditor5Field
class Article(models.Model):
title = models.CharField(max_length=100)
body = CKEditor5Field("正文", config_name="default")
URL 先接入包默认路由:
path("ckeditor5/", include("django_ckeditor_5.urls")),
默认上传接口是:
/ckeditor5/image_upload/
但后面我们会把它切到自己的上传视图。
四、把编辑器配置成真正适合写文章的样子
默认 toolbar 通常太克制。对技术博客或者文档后台来说,至少应该补上这些:
codecodeBlockinsertTabletodoListremoveFormatfindAndReplaceshowBlockssourceEditing- 图片样式、标题、缩放
一个比较实用的配置可以写成这样:
CKEDITOR_5_MAX_FILE_SIZE = 10 * 1024 * 1024
CKEDITOR_5_UPLOAD_FILE_TYPES = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff"]
CKEDITOR_5_CONFIGS = {
"default": {
"toolbar": [
"heading",
"|",
"bold",
"italic",
"underline",
"strikethrough",
"link",
"|",
"bulletedList",
"numberedList",
"todoList",
"outdent",
"indent",
"|",
"blockQuote",
"code",
"codeBlock",
"insertTable",
"horizontalLine",
"|",
"insertImage",
"mediaEmbed",
"removeFormat",
"showBlocks",
"findAndReplace",
"|",
"sourceEditing",
"undo",
"redo",
],
"toolbarShouldNotGroupWhenFull": True,
"image": {
"toolbar": [
"imageTextAlternative",
"toggleImageCaption",
"|",
"imageStyle:inline",
"imageStyle:block",
"imageStyle:side",
"|",
"resizeImage",
],
},
"table": {
"contentToolbar": [
"tableColumn",
"tableRow",
"mergeTableCells",
"tableCellProperties",
"tableProperties",
],
},
"link": {
"addTargetToExternalLinks": True,
"defaultProtocol": "https://",
},
"placeholder": "支持直接粘贴截图、插入图片、表格、代码块和媒体链接。",
}
}
这套配置的重点不是“按钮变多了”,而是它更适合高频写内容。尤其是 sourceEditing、codeBlock、insertTable 和图片 toolbar,几乎都是技术文章后台的高频需求。
五、图片上传路径为什么一定要自定义
图片上传如果继续使用默认逻辑,问题会越来越明显:
- 不按日期归档
- 文件名不可控
- 目录难管理
- 后续接对象存储或 CDN 不方便
所以更推荐把图片统一保存到:
media/uploads/YYYY/MM/DD/
例如:
media/uploads/2026/03/19/editor-shot_a1b2c3d4.png
这种结构最大的好处就是:一开始多做一步,后面省很多事。
六、怎么把 django_ckeditor_5 切到你自己的上传视图
这个包本身已经给了扩展点,它的 widget 渲染时会读取:
CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME
所以你不需要去改第三方包,只要做三件事:
- 写自己的上传视图
- 给它一个 URL name
- 在 settings 里指定这个 name
比如 settings 里这样配:
CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME = "blog_ck_editor_5_upload_file"
URL 里这样接:
path("ckeditor5/custom-image-upload/", upload_ckeditor_image, name="blog_ck_editor_5_upload_file"),
path("ckeditor5/", include("django_ckeditor_5.urls")),
这样编辑器的上传入口就会切到你的视图,但包默认路由仍然可以保留,不冲突。
七、自定义上传视图怎么写
这里的思路不是“全部自己重写”,而是:
- 复用包已有的权限检查
- 复用上传表单校验
- 复用图片合法性校验
- 你自己负责生成日期目录和文件名
一份可直接复用的代码如下:
from datetime import date
from pathlib import Path
from uuid import uuid4
from django.core.files.storage import default_storage
from django.http import JsonResponse
from django.utils.text import get_valid_filename
from django.views.decorators.http import require_POST
from django_ckeditor_5.exceptions import NoImageException
from django_ckeditor_5.forms import UploadFileForm
from django_ckeditor_5.permissions import check_upload_permission
from django_ckeditor_5.storage_utils import image_verify
def _dated_upload_name(filename: str) -> str:
today = date.today()
original = Path(filename or "image").name
stem = get_valid_filename(Path(original).stem) or "image"
suffix = Path(original).suffix.lower() or ".png"
return str(
Path("uploads")
/ today.strftime("%Y")
/ today.strftime("%m")
/ today.strftime("%d")
/ f"{stem}_{uuid4().hex[:8]}{suffix}"
)
@require_POST
@check_upload_permission
def upload_ckeditor_image(request):
form = UploadFileForm(request.POST, request.FILES)
if not form.is_valid():
error_message = form.errors.get("upload", ["Invalid form data"])[0]
return JsonResponse({"error": {"message": error_message}}, status=400)
uploaded = request.FILES["upload"]
try:
image_verify(uploaded)
except NoImageException as ex:
return JsonResponse({"error": {"message": str(ex)}}, status=400)
uploaded.seek(0)
saved_name = default_storage.save(_dated_upload_name(uploaded.name), uploaded)
return JsonResponse({"url": default_storage.url(saved_name)})
这段实现解决了几个核心问题:
- 图片按年月日归档
- 文件名不会直接裸存
- 原始文件名语义还能保留
- 不容易重名覆盖
这一步其实就是从“能上传”进化到“能管理”。
八、怎么判断“粘贴图片”是不是真的上传成功了
很多人会误判。
看到图片出现在编辑器里,不代表它已经真的上传到了服务器。
更稳的判断方式有三个:
方法 1:看 HTML
如果正文源码里已经出现:
<img src="/media/uploads/2026/03/19/xxx.png">
说明上传链路大概率已经跑通了。
方法 2:看浏览器网络请求
开发者工具里确认是否发出了:
POST /ckeditor5/custom-image-upload/
返回 JSON 应该类似:
{
"url": "/media/uploads/2026/03/19/editor-shot_a1b2c3d4.png"
}
方法 3:看服务器磁盘
直接检查目录里是否真的有文件:
ls -l media/uploads/2026/03/19/
这三步里,任何一步缺失,都说明你还没真正打通上传链路。
九、为什么生产环境总是“上传成功但图片不显示”
这是最常见的坑。
开发环境下你可能写了:
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
所以本地一直看起来没问题。
但上线之后:
DEBUG=False- Django 不再兜底服务
/media/ /media/必须由 Nginx 负责
正确的 Nginx 配置应该至少有:
location /static/ {
alias /srv/your_project/staticfiles/;
}
location /media/ {
alias /srv/your_project/media/;
}
这也是为什么很多项目“本地正常、线上挂掉”。不是编辑器问题,而是资源访问链路没有补完整。
十、以后在别的项目里怎么复用
如果你后面在别的 Django 项目里还要继续用这套方案,按这个顺序做基本就够了:
- 安装
django-ckeditor-5 - 注册
INSTALLED_APPS - 模型字段改成
CKEditor5Field - 配好
MEDIA_URL、MEDIA_ROOT - 指定
CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME - 写自己的上传视图
- URL 里把自定义上传接口接进去
- 生产环境补
/media/的 Nginx 配置 - 至少写三个测试:
- 上传接口返回 200
- 路径包含
uploads/YYYY/MM/DD/ - 文件真实保存到磁盘
这比“复制一份最小安装教程”靠谱得多。
十一、排错时我建议按这个顺序查
如果以后又遇到 CKEditor 图片相关问题,我建议按下面顺序排:
1. 粘贴没反应
先查:
- 浏览器有没有发上传请求
- 上传接口是不是返回 403
- 当前用户权限是否符合
CKEDITOR_5_FILE_UPLOAD_PERMISSION
2. 上传成功但图片不显示
先查:
- HTML 里的
img src是什么 - 浏览器直接打开这个 URL 是否 200
- Nginx 有没有正确配置
/media/
3. 图片路径太乱
先查:
- 你是不是还在用默认上传视图
CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME有没有生效- 自定义视图是否真的生成了日期目录
4. 正文里还是外链图片
这通常不是 CKEditor 上传问题,而是历史正文本来就混有外链图片。这个要单独做本地化处理。
十二、这套方案后面还能继续优化什么
如果你已经把上面这套跑通了,后面还可以继续往前走:
- 上传后自动压缩
- 自动转 WebP
- 历史外链图片本地化
- 把底层存储切到 OSS / COS / S3
- 接 CDN
但在我看来,第一阶段最重要的不是做高级优化,而是先把这四件事一次性做对:
- 配好更完整的 toolbar
- 接管上传视图
- 按年月日存图片
- 配好生产环境
/media/
这四步,就是从“凑合能用”到“长期可维护”的分界线。