Wagtail 表单组件实战:用 contrib.forms 构建可自动发邮件的联系页(FormPage)完整指南
2026/9/14 13:24:53 网站建设 项目流程

Wagtail 表单组件实战:用 contrib.forms 构建可自动发邮件的联系页(FormPage)完整指南

【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail

本文基于 Wagtail 官方教程《Create contact page》展开,讲解如何为站点添加一个“联系页”(Contact Page):从定义FormField/FormPage两个模型、编写表单与落地页模板,到执行数据库迁移、在后台创建并发布表单页,最后为联系表单添加样式。读完后你将掌握 Wagtailwagtail.contrib.forms表单模块的完整使用方式,并能从源码层面理解表单渲染、提交、存储与邮件通知的全链路实现。

一、为什么用 Wagtail 内置表单组件

在作品集(Portfolio)类站点中,联系页是连接潜在客户、雇主或其他从业者的入口。Wagtail 提供了一个专门的表单应用 wagtail/contrib/forms,其核心文件结构如下:

  • models.py:AbstractFormFieldAbstractFormAbstractEmailFormFormSubmission等模型;
  • forms.py:FormBuilder,负责把后台定义的字段类型动态映射为 Django Form 字段;
  • panels.py:FormSubmissionsPanel,在编辑面板中展示提交统计;
  • views.py:提交记录列表视图(含 CSV 导出);
  • wagtail_hooks.py:注册后台Forms菜单项。

你不需要手写表单处理逻辑——只要在models.py中定义两个模型并编写两个模板,表单的渲染、校验、存储、发邮件就都由框架完成。

二、定义模型:FormField 与 FormPage

按教程步骤,修改你项目中的base/models.py,在已有模型(如NavigationSettingsFooterText)之后添加如下代码:

from django.db import models # import parentalKey: from modelcluster.fields import ParentalKey # import FieldRowPanel and InlinePanel: from wagtail.admin.panels import ( FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel, PublishingPanel, ) from wagtail.fields import RichTextField from wagtail.models import ( DraftStateMixin, PreviewableMixin, RevisionMixin, TranslatableMixin, ) # import AbstractEmailForm and AbstractFormField: from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField # import FormSubmissionsPanel: from wagtail.contrib.forms.panels import FormSubmissionsPanel from wagtail.contrib.settings.models import ( BaseGenericSetting, register_setting, ) from wagtail.snippets.models import register_snippet # ... keep the definition of NavigationSettings and FooterText. Add FormField and FormPage: class FormField(AbstractFormField): page = ParentalKey("FormPage", on_delete=models.CASCADE, related_name="form_fields") class FormPage(AbstractEmailForm): intro = RichTextField(blank=True) thank_you_text = RichTextField(blank=True) content_panels = AbstractEmailForm.content_panels + [ FormSubmissionsPanel(), FieldPanel("intro"), InlinePanel("form_fields"), FieldPanel("thank_you_text"), MultiFieldPanel( [ FieldRowPanel( [ FieldPanel("from_address"), FieldPanel("to_address"), ] ), FieldPanel("subject"), ], "Email", ), ]

2.1 FormField:可排序的表单字段定义

FormField继承自 AbstractFormField。page = ParentalKey("FormPage", on_delete=models.CASCADE, related_name="form_fields")FormFieldFormPage之间建立父子关系:字段从属于某个表单页,删除表单页时级联删除其字段,反向关系名为form_fieldsAbstractFormField继承自Orderable,因此在后台可以为字段调整上下排序。

从源码看,AbstractFormField自带的字段为:

字段说明
label字段显示标签(必填)
field_type字段类型,取值见FORM_FIELD_CHOICES(下文详述)
required是否必填,默认True
choices选项列表,逗号或换行分隔,仅适用于 checkboxes / radio / dropdown
default_value默认值,checkboxes 支持逗号或换行分隔多个值
help_text帮助文本
clean_name字段的安全键名(label 转为 ascii_snake_case),作为提交数据 JSON 中的键。源码在 save() 方法 中只在首次创建时生成,之后修改 label 不会更新clean_name,以保证历史提交数据仍然有效

2.2 支持的字段类型

