【问题标题】:How to group individual form fields into sections with Wagtail formbuilder如何使用 Wagtail formbuilder 将单个表单字段分组为部分
【发布时间】:2021-11-02 07:04:17
【问题描述】:

如何从 Wagtail 的表单构建器中获取单个字段,并将它们显示为呈现表单中的两个单独的部分(边)。

在下面的表单代码中,名称和选择服务是一个部分,其他部分是如何使用 for 循环和 wagtail 中的单个表单字段的其他部分。

目标布局线框示例

products/models.py

from django.db import models
from wagtail.core.models import Page
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.images.blocks import ImageChooserBlock
from streams import blocks
from wagtail.core.fields import StreamField
from wagtail.images.edit_handlers import ImageChooserPanel
from django.db import models

from wagtail.core.models import Page
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.core import blocks

from wagtail.core.fields import StreamField

from streams import blocks

from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import (
    FieldPanel,
    FieldRowPanel,
    InlinePanel,
    MultiFieldPanel
)
from wagtail.core.fields import RichTextField
from wagtail.contrib.forms.models import (
    AbstractEmailForm,
    AbstractFormField
)
from wagtail.core.models import Page

# Create your models here.


class FormField(AbstractFormField):
        page = ParentalKey(
        'ProductPage',
        on_delete=models.CASCADE,
        related_name='form_fields',
    )


class ProductPage(AbstractEmailForm):

    template = "products.html"

    productslist = StreamField(
        [
            ("title_and_text", blocks.TitleAndTextBlock()),
            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("cards", blocks.CardBlock()),
            ("cta", blocks.CTABlock()),
            ('image', ImageChooserBlock()),
        ],
        null=True,
        blank=True,
    )   

    header_title = models.CharField(max_length=200, blank=True, null=True)
    meta_content = models.CharField(max_length=200, blank=True, null=True) 

    intro = RichTextField(blank=True)
    thank_you_text = RichTextField(blank=True)

    content_panels = AbstractEmailForm.content_panels + [
        StreamFieldPanel('productslist'),
        FieldPanel('header_title'),
        FieldPanel('meta_content'),
        FieldPanel('intro'),
        InlinePanel('form_fields', label='Form Fields'),
        FieldPanel('thank_you_text'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('from_address', classname="col6"),
                FieldPanel('to_address', classname="col6"),
            ]),
            FieldPanel("subject"),
        ], "Email Notification Config"),


    ]

Products.html

<form id="quickContact" class="bg-white p-4" action="{% pageurl page %}" method="POST">
        {% csrf_token %}
        <div class="row">
          <div class="col-lg-6 col-12">
            <div class="form-group mb-3">
              <input type="text" class="form-control border-0 bg-dark bg-opacity-10 border-0 font-14 ps-4" name="name" placeholder="ชื่อ" >
            </div>
          </div>
          <div class="col-lg-6 col-12">
            <div class="form-group mb-3">
              <select class="form-control form-select border-0 bg-dark bg-opacity-10 border-0 font-14 ps-4" name="select_service">
                <option>เลือกบริการ</option>
              </select>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-lg-6 col-12">
            <div class="form-group mb-3">
              <input type="text" class="form-control border-0 bg-dark bg-opacity-10 border-0 font-14 ps-4" placeholder="ชื่อ บริษัท">
            </div>
            <div class="form-group mb-3">
              <input type="text" class="form-control border-0 bg-dark bg-opacity-10 border-0 font-14 ps-4" placeholder="อีเมล">
            </div>
            <div class="form-group mb-3">
              <input type="text" class="form-control border-0 bg-dark bg-opacity-10 border-0 font-14 ps-4" placeholder="เบอร์ติดต่อ">
            </div>
          </div>
          <div class="col-lg-6 col-12">
            <textarea class="bg-dark bg-opacity-10 border-0 form-control font-14 ps-4" placeholder="คำอธิบาย" rows="7"></textarea>
          </div>
        </div>

        <div class="row">
          <div class="col-12 mt-3 text-center">
            <button class="btn btn-primary w-50 rounded-pill">ส่ง</button>
          </div>
        </div>
      </form>
    </div>
  </div>

【问题讨论】:

  • 你的意思是两个“部分”的形式吗?
  • 是的,两部分形式..我已经给出了上面的图像,就像那个图像我需要两部分形式如何做到这一点

标签: django wagtail


【解决方案1】:
  • 此答案采用的方法是添加一个名为“section”的特殊字段类型,用户可以在管理界面中添加该字段类型以指示字段部分的开始。
  • 此方法向后兼容,可确保表单、报告或模板上的任何现有字段都不会中断(即使未添加任何部分)。

第 1 步 - 为您的字段类型添加“部分”选项

  • 此处的目标是允许用户在 Wagtail 管理员中创建一个不是字段但充当“部分开头”的字段。
  • 重要提示:在修改FormField 数据模型时,您需要在此代码机会后运行./manage.py makemigrations,然后运行./manage.py migrate

products/models.py

#... other imports
from wagtail.contrib.forms.models import (
    AbstractEmailForm,
    AbstractFormField,
    FORM_FIELD_CHOICES,
)


class FormField(AbstractFormField):
    page = ParentalKey(
        "ProductPage",
        related_name="form_fields",
        on_delete=models.CASCADE,
    )

    field_type = models.CharField(
        verbose_name="field type",
        max_length=16,
        choices=FORM_FIELD_CHOICES + (("section", "Section"),),
    )

