大家好,我是木木。
今天给大家分享一个灵活的 Python 库,wagtail。
wagtail
如果你做的是内容站、品牌站或长期运营的网站,Wagtail 那种“编辑体验先站稳、开发扩展再跟上”的路子会很顺手。它基于 Django,但重心不只是在后台 CRUD,而是把页面结构、富文本、图片裁切和内容治理这些 CMS 里的高频问题整理成了一套更稳定的工作流。
项目地址:https://github.com/wagtail/wagtail
官方文档:https://docs.wagtail.org/en/stable/
三大特点
编辑体验顺手
页面编辑、预览、发布这些动作放在一个流里,内容团队上手成本比较低。
结构化内容强
从富文本到块级内容,既能给编辑自由度,也不容易把页面结构写散。
图片处理成熟
封面裁切、格式限制和展示比例这些细节,框架内部就考虑得比较完整。
最佳实践
安装方式:python -m pip install wagtail
如果你是从 Django 项目往 CMS 方向扩展,Wagtail 很适合做“保留后端掌控感,同时把编辑体验补齐”的那一步。这篇我不直接起完整站点,而是先拿源码里几个能单独运行的核心能力做最小实验,看看它为什么常被拿来做长期维护的内容系统。
功能一:让编辑器里的占位内容在前台正确展开
这段代码解决什么问题:CMS 里的富文本通常不是直接把最终 HTML 原样存库,而是先存“可编辑、可追踪”的内部标记。Wagtail 在渲染时再把这些标记展开成真实链接和嵌入内容,这样内容迁移、引用追踪和前台输出会更稳。
fromdjango.confimportsettingsimportsysfrompathlibimportPathifnotsettings.configured:settings.configure(USE_I18N=False,USE_TZ=True,SECRET_KEY="demo")sys.path.insert(0,str(Path.cwd()/"repo"))fromwagtail.rich_textimport(# noqa: E402EmbedHandler,LinkHandler,expand_db_html,extract_references_from_rich_text,features,get_rewriter,)classDocLinkHandler(LinkHandler):identifier="doc"@staticmethoddefexpand_db_attributes(attrs):returnf'<a href="/docs/{attrs["slug"]}/">'@classmethoddefextract_references(cls,attrs):yield("docs",attrs["slug"],"knowledge.Doc","body")classAssetEmbedHandler(EmbedHandler):identifier="asset"@staticmethoddefexpand_db_attributes(attrs):returnf'<figure data-kind="{attrs["kind"]}">{attrs["filename"]}</figure>'@classmethoddefextract_references(cls,attrs):yield("assets",attrs["filename"],"media.Asset","body")features.link_types.clear()features.embed_types.clear()features.register_link_type(DocLinkHandler)features.register_embed_type(AssetEmbedHandler)features.has_scanned_for_features=Trueget_rewriter.cache_clear()source=('<p>Launch prep: ''<a linktype="doc" slug="launch-checklist">checklist</a>.'"</p>"'<embed embedtype="asset" kind="image" filename="cover-hero.png" />')print("SOURCE")print(source)print("EXPANDED")print(expand_db_html(source))print("REFERENCES")forrefinextract_references_from_rich_text(source):print(ref)

你会发现它不只是把内容“显示出来”,还顺手把引用对象抽出来了。这件事在内容系统里很值钱,因为页面里用了哪个文档、哪张图、哪条内部链接,后面都能继续追踪,不用等线上出问题再回头全文搜索。
功能二:把编辑器里的字符限制和索引文本拆开处理
这段代码解决什么问题:编辑器里经常会有加粗、标题、列表这些标签,如果按原始 HTML 长度去做摘要限制,体验会很别扭。Wagtail 的做法是把“给搜索引擎和校验器看的纯文本”单独抽出来,让编辑限制更贴近真实内容长度。
fromdjango.confimportsettingsimportsysfrompathlibimportPathifnotsettings.configured:settings.configure(USE_I18N=False,USE_TZ=True,SECRET_KEY="demo")sys.path.insert(0,str(Path.cwd()/"repo"))fromdjango.core.exceptionsimportValidationError# noqa: E402fromwagtail.rich_textimport(# noqa: E402RichTextMaxLengthValidator,RichTextMinLengthValidator,get_text_for_indexing,)fromwagtail.utils.textimporttext_from_html# noqa: E402samples={"article_intro":("<h2>Launch Plan</h2>""<p>Editor-friendly <strong>body</strong> with <em>images</em>.</p>""<ul><li>Checklist</li><li>Preview</li></ul>"),"tiny_note":"<p><b>Short</b></p>",}print("INDEX_TEXT")print(get_text_for_indexing(samples["article_intro"]))print("PLAIN_TEXT")print(text_from_html(samples["article_intro"]))print("VALIDATION")validators=[("min 12",RichTextMinLengthValidator(12)),("max 40",RichTextMaxLengthValidator(40)),]forsample_name,htmlinsamples.items():plain=text_from_html(html)print(f"[{sample_name}] chars={len(plain)} text={plain!r}")forlabel,validatorinvalidators:try:validator(html)print("",label,"OK")exceptValidationErrorasexc:print("",label,"ERR",exc)

这类能力很适合做摘要、SEO 描述、卡片简介和后台校验。编辑看到的是“真实字数”,搜索索引拿到的是“去标签后的内容”,前台体验就不会被一堆格式标签搅乱。
功能三:根据焦点区域裁出不同封面比例
这段代码解决什么问题:同一张图在首页横幅、列表卡片、正方形缩略图里往往要裁三次。如果每次都手工调,内容团队会很累。Wagtail 底层把裁切区域当成几何对象处理,不同尺寸只要围绕同一焦点计算就行。
importsysfrompathlibimportPathsys.path.insert(0,str(Path.cwd()/"repo"))fromwagtail.images.rectimportRect# noqa: E402canvas=Rect(0,0,1600,900)focal=Rect.from_point(1180,340,420,280)square=Rect.from_point(focal.x,focal.y,560,560)wide=Rect.from_point(focal.x,focal.y,960,540)hero=Rect.from_point(focal.x,focal.y,1200,500)print("CANVAS",canvas.round())print("FOCAL ",focal.round())forname,rectin[("square",square),("wide",wide),("hero",hero)]:clamped=rect.move_to_clamp(canvas).round()print(name.upper())print(" requested:",rect.round())print(" final :",clamped)print(" size :",clamped.width,"x",clamped.height)
这个思路看起来像底层实现细节,但对 CMS 很关键。你一旦要照顾首页、栏目页、推荐位和社交分享图,多比例裁切就不是“锦上添花”,而是内容交付能不能稳定的一部分。
环境与版本信息
- 本文 demo 环境:Windows 11,Python 3.11,直接基于
wagtail/wagtail 仓库源码运行核心模块 - 仓库当前活跃状态:GitHub
pushed_at 为 2026-04-18T00:51:04Z - 官方包要求:
pyproject.toml 当前声明 Python >=3.10 - README 当前说明:Wagtail 支持 Django
5.2.x 和 6.0.x,适合继续放在现代 Django 栈里演进
适用场景
- 你要做内容站、品牌站、媒体站或机构官网,而且编辑团队会长期在后台里维护内容
- 你已经接受 Django 作为底座,希望 CMS 能保留扩展性,不想被纯模板化后台绑死
- 你很在意图片、富文本、页面结构和发布体验这些内容治理细节
不适用场景
- 只是临时做一个几页静态展示站,后续几乎没有内容团队参与
- 团队不想维护 Django 项目,只想找一个零后端心智负担的托管式 CMS
- 你当前最优先的是极轻量 API 内容仓,而不是完整的编辑和发布工作流
上线检查
- 先按官方版本矩阵确认 Python 和 Django 组合,不要只看本地能不能
pip install - 把图片裁切、富文本输出和搜索索引各做一轮真实内容的 smoke test
- 如果要接自定义 block、内部链接或媒体策略,先补一组内容回归样例,再开放给编辑团队使用
总结
如果你想要的是一个编辑体验不差、内容结构能收得住、又保留 Django 扩展空间的 CMS,Wagtail 很值得认真看一遍。