Reputation: 461
I'm trying to add css classes (for column width) and placeholder text to my Wagtail form fields via the Wagtail admin form builder. I've tried using wagtail.contrib.forms and also the wagtailstreamforms package to no avail.
I know it says here that the Wagtail forms aren't a replacement for Django forms. However, without such basic functionality it's limited in its usefulness.
Upvotes: 1
Views: 1607
Reputation: 5196
The below solution is a way to leverage the Wagtail contrib form builder to add a field to the UI where CMS users can add custom classes and a placeholder field.
FormPage
similar to the implementation in the Wagtail form builder docs.FormField
model, in the example below I have called this field_classname
, remember to run Django migrations to update your DB.panels
, this is similar to modifying the panels in a Page
model.FormPage
and see the new field and be able to save values to it.FormBuilder
class that extends the one that is normally used and then on your FormPage
model set this as an attribute via form_builder
, this is similar to how new fields can be added as described in the docs.CustomFormBuilder
will override the method get_create_field_function
with a wrapper function that will return the generated field (which will be a Django Field instance).Field
instance will be returned almost the same, except for an update to the widget attrs which is essentially a Dict that you can add anything to.class
attr will add the class name to the field but may not add it where you want, try this first though.div
that is rendered, in the code below I have used the same field_classname
key.CustomFormBuilder
-> <div class="fieldWrapper {{ field.field.widget.attrs.field_classname }}" aria-required={% if field.field.required %}"true"{% else %}"false"{% endif %}>
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
{% 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>
Upvotes: 4