Wagtail 表单组件实战:用 contrib.forms 构建可自动发邮件的联系页(FormPage)完整指南
发布时间:2026/9/14 13:24:58 作者:尧图编辑部 阅读量:1,286
完整指南)
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.pyAbstractFormField、AbstractForm、AbstractEmailForm、FormSubmission等模型forms.pyFormBuilder负责把后台定义的字段类型动态映射为 Django Form 字段panels.pyFormSubmissionsPanel在编辑面板中展示提交统计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_deletemodels.CASCADE, related_nameform_fields) class FormPage(AbstractEmailForm): intro RichTextField(blankTrue) thank_you_text RichTextField(blankTrue) 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_deletemodels.CASCADE, related_nameform_fields)在FormField与FormPage之间建立父子关系字段从属于某个表单页删除表单页时级联删除其字段反向关系名为form_fields。AbstractFormField继承自Orderable因此在后台可以为字段调整上下排序。从源码看AbstractFormField自带的字段为字段说明label字段显示标签必填field_type字段类型取值见FORM_FIELD_CHOICES下文详述required是否必填默认Truechoices选项列表逗号或换行分隔仅适用于 checkboxes / radio / dropdowndefault_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 textCharFieldmax_length255multilineMulti-line textCharFieldTextarea控件emailEmailEmailFieldnumberNumberDecimalFieldurlURLURLFieldcheckboxCheckboxBooleanFieldcheckboxesCheckboxesMultipleChoiceFieldCheckboxSelectMultipledropdownDrop downChoiceFieldmultiselectMultiple selectMultipleChoiceFieldradioRadio buttonsChoiceFieldRadioSelectdateDateDateFielddatetimeDate/timeDateTimeFieldhiddenHidden fieldCharFieldHiddenInput控件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 classpage-form action{% pageurl page %} methodPOST {% csrf_token %} {{ form.as_div }} button typeSubmitSubmit/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, pageself, userrequest.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(pageself.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继承自AbstractFormSubmissionform_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),仅供参考