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>