Package: feincms3-forms
This is an extremely flexible forms builder for the Django admin interface. It
allows usingdjango-content-editorfor your form which enables:Build your own form in the CMS and not have to ask programmers to change
anything.Reorder, add and remove pre-existing fields.Add content (text, images, anything) between form fields.Use regions to add additional structure to a form, e.g. to build configurable
multi-step forms (wizards).Add your own form field plugins with all the flexibility and configurability
you desire.If you only want to integrate short and simple forms (e.g. a contact form)
you’re probably better off usingform_designer. The feincms3 documentation
contains aguide showing how to integrate it.High level overviewThe documentation is very sparse, sorry for that.ModelsFormFieldBaseForm fields have to inheritFormFieldBase.FormFieldBaseonly has anamefield. This field can be checked for clashes etc. The base class is
used instead of duck typing in various places where the code may encounter not
only form field plugins but also other django-content-editor plugins. The
latter are useful e.g. to add blocks of text or other content between form
fields.TheFormFieldBasemodel defines the basic API of form fields:get_fields(): Return a dictionary of form fields.get_initial(): Return initial values of said fields.get_cleaners(): Return a list of callables which receive the form
instance, return the cleaned data and may raiseValidationErrorexceptions.get_loaders(): Return a list of loaders. The purpose of loaders is to
load form submissions, e.g. for reporting purposes. Loaders are callables
which receive the serialized form data and return a dictionary of the
following shape:{"name":...,"label":...,"value":...}.FormFieldTheFormFieldoffers a basic set of attributes for standard fields such as
a label, a help text and whether the field should be required or not. You do
not have to use this model if you want to define your own. It’s purpose is just
to offer a few good defaults.SimpleFieldBaseTheSimpleFieldBaseshould be instantiated in your project and can be used
to cheaply add support for many basic field types such as text fields, email
fields, checkboxes, choice fields and more with a single backing database table
and model.TheSimpleFieldBasehas a correspondingSimpleFieldInlinein thefeincms3_forms.adminmodule which shows and hides fields depending on the
field type. For example, it makes no sense to define placeholders for
checkboxes (browsers do not support them) therefore the field is omitted in the
CMS.RendererThe renderer functions are responsible for creating and instantiating the form
class. Form class creation and instantiation happens at once.ValidationThe validation module offers utilities to validate a form when it it defined in
the CMS. For example, the backend code may require that an email field always
exists and always has a certain predefined name (for exampleemail😏).
These rules are not enforced at the moment but the user is always notified and
can therefore choose to head them. Or bad things may happen depending on the
code you write.ReportingThe reporting functions are mostly useful if you want to do something with
submitted data.Installation and usageCreate a module containing the models for the form builder (app.forms.models):fromcontent_editor.modelsimportRegion,create_plugin_basefromdjango.dbimportmodelsfromfeincms3importpluginsfromfeincms3_formsimportmodelsasforms_modelsclassConfiguredForm(forms_models.ConfiguredForm):FORMS=[forms_models.FormType(key="contact",label="contact form",regions=[Region(key="form",title="form")],# Base class for the dynamically created form:# form_class="...",# Validation hook for configured form (the bundled ModelAdmin# class calls this):# validate="...",# Processing function which you can call after submission# (feincms3-forms never calls this function itself, but it# may be a nice convention):process="app.forms.forms.process_contact_form",),]ConfiguredFormPlugin=create_plugin_base(ConfiguredForm)classSimpleField(forms_models.SimpleFieldBase,ConfiguredFormPlugin):passText=SimpleField.proxy(SimpleField.Type.TEXT)Email=SimpleField.proxy(SimpleField.Type.EMAIL)URL=SimpleField.proxy(SimpleField.Type.URL)Date=SimpleField.proxy(SimpleField.Type.DATE)Integer=SimpleField.proxy(SimpleField.Type.INTEGER)Textarea=SimpleField.proxy(SimpleField.Type.TEXTAREA)Checkbox=SimpleField.proxy(SimpleField.Type.CHECKBOX)Select=SimpleField.proxy(SimpleField.Type.SELECT)Radio=SimpleField.proxy(SimpleField.Type.RADIO)SelectMultiple=SimpleField.proxy(SimpleField.Type.SELECT_MULTIPLE)CheckboxSelectMultiple=SimpleField.proxy(SimpleField.Type.CHECKBOX_SELECT_MULTIPLE)classRichText(plugins.richtext.RichText,ConfiguredFormPlugin):passAdd the processing function referenced above (app.forms.forms):fromdjango.core.mailimportmail_managersfromdjango.httpimportHttpResponsedefprocess_contact_form(request,form,*,configured_form):mail_managers("Contact form",repr(form.cleaned_data))returnHttpResponseRedirect(".")Add the renderer and the view (app.forms.views):fromcontent_editor.contentsimportcontents_for_itemfromdjango.shortcutsimportrenderfromfeincms3.rendererimportRegionRenderer,render_in_context,template_rendererfromfeincms3_forms.rendererimportcreate_form,short_prefixfromapp.formsimportmodelsrenderer=RegionRenderer()renderer.register(models.RichText,template_renderer("plugins/richtext.html"))renderer.register(models.SimpleField,lambdaplugin,context:render_in_context(context,"forms/simple-field.html",{"plugin":plugin,"fields":context["form"].get_form_fields(plugin)},),)defform(request):context={}cf=models.ConfiguredForm.objects.first()contents=contents_for_item(cf,plugins=renderer.plugins())# Add a prefix in case more than one form exists on the same page:form_kwargs={"prefix":short_prefix(cf,"form")}ifrequest.method=="POST":form_kwargs|={"data":request.POST,"files":request.FILES}form=create_form(contents["form"],form_class=cf.type.form_class,form_kwargs=form_kwargs,)ifform.is_valid():returncf.type.process(request,form,configured_form=cf)context["form"]=formcontext["form_other_fields"]=form.get_form_fields(None)context["form_regions"]=renderer.regions_from_contents(contents)returnrender(request,"forms/form.html",context)Theforms/simple-field.htmltemplate referenced above might look as
follows:{%forfieldinfields.values%}{{field}}{%endfor%}An exampleforms/form.html:{%extends"base.html"%}{%loadfeincms3i18n%}{%blockcontent%}<divclass="content"><formclass="form"method="post">{%csrf_token%}{{form.errors}}{%render_regionform_regions'form'%}{%forfieldinform_other_fields.values%}{{field}}{%endfor%}<buttontype="submit">Submit</button></form></div>{%endblockcontent%}Finally, the form would have to be added to the admin site (app.forms.admin):fromcontent_editor.adminimportContentEditorInlinefromdjango.contribimportadminfromfeincms3importpluginsfromfeincms3_forms.adminimportConfiguredFormAdmin,SimpleFieldInlinefromapp.formsimportmodels@admin.register(models.ConfiguredForm)classConfiguredFormAdmin(ConfiguredFormAdmin):inlines=[plugins.richtext.RichTextInline.create(model=models.RichText),SimpleFieldInline.create(model=models.Text,button='<i class="material-icons">short_text</i>',),SimpleFieldInline.create(model=models.Email,button='<i class="material-icons">alternate_email</i>',),SimpleFieldInline.create(model=models.URL,button='<i class="material-icons">link</i>',),SimpleFieldInline.create(model=models.Date,button='<i class="material-icons">event</i>',),SimpleFieldInline.create(model=models.Integer,button='<i class="material-icons">looks_one</i>',),SimpleFieldInline.create(model=models.Textarea,button='<i class="material-icons">notes</i>',),SimpleFieldInline.create(model=models.Checkbox,button='<i class="material-icons">check_box</i>',),SimpleFieldInline.create(model=models.Select,button='<i class="material-icons">arrow_drop_down_circle</i>',),SimpleFieldInline.create(model=models.Radio,button='<i class="material-icons">radio_button_checked</i>',),]And last but not least, create and apply migrations. That should be basically
it. We haven’t touched validating the configured form, reporting utilities or
creating your own (compound) field types yet, for now you have to check the
testsuite.