Wagtail - добавление CSS-класса или заполнителя к полям конструктора форм

Я пытаюсь добавить классы css (для ширины колонок) и текст-заполнитель в поля формы Wagtail через конструктор форм администратора Wagtail. Я пробовал использовать wagtail.contrib.forms, а также пакет wagtailstreamforms безрезультатно.

Я знаю, что здесь написано , что формы Wagtail не заменяют формы Django. Однако, без такой базовой функциональности его польза ограничена.

Нижеприведенное решение представляет собой способ использования конструктора форм Wagtail contrib для добавления поля в пользовательский интерфейс, где пользователи CMS могут добавлять пользовательские классы и поле-заполнитель.

Выполнение

  • Предполагается, что у вас есть работающий FormPage, похожий на реализацию в документации по построению форм Wagtail .
  • Сначала вам нужно будет добавить новое поле в модель FormField, в примере ниже я назвал его field_classname, не забудьте запустить миграции Django для обновления вашей БД.
  • Для того чтобы поле отображалось в пользовательском интерфейсе администратора, вам нужно изменить panels, это похоже на изменение панелей в Page модели.
  • На этом этапе вы должны быть в состоянии открыть пользовательский интерфейс администратора для FormPage и увидеть новое поле и иметь возможность сохранять значения для него.
  • Следующим шагом будет добавление пользовательского FormBuilder класса, который расширяет тот, который обычно используется, а затем в вашей FormPage модели установите его как атрибут через form_builder, это похоже на то, как можно добавить новые поля, как описано в документации .
  • Этот CustomFormBuilder переопределит метод get_create_field_function с функцией-оберткой, которая вернет сгенерированное поле (которое будет экземпляром Django Field).
  • Каждый экземпляр Field будет возвращаться почти таким же, за исключением обновления аттрибутов widget attrs, которые по сути являются Dict'ом, в который вы можете добавить что угодно.
  • ВАЖНО: Использование class attr добавит имя класса в поле, но может не добавить его туда, куда вы хотите, попробуйте сначала это.
  • Предполагая, что у вас есть более детальный контроль над рендерингом шаблона, вам нужно будет передать этот attr в шаблон для каждого поля div, которое рендерится, в коде ниже я использовал тот же field_classname ключ.
  • Критическим изменением в шаблоне является извлечение пользовательского значения attrs виджета, установленного нашим 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>
Вернуться на верх