FormFieldBase is the base class all form field plugins must inherit from. It
has a single name field used to identify the field in submitted data and for
clash detection. The base class is used instead of duck typing in places where
the code encounters a mix of form field plugins and other
django-content-editor plugins (e.g. rich text blocks between fields).
FormFieldBase defines the form field plugin API:
get_fields(**kwargs): Return a dictionary of Django form fields.get_initial(): Return a dictionary of initial values for those fields.get_cleaners(): Return a list of callables which receive the form instance, return cleaned data, and may raiseValidationError.get_loaders(): Return a list of loader callables (see Loaders below).
FormField extends FormFieldBase with a standard set of attributes for
typical form fields: label, help_text, and is_required. You do not
have to use this model — it exists to provide useful defaults for common cases.
SimpleFieldBase covers many standard field types (text, email, URL, date,
integer, textarea, checkbox, select, radio, and multi-select) with a single
database table, using Django's proxy model mechanism.
Instantiate it in your project and create proxy models for each field type you need:
class SimpleField(forms_models.SimpleFieldBase, ConfiguredFormPlugin):
pass
Text = SimpleField.proxy(SimpleField.Type.TEXT)
Email = SimpleField.proxy(SimpleField.Type.EMAIL)
Select = SimpleField.proxy(SimpleField.Type.SELECT)
# etc.SimpleFieldBase has a corresponding SimpleFieldInline in
feincms3_forms.admin which shows and hides admin fields depending on the
field type — for example, it hides the placeholder field for checkboxes since
browsers do not support them.
ConfiguredForm is the top-level model that ties a form together. Subclass
it in your project and define FORMS as a list of FormType instances,
one per form variant your project supports.
FormType accepts:
key: A unique string identifier.label: Human-readable name shown in the admin.regions: A list ofRegionobjects, or a callable that takes theConfiguredForminstance and returns a list of regions.form_class(optional): Dotted path to a base form class for the dynamically created form.validate(optional): Dotted path to a validation function called byConfiguredFormAdmin.process(optional): Dotted path to the function called after a valid submission. feincms3-forms never calls this directly, but it's a useful convention.
The renderer is responsible for creating and instantiating the Django form class from a list of content editor plugins.
create_form(plugins, form_class=None, form_kwargs=None)- Creates and instantiates a form from a list of plugins.
form_classdefaults to a plainforms.Form.form_kwargsare passed to the form constructor. short_prefix(obj, suffix)- Returns a short, stable form prefix string based on the object's primary key. Useful when multiple forms may appear on the same page.
The created form has a get_form_fields(plugin, strip_name_prefix=False)
method that returns a dictionary of bound form fields for the given plugin.
Pass strip_name_prefix=True to strip the plugin's name prefix from
the dictionary keys, which makes it easier to reference fields by simple names
in templates (see :ref:`strip-name-prefix`).
The feincms3_forms.validation module provides validators for checking
that a configured form meets your application's requirements. These are called
from your validate function, which ConfiguredFormAdmin invokes
automatically.
validate_uniqueness(fields)- Checks for duplicate field names. Returns warnings for any duplicates.
validate_required_fields(fields, required)- Checks that all field names in
requiredare present. Returns errors for missing fields. validate_fields(fields, schema)- Validates fields against a schema dict mapping field names to expected
attributes (
type,is_required, etc.). Returns warnings for missing fields and errors for attribute mismatches.
Example validation function:
from feincms3_forms.validation import (
validate_fields,
validate_required_fields,
validate_uniqueness,
)
def validate_contact_form(configured_form):
fields = configured_form.get_formfields_union(
plugins=renderer.plugins(), attributes=["type", "is_required"]
)
return [
*validate_uniqueness(fields),
*validate_required_fields(fields, {"email"}),
*validate_fields(
fields,
{
"email": {"type": "email", "is_required": True},
},
),
]Reference the function in your FormType:
forms_models.FormType(
key="contact",
label="contact form",
regions=[Region(key="form", title="form")],
validate="app.forms.forms.validate_contact_form",
process="app.forms.forms.process_contact_form",
)Loaders convert serialized form submission data (stored as a JSON dict) back
into a human-readable format for display and export. Each form field plugin
should implement get_loaders() returning a list of loader callables. Each
loader takes the submission data dict and returns a dict:
{"name": "field_name", "label": "Field Label", "value": "submitted value"}
Simple loader:
from functools import partial
from feincms3_forms.models import simple_loader
class MyField(FormFieldBase, ConfiguredFormPlugin):
label = models.CharField(max_length=200)
def get_loaders(self):
return [partial(simple_loader, label=self.label, name=self.name)]Compound fields (those that generate multiple form fields) return multiple loaders, one per generated field:
def get_loaders(self):
return [
partial(simple_loader, label=self.label_from, name=f"{self.name}_from"),
partial(simple_loader, label=self.label_until, name=f"{self.name}_until"),
]The feincms3_forms.reporting module provides helpers for working with
submission data.
get_loaders(plugins)Collects all loaders from a list of plugin instances and returns a flat list of loader callables:
from content_editor.contents import contents_for_item from feincms3_forms.reporting import get_loaders contents = contents_for_item(configured_form, plugins=renderer.plugins()) loaders = get_loaders(contents) for loader in loaders: row = loader(submitted_data) print(f"{row['label']}: {row['value']}")
simple_report(contents, data)Generates an HTML summary of submitted data suitable for display in the Django admin:
from django.contrib.admin import display from feincms3_forms.reporting import simple_report @display(description="Submitted Data") def pretty_data(self, obj): return simple_report( contents=contents_for_item( obj.configured_form, plugins=renderer.plugins() ), data=obj.data, )
value_default(row, default="Ø")Returns
defaultwhen the field value inrowis empty:values = [value_default(loader(data)) for loader in loaders]