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 givendefinition_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.
-
exception
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 |
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.']}