源码中 FORM_FIELD_CHOICES 定义了后台可选的全部字段类型,FormBuilder通过create_<类型>_field方法动态构造对应的 Django 表单字段(见 forms.py):

类型值中文名生成的 Django 字段
singlelineSingle line textCharField(max_length=255)
multilineMulti-line textCharField+Textarea控件
emailEmailEmailField
numberNumberDecimalField
urlURLURLField
checkboxCheckboxBooleanField
checkboxesCheckboxesMultipleChoiceField+CheckboxSelectMultiple
dropdownDrop downChoiceField
multiselectMultiple selectMultipleChoiceField
radioRadio buttonsChoiceField+RadioSelect
dateDateDateField
datetimeDate/timeDateTimeField
hiddenHidden fieldCharField+HiddenInput控件

2.3 FormPage:继承邮件发送能力

FormPage继承自 AbstractEmailForm,其多重继承为EmailFormMixin, FormMixin, Page。与AbstractForm相比,AbstractEmailForm额外提供“表单转邮件”能力,由 EmailFormMixin 定义三个字段:

  • to_address:收件地址,可选,支持逗号分隔多个地址;源码通过 validate_to_address 校验每个地址的合法性;
  • from_address:发件人地址;
  • subject:邮件主题。

教程中的content_panels把这些邮件字段组织到名为 "Email" 的MultiFieldPanel中,并加入FormSubmissionsPanel()以在编辑页顶部展示提交统计。表单页自身还新增了intro(表单页引言)和thank_you_text(提交成功后的感谢文案)两个富文本字段。

三、模板:form_page.html 与 form_page_landing.html

定义完模型后,必须创建两个模板。form_page模板与普通 Wagtail 模板的区别在于:模板上下文多了一个名为form的变量,其中包含一个 DjangoForm对象,与常规的Page变量同时传入。form_page_landing.html则是标准的 Wagtail 模板——当用户成功提交表单后,站点会渲染它作为落地页。

创建base/templates/base/form_page.html

{% extends "base.html" %} {% load wagtailcore_tags %} {% block body_class %}template-formpage{% endblock %} {% block content %} <h1>{{ page.title }}</h1> <div>{{ page.intro|richtext }}</div> <form class="page-form" action="{% pageurl page %}" method="POST"> {% csrf_token %} {{ form.as_div }} <button type="Submit">Submit</button> </form> {% endblock content %}

创建base/templates/base/form_page_landing.html

{% extends "base.html" %} {% load wagtailcore_tags %} {% block body_class %}template-formpage{% endblock %} {% block content %} <h1>{{ page.title }}</h1> <div>{{ page.thank_you_text|richtext }}</div> {% endblock content %}

3.1 为什么落地页模板必须叫form_page_landing.html

这不是命名巧合。从源码看,FormMixin.init在页面未显式指定landing_page_template时,会基于页面模板名自动推导落地页模板:取模板名去掉扩展名后插入_landing,例如form_page.htmlform_page_landing.html。因此两个模板必须成对命名,落地页模板才会被正确解析。

3.2 提交处理流程:serve 方法

表单页的serve方法(FormMixin.serve)实现了完整的提交闭环:

  1. POST 请求self.get_form(request.POST, request.FILES, page=self, user=request.user)构建表单实例;form.is_valid()通过后调用process_form_submission(form)保存提交记录,随后render_landing_page()渲染落地页并返回;
  2. GET 请求:构建空表单实例,将context["form"] = form放入模板上下文,渲染form_page.html——这就是模板里能直接用{{ form.as_div }}的原因。

注意:serve在验证通过后总是先渲染落地页(除非子类覆写render_landing_page),所以落地页模板中可用form_submission上下文变量访问本次提交对象。

此外源码还定义了 preview_modes:后台预览提供 "Form" 和 "Landing page" 两种模式,serve_preview会按模式分别调用render_landing_page或常规预览,方便发布前检查两个页面。

四、迁移数据库

添加两个新模型后,按教程执行:

python manage.py makemigrations python manage.py migrate

这会在你的base应用中生成FormFieldFormPage的迁移文件。同时注意:wagtail.contrib.forms自身也带有一组迁移(见 migrations,其中包含FormSubmission等模型),首次安装表单应用时同样由migrate一并创建,无需手工建表。

