wagtail,一个强大的 Python 库!
后台编辑一篇活动页,运营只想加一张图、两段文案、一个按钮。研发这边一看需求,页面结构不固定,字段还会变。 这种东西如果还硬写成 title、content、image、button_text、button_url,后面肯定要补字段,补到自己都烦。
Wagtail 我一般就是在这种场景下拿出来用。
它不是那种“装完就能糊页面”的玩具后台。Wagtail 是基于 Django 的开源 Python CMS,官方定位也是 open-source Django content management system。页面模型、权限、图片、文档、内容审核这些东西,它都放在 Django 那套体系里,不是另起炉灶。
我比较喜欢它的一点是:内容后台归后台,代码结构还在程序员手里。
比如文章页,我不会上来就给一个大字段 body = RichTextField(),那样编辑器爽了,前端后面难受。图片、引用、代码块、CTA 按钮,全塞进一坨富文本里,线上改样式的时候你就知道痛了。
Wagtail 里更顺手的是 StreamField。官方文档里说得也很直接,它适合那种结构不固定的页面,比如新闻、博客,文本中间可以穿插标题、图片、引用、视频,按 block 顺序组合。
我一般会这么写,别一上来搞太复杂:
# app/models.py
from django.db import models
from wagtail.models import Page
from wagtail.fields import StreamField
from wagtail import blocks
from wagtail.images.blocks import ImageChooserBlock
from wagtail.admin.panels import FieldPanelclassTechArticlePage(Page):
intro = models.CharField(max_length=180, blank=True)
body = StreamField(
[
("para", blocks.RichTextBlock(features=["bold", "link", "code"])),
("code", blocks.StructBlock([
("lang", blocks.ChoiceBlock(choices=[
("python", "Python"),
("shell", "Shell"),
("sql", "SQL"),
])),
("text", blocks.TextBlock()),
])),
("image", ImageChooserBlock()),
("note", blocks.TextBlock(help_text="编辑备注,适合放排查结论")),
],
use_json_field=True,
blank=True,
)
content_panels = Page.content_panels + [
FieldPanel("intro"),
FieldPanel("body"),
]
这段代码看着不花,但现场够用了。
运营在后台看到的是一块一块内容,可以拖动,可以重复加。研发看到的是明确结构,模板里也好处理。你要给代码块加复制按钮,要给图片加懒加载,要给 note 单独出灰底样式,都不用去富文本里猜 HTML。
模板也别写成一锅粥:
{% for item in page.body %}
{% if item.block_type == "para" %}
<div class="article-p">{{ item.value|richtext }}</div>
{% elif item.block_type == "code" %}
<pre data-lang="{{ item.value.lang }}"><code>{{ item.value.text }}</code></pre>
{% elif item.block_type == "image" %}
{% image item.value width-900 %}
{% elif item.block_type == "note" %}
<div class="ops-note">{{ item.value }}</div>
{% endif %}
{% endfor %}
这里有个小判断:如果你的内容类型以后会扩张,别偷懒只放一个富文本字段。前期省了十分钟,后面内容治理会还回来。
Wagtail 的 Page 也不是虚的。官方文档里明确说,每种页面类型都是一个 Django model,数据库里会有对应表,每个页面类型可以有自己的字段。 这意味着什么?你原来会写 Django model、QuerySet、索引、校验,那套手感还在。
比如我想让文章发布前必须有 intro,不想靠运营自觉:
from django.core.exceptions import ValidationErrorclassTechArticlePage(Page):
# 省略前面的字段
defclean(self):
super().clean()
if self.live andnot self.intro.strip():
raise ValidationError({
"intro": "要发布就补一句摘要,列表页不能空着。"
})
这种校验放在模型层,比在模板里兜底靠谱。模板兜底只能让页面不炸,不能保证内容质量。
还有一个东西也常用:Snippet。
页脚链接、作者信息、专题标签、首页推荐位,这些内容不一定是一张页面,但又要给编辑人员维护。Wagtail 的 Snippets 本质上是 Django model,不继承 Page,也不挂在页面树下,适合做这些可复用内容。
from wagtail.snippets.models import register_snippet
from wagtail.admin.panels import FieldPanel@register_snippet
classArticleColumn(models.Model):
name = models.CharField(max_length=40)
slug = models.SlugField(unique=True)
show_in_nav = models.BooleanField(default=False)
panels = [
FieldPanel("name"),
FieldPanel("slug"),
FieldPanel("show_in_nav"),
]
def__str__(self):
return self.name
这个东西做栏目、作者、推荐标签都很舒服。别什么都建 Page,页面树会乱。页面树一乱,编辑找内容像翻仓库,最后又来找研发。
不过 Wagtail 也不是没有坑。
第一,页面查询别乱写。
我见过有人在列表页这么拿数据:
pages = TechArticlePage.objects.live().order_by("-first_published_at")
看着没问题,但如果模板里又访问图片、作者、栏目,SQL 可能就开始碎了。这个时候我一般先开 Django Debug Toolbar 或直接看日志,不先怪 Wagtail。
可以先把列表页控制薄一点:
defget_context(self, request):
ctx = super().get_context(request) ctx["articles"] = (
TechArticlePage.objects
.live()
.public()
.only("title", "intro", "first_published_at")
.order_by("-first_published_at")[:20]
)
return ctx
第二,后台字段别给太自由。
CMS 最怕的不是不能编辑,是太能编辑。颜色、字号、间距、布局都开放,最后每个页面都像临时搭的。Wagtail 的强大不是让运营随便拼,而是让研发把边界写清楚,运营在边界内高效改内容。
第三,别把 Wagtail 当 WordPress 替代品去理解。
它更像是 Django 项目里长出来的一套内容管理能力。你可以拿它做官网、文档站、内容平台、企业门户,也可以把它做成 headless CMS,只让前端通过 API 取内容。但如果你的需求只是三页静态页面,硬上 Wagtail 也没必要。
我对 Wagtail 的评价挺简单:它强在“可控”。
编辑体验比自己手搓后台强太多,代码可控性又比很多传统 CMS 好很多。尤其是 Python 团队,本来就在用 Django,Wagtail 接进去不会有明显割裂感。
真要落地,我建议先从一个内容页开始,不要一口气把全站后台都重构掉。先把文章页、活动页、专题页这种变化多的地方迁进去。跑两轮发布流程,再决定要不要扩大。
工具这东西,能不能用住,不看官网写得多漂亮,看线上改需求的时候,研发会不会骂人。Wagtail 在这一点上,至少没让我骂太多。