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:
AbstractFormField、AbstractForm、AbstractEmailForm、FormSubmission等模型; - forms.py:
FormBuilder,负责把后台定义的字段类型动态映射为 Django Form 字段; - panels.py:
FormSubmissionsPanel,在编辑面板中展示提交统计; - views.py:提交记录列表视图(含 CSV 导出);
- wagtail_hooks.py:注册后台
Forms菜单项。
你不需要手写表单处理逻辑——只要在models.py中定义两个模型并编写两个模板,表单的渲染、校验、存储、发邮件就都由框架完成。
二、定义模型:FormField 与 FormPage
按教程步骤,修改你项目中的base/models.py,在已有模型(如NavigationSettings、FooterText)之后添加如下代码:
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")在FormField与FormPage之间建立父子关系:字段从属于某个表单页,删除表单页时级联删除其字段,反向关系名为form_fields。AbstractFormField继承自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 字段 |
|---|---|---|
singleline | Single line text | CharField(max_length=255) |
multiline | Multi-line text | CharField+Textarea控件 |
email | EmailField | |
number | Number | DecimalField |
url | URL | URLField |
checkbox | Checkbox | BooleanField |
checkboxes | Checkboxes | MultipleChoiceField+CheckboxSelectMultiple |
dropdown | Drop down | ChoiceField |
multiselect | Multiple select | MultipleChoiceField |
radio | Radio buttons | ChoiceField+RadioSelect |
date | Date | DateField |
datetime | Date/time | DateTimeField |
hidden | Hidden field | CharField+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.html→form_page_landing.html。因此两个模板必须成对命名,落地页模板才会被正确解析。
3.2 提交处理流程:serve 方法
表单页的serve方法(FormMixin.serve)实现了完整的提交闭环:
- POST 请求:
self.get_form(request.POST, request.FILES, page=self, user=request.user)构建表单实例;form.is_valid()通过后调用process_form_submission(form)保存提交记录,随后render_landing_page()渲染落地页并返回; - 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应用中生成FormField与FormPage的迁移文件。同时注意:wagtail.contrib.forms自身也带有一组迁移(见 migrations,其中包含FormSubmission等模型),首次安装表单应用时同样由migrate一并创建,无需手工建表。
五、后台创建并发布联系页
数据库迁移完成后,按以下步骤在后台添加联系信息:
- 在 Home 下创建 Form page: a. 重启开发服务器; b. 进入管理后台; c. 点击侧边栏的
Pages; d. 点击Home; e. 点击页面顶部的+图标(Add child page); f. 在页面类型列表中选择Form page。 - 在编辑面板中填入所需数据:表单页标题、
intro、用InlinePanel添加表单字段(每个字段可设置标签、类型、是否必填、选项、默认值、帮助文本)、感谢文案,以及 Email 分组中的发件人/收件人与主题。 - 发布该
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_mail将to_address按逗号拆分为多个地址,调用wagtail.admin.mail.send_mail(即 Django 的send_mail封装)发送;render_email把表单数据逐字段渲染为标签: 值的文本行:多值列表用逗号拼接,datetime/date值按SHORT_DATETIME_FORMAT/SHORT_DATE_FORMAT格式化。
提交数据本身则保存在 FormSubmission(继承自AbstractFormSubmission):form_data为JSONField(以各字段的clean_name为键),page外键指向表单页,submit_time由auto_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),仅供参考