Forms

The main purpose of this app is to handle Forms. Of course, the app provides an API to Create and Edit forms, but it’s not the only option: django-formidable also provides a full python builder in order to create forms. django-formidable also provides a method to retrieve a standard django form class which can then be used just like an ordinary django form.

Formidable object

The main class is formidable.models.Formidable. This class is a classic django model which defines a representation of a dynamic form.

class formidable.models.Formidable(id, label, description, conditions)
exception DoesNotExist
exception MultipleObjectsReturned
static from_json(definition_schema, **kwargs)

Proxy static method to create an instance of Formidable more easily with a given definition_schema.

Params definition_schema:
 Schema in JSON/dict
>>> Formidable.from_json(definition_schema)
<Formidable: Formidable object>
get_django_form_class(role=None, field_factory=None)

Return the django form class associated with the formidable definition. If no role_id is provided all the fields are fetched with an EDITABLE access-right. :params role: Fetch defined access for the specified role. :params field_factory: Instance of Custom field factory if needed. :params field_map: Custom Field Builder used by the field_factory.

get_next_field_order()

Get the next order to set on the field to arrive. Try to avoid using this method for performance reasons.

This is the main object which is used to create or edit dynamic forms through the RESTful API or directly in Python/Django.

Django form class

One of the main feature is to provide a standard django form class built from the definition stored as Formidable object. The django form class is accessible throught the formidable.models.Formidable.get_django_form_class().

>>> formidable = Formidable.objects.get(pk=42)
>>> form_class = formidable.get_django_form_class()

This form class can be manipulated as all django form class, you can build an instance to validate data:

>>> form = form_class(data={'first_name': 'Obiwan'})
>>> form.is_valid()
False
>>> form.errors
{'last_name': ['This field is required.']}
>>> form = form_class(data={'first_name': 'Obiwan', 'last_name': 'Kenobi'})
>>> form.is_valid()
True

Or to render it:

{{ form.as_p }}

When a standard mechanism is implemented, you have a method to custom the final objec we get. django-formidable provides a way in order to custom the form class you get.

Each kind of field is built with an associated FieldBuilder:

slug Field / Widgets FieldBuilder
text CharField / TextInput formidable.forms.field_builder.TextFieldBuilder
paragraph CharField / TextArea formidable.forms.field_builder.ParagraphFieldBuilder
dropdown ChoiceField / Select formidable.forms.field_builder.DropdownFieldBuilder
checkbox ChoiceField / CheckboxInput formidable.forms.field_builder.CheckboxFieldBuilder
radios ChoiceField / RadioSelect formidable.forms.field_builder.RadioFieldBuilder
checkboxes ChoiceField / CheckboxSelectMultiple formidable.forms.field_builder.CheckboxesFieldBuilder
email EmailField / TextInput formidable.forms.field_builder.EmailFieldBuilder
date DateField formidable.forms.field_builder.DateFieldBuilder
number IntegerField formidable.forms.field_builder.IntegerFieldBuilder

