Wagtail - 添加 CSS class 或占位符以形成构建器字段

Wagtail - adding CSS class or placeholder to form builder fields

我正在尝试通过 Wagtail 管理表单生成器将 css 类(用于列宽)和占位符文本添加到我的 Wagtail 表单字段中。我试过使用 wagtail.contrib.forms 和 wagtailstreamforms 包都无济于事。

我知道它说 here Wagtail 表单不能替代 Django 表单。但是,如果没有这样的基本功能,它的用途就会受到限制。

下面的解决方案是一种利用 Wagtail contrib 表单构建器向 UI 添加字段的方法,CMS 用户可以在其中添加自定义 classes 和占位符字段。

实施

  • 假定您有一个与 Wagtail form builder docs.
  • 中的实现类似的工作 FormPage
  • 首先,您需要向 FormField 模型添加一个新字段,在下面的示例中我称之为 field_classname,请记住 运行 Django 迁移来更新您的数据库
  • 要确保该字段显示在管理 UI 中,您需要修改 panels,这类似于修改 Page 模型中的面板。
  • 此时您应该能够打开 FormPage 的管理 UI 并查看新字段并能够将值保存到其中。
  • 下一步是添加一个自定义 FormBuilder class 来扩展通常使用的那个,然后在您的 FormPage 模型上通过 [=20= 将其设置为属性],这类似于 new fields can be added as described in the docs.
  • CustomFormBuilder 将使用包装函数覆盖方法 get_create_field_function,该包装函数将 return 生成的字段(将是 Django Field instance)。
  • 每个 Field 实例将被 return 编辑几乎相同,除了对 widget attrs 的更新,它本质上是一个可以添加任何内容的字典。
  • 重要提示: 使用 class 属性会将 class 名称添加到字段中,但可能不会将其添加到您想要的位置,但请先尝试此操作.
  • 假设您对模板渲染有更精细的控制,您需要将此 attr 传递给渲染的每个字段的模板 div,在下面的代码中我使用了相同的 field_classname键。
  • 模板中的关键变化是提取我们 CustomFormBuilder -> <div class="fieldWrapper {{ field.field.widget.attrs.field_classname }}" aria-required={% if field.field.required %}"true"{% else %}"false"{% endif %}>
  • 设置的小部件属性自定义值

models.py

from django.db import models

from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField

# ... other imports


class FormField(AbstractFormField):

    page = ParentalKey("FormPage", related_name="form_fields", on_delete=models.CASCADE)
    
    # add custom fields to FormField model
    field_classname = models.CharField("Field classes", max_length=254, blank=True)
    placeholder = models.CharField("Placeholder", max_length=254, blank=True)
    
    # revise panels so that the field can be edited in the admin UI
    panels = AbstractFormField.panels + [
        FieldPanel("field_classname"),
        FieldPanel("placeholder"),
    ]


class CustomFormBuilder(FormBuilder):
    def get_create_field_function(self, type):
        """
        Override the method to prepare a wrapped function that will call the original
        function (which returns a field) and update the widget's attrs with a custom
        value that can be used within the template when rendering each field.
        """

        create_field_function = super().get_create_field_function(type)

        def wrapped_create_field_function(field, options):

            created_field = create_field_function(field, options)
            created_field.widget.attrs.update(
                # {"class": field.field_classname} # Important: using the class may be sufficient, depending on how your form is being rendered, try this first.
                {"field_classname": field.field_classname} # this is a non-standard attribute and will require custom template rendering of your form to work
                {"placeholder": field.placeholder},
            )

            return created_field

        return wrapped_create_field_function


class FormPage(AbstractEmailForm):
    form_builder = CustomFormBuilder  # use custom form builder to override behaviour

    # ... other form page fields, panels etc

templates/form_page.html

{% comment %}
You could render your form using a Django rendering shortcut such as `{{ form.as_p }}` but that will tend towards unsemantic code, and make it difficult to style. You can read more on Django form at:
https://docs.djangoproject.com/en/1.10/topics/forms/#form-rendering-options
{% endcomment %}
<form action="{% pageurl page %}" 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 field in form %}
        <div class="fieldWrapper {{ field.field.widget.attrs.field_classname }}" 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 %}

    <input type="submit">
</form>