第 2 步 - 确保您可以将分组的字段输出为模板中的部分

  • 我们现在将创建一个自定义 FormBuilder,以便我们可以覆盖为您的模板构建表单的行为。
  • 如果没有这个,你只会得到一个错误,因为“部分”字段无法知道应该呈现什么,实际希望是使用这个“字段”作为启动新部分组的一种方式。
  • 我们将其称为FieldsetFormBuilder,因为它允许我们根据部分字段类型的使用将字段分组到字段集中。
  • 创建一个新类,代码如下。您可以在文档 https://docs.wagtail.io/en/stable/reference/contrib/forms/customisation.html#adding-a-custom-field-type 中阅读有关使用自定义 FormBuilder 的更多示例

products/models.py


#...other imports
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.models import (
    AbstractEmailForm,
    AbstractFormField,
    FORM_FIELD_CHOICES,
)


class FieldsetFormBuilder(FormBuilder):
    def __init__(self, fields):
        """
        Assign the `fields` as a subset of the fields, excluding fieldset types.
        Assign the `all_fields` as the raw fields to be used when generating the
        fieldset data.
        """
        self.all_fields = fields
        self.fields = fields.exclude(field_type="section")

    def prepare_get_fieldsets(self, allow_empty=False, field_type="section"):
        """
        Prepare a function which will have an array fieldset data that contains
        the keys for the fields in that fieldset and the `options` + `id` for the fieldset.
        This function will be called as an instance method on the Form and can be accessed
        within the template as `form.get_fieldsets` which will return an array of tuples
        where the first item is an array of fields and the second item is the fieldset data.
        """

        fieldsets = [[[], {}]]

        for field in self.all_fields:
            is_section = field.field_type == field_type

            if is_section:
                options = self.get_field_options(field)
                options["id"] = f"fieldset-{field.clean_name}"
                fieldsets.append([[], options])
            else:
                fieldsets[-1][0].append(field.clean_name)

        def get_fieldsets(form):

            return [
                (
                    [form[field] for field in fields],
                    options,
                )
                for fields, options in fieldsets
                if bool(fields) or bool(options and allow_empty)
            ]

        return get_fieldsets

    @property
    def formfields(self):
        """
        Prepare a get_fieldsets method to the generated form class so that
        it can be used within templates and access the form for the final
        field content.
        """
        formfields = super().formfields
        formfields["get_fieldsets"] = self.prepare_get_fieldsets()
        return formfields

第 3 步 - 让您的 FormPage 使用自定义表单构建器并确保报告不会尝试输出部分字段

  • 在您的表单页面类 (ProductPage) 中,添加属性 form_builder 以告诉该表单在将最终表单呈现到模板时应该使用自定义类。
  • 我们还想更新 get_form_fieldsget_form_class 方法,以便不会将部分字段输出到报告系统以进行表单响应。

products/models.py

class ProductPage(AbstractEmailForm):
    form_builder = FieldsetFormBuilder # important - must be added
    
    #... fields, panels etc

    # next two method changes will ensure that the 'section' form fields do not show up in your reporting

    def get_form_fields(self, form=False):
        form_fields = super().get_form_fields()

        if form:
            return form_fields

        return form_fields.exclude(field_type="section")

    def get_form_class(self):
        fb = self.form_builder(self.get_form_fields(form=True))
        return fb.get_form_class()

第 4 步 - 在表单模板中使用 fieldset 输出

  • 下面的模板示例与您的示例不完全相同,但应该提供使用这种方法(部分来自 bakerydemo 代码)获得两列布局(使用引导类)的最简单方法。
  • 注意:您的示例模板不包含任何与元素有关的错误消息或可访问性相关属性,此代码示例包括这些。
  • 这里的主要部分是{% for fields, fieldset in form.get_fieldsets %},我们现在可以在我们的表单中遍历一个字段集(也就是一组字段),在每个字段中我们可以获得该部分和各个字段的标签。

templates/myapp/products_page.html

<div class="container">
  <div class="row">
    <div class="col-md-8 form-page">
      <form id="quickContact" action="{% pageurl page %}" class="row bg-white p-4" method="POST" role="form">
        {% csrf_token %}
        {% if form.subject.errors %}
          <ol role="alertdialog">
          {% for error in form.subject.errors %}
            <li role="alert"><strong>{{ error|escape }}</strong></li>
          {% endfor %}
          </ol>
        {% endif %}
        {% for fields, fieldset in form.get_fieldsets %}
          <fieldset class="col-md-6" id="{{ fieldset.id }}">
            {% if fieldset.label %}<legend>{{ fieldset.label }}</legend>{% endif %}
            {% if fieldset.help_text %}<p class="fieldset-help-text">{{ fieldset.help_text }}</p>{% endif %}
            {% for field in fields %}
              <div class="fieldWrapper" aria-required={% if field.field.required %}"true"{% else %}"false"{% endif %}>
                {{ field.label_tag }}{% if field.field.required %}<span class="required">*</span>{% endif %}
                {{ field }}
                {% if field.help_text %}
                  <p class="help">{{ field.help_text|safe }}</p>
                {% endif %}
              </div>
            {% endfor %}
          </fieldset>
        {% endfor %}
        <div class="col-12">
          <input type="submit">
        </div>
      </form>
    </div>
  </div>
</div>

注意事项

  • 关于 fieldset 元素 https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset 的 MDN 文档
  • 使用这种方法的注意事项 - 在编辑界面中看到一个部分的“帮助文本”和“必需”选项可能会让用户有点困惑。 required 复选框可用于指示字段集是必需的,但此答案未设置该逻辑。此外,这种方法不适用于任何更复杂的 Wagtail 功能,例如嵌套内联面板或流场,因为它们需要对旧表单数据和新表单数据进行某种类型的数据转换。
  • 最好指向 Wagtail formbuilder 文档并建议构建复杂的表单可能会遇到 Wagtail 系统的限制,并且在某些时候您可能想要使用 Django 的完整表单系统。 https://docs.wagtail.io/en/stable/reference/contrib/forms/index.html#form-builder

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多