So, as describe in django document (https://docs.djangoproject.com/en/1.9/topics/forms/media/#assets-as-a-static-definition), if you want add a CalendarWidget on the date field on your form, you can write your own field builder.

from django import forms

from formidable.forms.field_builder import DateFieldBuilder, FormFieldFactory

class CalendarWidget(forms.TextInput):

    class Media:
        css = {
            'all': ('pretty.css',)
        }
        js = ('animations.js', 'actions.js')


class CalendarDateFieldBuilder(DateFieldBuilder):
    widget_class = CalendarWidget


class MyFormFieldFactory(FormFieldFactory):
    field_map = FormFieldFactory.field_map.copy()
    field_map['date'] = CalendarDateFieldBuilder

With this definition you can call:

>>> formidable.get_django_form_class(field_factory=MyFormFieldFactory)

Roles and access-rights

Roles

One of the main features of formidable is to set up different access-rights for the same form. This way, you can create a form with certain fields that are only accessible to a specific group of users, for example.

For the moment, formidable is not designed to work without roles, so even if you don’t need to handle multiple roles or access-rights inside your application, you will still have to define a default role for formidable to work properly.

All roles must be declared through a formidable.accesses.AccessObject instance. This class must be instantiated with an id and a label. The id has to be unique, it’s up to you to maintain this constraint. The label serves as a human readable value. You can set this to any string you like.

from formidable.accesses import AccessObject

padawan = AccessObject(id='padawan', label='Padawan')
jedi = AccessObject(id='jedi', label='Jedi')
sith = AccessObject(id='sith', label='Bad Guy')

django-formidable needs to know how to get all declared instances. To do so, you will need to create a function which returns the correct instances:

def get_accesses():
    return [padawan, jedi, sith]

Once this function is defined, you will need to fill the settings key FORMIDABLE_ACCESS_RIGHTS_LOADER.

FORMIDABLE_ACCESS_RIGHTS_LOADER = 'myapp.accesses.get_accesses'

Once this is done, django-formidable will know which roles have been defined, so it can create or check access-rights as necessary.

Fetch context

Occasionally, django-formidable will require access to the web request’s context, e.g. to find out which kind of user is accessing the current form.

For this reason, you must define a function to fetch the context of the current request. The function takes as parameters the request object of the view (self.request) and the view kwargs (self.kwargs).

The function must return an access id which is defined in one of the AccessObject instances returned by the method configured in FORMIDABLE_ACCESS_RIGHTS_LOADER.

If the user’s role is defined as an attribute, you can just return it directly:

def fetch_context(request, kwargs):
    return request.user.role

Then, set FORMIDABLE_CONTEXT_LOADER in your settings:

FORMIDABLE_CONTEXT_LOADER = myapp.accesses.fetch_context

Available access-rights

For each field of a form, and for each role you have defined, you can define a specific access-right. There are four different available access-rights:

  • EDITABLE, the user may fill-in the field but there is no obligation to do so.
  • REQUIRED, the user must fill-in the field in order to submit the form.
  • READONLY, this will render the field as disabled, allowing the user to view but not modify its contents.
  • HIDDEN, the field will not be available to the user, preventing the user from either viewing or modifying its contents.

All the value are defined in formidable.constants

Conditions

Important

As of 1.4.0, it is allowed to have several conditional display rules that target a common field. In case of “conflict” between these rules, priority goes to the display, rather than the hide action.

e.g.:

  • Rule 1 says: “if checkbox-1 is checked, then display field X”
  • Rule 2 says: “if checkbox-2 is checked, then display field X and Y”

if only checkbox-1 is checked, field X will be displayed, even if checkbox-2 is unchecked, and vice-versa. If both are checked, fields X and Y will be displayed. If none is checked, fields X and Y will be hidden.

Types for conditional rules

At this moment, we can guarantee only the support of the checkboxes and dropdown lists, but normally you could use it for any type you want.

Also, you could specify types allowed for the conditions using the settings variable FORMIDABLE_CONDITION_FIELDS_ALLOWED_TYPES By default formidable will accept any type.

FORMIDABLE_CONDITION_FIELDS_ALLOWED_TYPES = []  # formidable will allow any type for the conditional rules
FORMIDABLE_CONDITION_FIELDS_ALLOWED_TYPES = ['checkbox']  # formidable will allow checkboxes only

In case you try to configure a conditional display based on a field that has been excluded from the allowed types, you’ll receive a ValidationError when trying to save the form.

Here is a list of all the available types:

available_types = [
    'title', 'helpText', 'fieldset', 'fieldsetTable', 'separation',
    'checkbox', 'checkboxes', 'dropdown', 'radios', 'radiosButtons',
    'text', 'paragraph', 'file', 'date', 'email', 'number'
]

Python builder

In some cases, you may want to build a formidable object without using the RESTful API (in tests for example). django-formidable provides a Python API in order to that. Take a look at formidable.forms.fields to discover all the fields that are available through this API.

The main class to use is formidable.forms.FormidableForm. Feel free to subclass this form and define your own form(s), just like any other django form.

For example, let’s say we need to build a form with a first name, last name and a description. We can use formidable.fields to accomplish this. Lets consider using the different roles defined in the installation part, jedi and padawan.

from formidable.forms import FormidableForm
from formidable.forms import fields


class MySubscriptionForm(FormidableForm):

    first_name = fields.CharField(label='Your First Name')
    last_name = fields.CharField(label='Your Last Name')
    description = fields.TextField(
        label='Description',
        help_text='Tell us about yourself.'
    )

Attributes like required should not be used as these will depend on the context when the form is built. If you want to define a field as required, it will need to be required for a specific role through the accesses argument. This argument is a dictionary containing the various access-rights for each role. By default, if you don’t specify any access-rights for a previously defined role, the field will be created as EDITABLE:

class MySubscriptionForm(FormidableForm):

    first_name = fields.CharField(
        label='Your First Name',
        accesses={'padawan': constants.REQUIRED, 'jedi': constants.READONLY}
    )
    last_name = fields.CharField(label='Your Last Name')
    description = fields.TextField(
        label='Description',
        help_text='Tell us about yourself.'
    )

When the form definition is complete, you can create a new formidable.models.Formidable object:

formidable = MySubscriptionForm.to_formidable(
    label='My Subscription Form',
    description='This form is for subscribing to the jedi order.')

This method will create the object in the database and return the complete instance:

>>> formidable.pk
42

You can also get the django form class from the formidable object:

>>> form_class = formidable.get_django_form_class(role='padawan')

For our ‘padawan’ role, the first_name is required:

>>> form = form_class(data={'last_name': 'Kenobi'})
>>> form.is_valid()
False
>>> form.errors
{'first_name': ['This field is required.']}

Available fields

Available Widgets