五、后台创建并发布联系页

数据库迁移完成后,按以下步骤在后台添加联系信息:

  1. 在 Home 下创建 Form page: a. 重启开发服务器; b. 进入管理后台; c. 点击侧边栏的Pages; d. 点击Home; e. 点击页面顶部的+图标(Add child page); f. 在页面类型列表中选择Form page
  2. 在编辑面板中填入所需数据:表单页标题、intro、用InlinePanel添加表单字段(每个字段可设置标签、类型、是否必填、选项、默认值、帮助文本)、感谢文案,以及 Email 分组中的发件人/收件人与主题。
  3. 发布该Form Page

编辑面板顶部的提交统计来自 FormSubmissionsPanel:其BoundPanel通过submissions缓存属性查询get_submission_class().objects.filter(page=self.instance),页面尚未创建(无 pk)时返回空集;只有存在提交记录(is_shown基于提交数判断)时面板才显示,并展示提交总数与最近一次提交时间(last_submit_time)。

发布表单页后,后台侧边栏还会出现全局的Forms菜单项。从 wagtail_hooks.py 看,该菜单通过register_admin_menu_item钩子注册,指向admin/forms/,且FormsMenuItem.is_shown仅在用户至少拥有一个表单的提交读取权限时才显示。进入后可以看到所有表单页的提交记录列表,SubmissionsListView 继承了SpreadsheetExportMixin,支持将提交结果导出为 CSV 表格。

六、邮件通知的实现原理

AbstractEmailForm的邮件行为全部位于 EmailFormMixin:

  • 覆写的process_form_submission先调用父类保存FormSubmission,然后仅当to_address非空时才发送通知邮件,因此表单页可以只存数据不发邮件;
  • send_mailto_address按逗号拆分为多个地址,调用wagtail.admin.mail.send_mail(即 Django 的send_mail封装)发送;
  • render_email把表单数据逐字段渲染为标签: 值的文本行:多值列表用逗号拼接,datetime/date值按SHORT_DATETIME_FORMAT/SHORT_DATE_FORMAT格式化。

提交数据本身则保存在 FormSubmission(继承自AbstractFormSubmission):form_dataJSONField(以各字段的clean_name为键),page外键指向表单页,submit_timeauto_now_add自动记录。

前提说明:邮件实际送达依赖 Django 项目自身的EMAIL_BACKEND等设置(如django.core.mail.backends.console_email或 SMTP 配置),Wagtail 表单模块只负责生成并调用发送,不负责配置邮件服务器。

七、为联系页添加样式

最后,为表单排版。将以下 CSS 追加到mysite/static/css/mysite.css文件:

.page-form label { display: block; margin-top: 10px; margin-bottom: 5px; } .page-form :is(textarea, input, select) { width: 100%; max-width: 500px; min-height: 40px; margin-top: 5px; margin-bottom: 10px; } .page-form .helptext { font-style: italic; }
  • :is(textarea, input, select)选择器统一约束所有表单控件:宽度撑满容器但最大 500px,最小高度 40px,保持垂直间距;
  • {{ form.as_div }}渲染时每个字段是<div>包裹的 label + 控件结构,因此label设为块级显示并上下留白;
  • .helptext对应字段定义中help_text字段渲染出的帮助文本,斜体区分于正文。

至此,联系页的模型、模板、迁移、后台操作与样式全部完成。继续教程后续章节时,你可以在此基础上为站点添加作品集页面(见 create_portfolio_page.md)。

八、关键源码索引

能力源码位置
字段类型清单与表单字段模型wagtail/contrib/forms/models.py#L22-L36、wagtail/contrib/forms/models.py#L79-L172
提交/预览/serve 流程wagtail/contrib/forms/models.py#L286-L318
邮件发送逻辑wagtail/contrib/forms/models.py#L333-L391
字段类型到 Django 字段的动态映射wagtail/contrib/forms/forms.py#L33-L117
编辑面板提交统计wagtail/contrib/forms/panels.py#L7-L48
后台 Forms 菜单注册wagtail/contrib/forms/wagtail_hooks.py#L10-L31
提交记录列表与 CSV 导出wagtail/contrib/forms/views.py#L205

【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询