Getting Started

Introduction

django-dataforms is a Django application that that allows Django forms to be dynamically data-driven. Django form logic is abstracted to the database layer. This allows for quick updates to forms and gives the user access to modify forms through the Django admin interface.

Requirements

  1. Django 1.3

    django-dataforms has only been tested with 1.3. It might work with previous versions, but has not been tested as such.

  2. JQuery

    django-dataforms uses JQuery for parts of the admin interface. JQuery-UI is also used for date picker fields.

Installation

To get this application up and running, please follow the steps below:

  1. Create a Django production environment using the setup of your choice. Refer to: http://docs.djangoproject.com/en/dev/intro/install/

  2. Create a new Django Project:

    $ django-admin.py startproject <projectname>
    
  3. Install django-bft to either your PYTHON_PATH or in a folder inside your project:

    • Install from pip:

      $ pip install django-dataforms
      
    • Download and install from source:

      $ python setup.py install
      
    • Install source to local directory:

      $ python setup.py build
      $ cp build/lib/bft /<PROJECT_ROOT>/
      
  4. Add the following to your settings.py file:

    • Add ‘dataforms’, ‘staticfiles’ and ‘admin’ to INSTALLED_APPS:

      INSTALLED_APPS = (
              ...
              'django.contrib.admin',
              'django.contrib.staticfiles',
              'dataforms',
      )
      
    • Make sure your static file finders are configured:

      STATICFILES_FINDERS = (
          'django.contrib.staticfiles.finders.AppDirectoriesFinder',
      )
      
    • To maintain data integrity, you’ll need to add the transaction middleware to your settings.py. This will put all queries from each request into a single transaction—so if something goes wrong, all DB changes from the entire request will not be committed.:

      MIDDLEWARE_CLASSES = (
              # ...
              'django.middleware.transaction.TransactionMiddleware',
      )
      
  5. Modify app_settings.py as needed. See Dataforms Settings for specifics.

  6. Don’t forget to collect your static files and sync your database:

    $ python manage.py syncdb
    $ python manage.py collectstatic
    

Usage

Use the following examples below as a base for adding Dataforms to your Django views.

Remember: For most arguments on create_form and create_collection, you can pass in the actual object instead of the slug if that object already exists in your code. This way it will save extra database calls.

Basic Form

from dataforms.forms import create_form

# ...

form = create_form(request, form="form-slug", submission="submission-slug")

if request.method == "POST":
   if form.is_valid():
      form.save()
      return redirect(...)

return render(request, "index.html", { 'form' : form })

Basic Collection

collection = create_collection(request, collection="collection-slug", submission="submission-slug", section="section-slug")

if request.method == "POST":
   if collection.is_valid():
      collection.save()
      return redirect(...)

return render(request, 'collection.html', { 'forms' : collection })

Example with objects instead of slugs

from dataforms.forms import create_form
from dataforms.models import Submission

submission = Submission.objects.get(pk=1)

form = create_form(request, form="form-slug", submission=submission)

Returning a de-coupled Form class object

Sometimes it might be nice just to get back a form object that you can use to save to somewhere else:

from dataforms.forms import create_form

FormClass = create_form(request, form="form-slug", submission="submission-slug", return_class=True)
form = FormClass(request.POST or None)

if form.is_valid():
   # Process the data in form.cleaned_data
   # ...

Collection with Table of Contents

Assuming your view has something similiar to the Basic Collection example above, do something like this in your template:

{% for section in collection.sections %}
   <li class="{% if section == collection.current_section %}bold{% endif %}">
      <a href="...">{{ section.title }}</a>
   </li>
{% endfor %}

Remember: You also have collection.next_section and collection.previous_section available for you to use.

Concepts and Definitions

Below are some important concepts that are helpfull to understand when working with Dataforms.

Collection

A Dataform collection is a group of Dataform Forms. A collection can separate out forms into sections and by doing this, you can render a table of contents.

See the create_collection and BaseCollection objects in Dataforms Form API for more information.

Bindings

Bindings are a way of showing and/or hiding certain fields based on the actions of others fields. Say you hide certain fields in your form until a uses clicks on a specific checkbox and then show them.

Bindings can show or hide fields based on the follow conditions:

  • A Field that is checked or has a value
  • A Field value that is equal to a specific value
  • A Field value that is not equal to specific value
  • A Field value that contains a specific value
  • A Field value that does not contains a specific value

Bindings can be added through the Django admin.

Mappings

You will notice in the Django admin there are three mapping models. (Choice Mappings, Collection Mappings, and Field Mappings) These are the bridge table relations that connect Choices to Fields, Forms to Collections, and Fields to Forms.

In the future, a re-work of the admin interface is planned to be more intuitive but for now this admin areas serve as the way to connect the pieces of your forms togethor.

Dataforms Settings

Below are the available settings for Dataforms that can go in your settings.py file.

DATAFORMS_FIELD_MAPPINGS

A dictionary of form fields are available to be used. The dictionary cat contain the following keys:

class:required The full python path to the field class as a string.
widget:required The full python path to the widget as a string.
widget_kwargs:optional A dictionary on arguments to pass to the widget.
widget_attrs:optional A dictionary of widget attrs to pass to the widget.

Here is what is in FIELD_MAPPINGS by default:

{
        'TextInput' : { 'class': 'django.forms.CharField', 'widget': 'django.forms.TextInput' },
        'Textarea' : { 'class': 'django.forms.CharField', 'widget': 'django.forms.Textarea' },
        'Select' : { 'class': 'django.forms.ChoiceField', 'widget': 'django.forms.Select' },
        'SelectMultiple' : { 'class': 'django.forms.MultipleChoiceField', 'widget': 'django.forms.SelectMultiple' },
        'RadioSelect' : { 'class': 'django.forms.ChoiceField', 'widget': 'django.forms.RadioSelect' },
        'Password' : { 'class': 'django.forms.CharField', 'widget': 'django.forms.PasswordInput', 'widget_kwargs' : { 'render_value' : True } },
        'Email' : { 'class': 'django.forms.EmailField', 'widget': 'django.forms.TextInput' },
        'DateField' : { 'class': 'django.forms.DateField', 'widget': 'django.forms.DateTimeInput', 'widget_attrs' : { 'class' : 'datepicker' } },
        'CheckboxInput' : { 'class': 'django.forms.BooleanField', 'widget': 'django.forms.CheckboxInput' },
        'CheckboxSelectMultiple': { 'class': 'django.forms.MultipleChoiceField', 'widget': 'django.forms.CheckboxSelectMultiple' },
        'HiddenInput' : { 'class': 'django.forms.Field', 'widget': 'django.forms.HiddenInput' },
        'FileInput' : { 'class': 'django.forms.FileField', 'widget': 'django.forms.ClearableFileInput' },
        'ImageFileInput' : { 'class': 'django.forms.ImageField', 'widget': 'django.forms.ClearableFileInput' },
        'IntegerInput' : { 'class': 'django.forms.IntegerField', 'widget': 'django.forms.TextInput' },
        'DecimalInput' : { 'class': 'django.forms.DecimalField', 'widget': 'django.forms.TextInput' },
        # Note Widget:  This is a way you can add sub headings to your forms.  See - dataforms.widgets.NoteWidget
        'Note' : { 'class': 'django.forms.CharField', 'widget': 'dataforms.widgets.NoteWidget' },
}
DATAFORMS_FILE_UPLOAD_PATH
A path relative to the MEDIA_ROOT where to store file uploads for Dataforms
Be sure to add a trailing shash.
default = ‘uploads/’
DATAFORMS_MAX_UPLOAD_SIZE
The maximum size for an individual file upload in bytes. This should be a integer.
default = 10485760
DATAFORMS_UPLOAD_FIELDS
A tuple of field keys in DATAFORMS_FIELD_MAPPINGS that should be treated as upload fields.
default = (‘FileInput’, ‘ImageFileInput’)
DATAFORMS_BOOLEAN_FIELDS
A tuple of field keys in DATAFORMS_FIELD_MAPPINGS that should be treated as boolean fields.
default = (‘CheckboxInput’,)
DATAFORMS_SINGLE_CHOICE_FIELDS
A tuple of field keys in DATAFORMS_FIELD_MAPPINGS that should be treated as single choice fields.
default = (‘Select’, ‘RadioSelect’)
DATAFORMS_MULTI_CHOICE_FIELDS
A tuple of field keys in DATAFORMS_FIELD_MAPPINGS that should be treated as single choice fields.
default = (‘SelectMultiple’, ‘CheckboxSelectMultiple’)
DATAFORMS_STATIC_CHOICE_FIELDS
A tuple of field keys in DATAFORMS_FIELD_MAPPINGS that should be treated as a comma-delimited string
This can be usefull for choice fields that have numbers as their values.
default = ()
DATAFORMS_FIELD_DELIMITER
The delimiter to be used in the creation of a field name.
default = ‘__’
DATAFORMS_VALIDATION_MODULE
The module path that conains and django form validation that will be used with dataforms.
See Dataforms Form Validation for details.
default = ‘validation’
DATAFORMS_USE_REMOTE_JQUERY
Specify where or not to use remote JQuery libraries.
default = True

Dataforms Form Validation

Validation on Dataforms is very similiar to how you would do validation on a regular Django form. The are only 2 things that you needs to remember:

  • Make sure you declare a staticmethod decorator on all methods that are to be used for validation on Dataforms

  • The name of you validation class has to match the slug of the dataform
    for which you intend to validate.

    This is accomplished by create_form_class_title method in dataforms.forms.
    For example: a slug of ‘my-bio’ will translate to ‘MyBioForm’

Below is an example of how this could work:

from django import forms

class BaseValidationForm(object):
        @staticmethod
        def clean(self):
                raise forms.ValidationError('clean base error')

# The name of your form slug should be 'personal-information'
class PersonalInformationForm(BaseValidationForm):
        @staticmethod
        def clean(self):
                raise forms.ValidationError('clean error')

        @staticmethod
        def clean_textbox(self):
                raise forms.ValidationError('clean field error')

Dataforms Form API

See the GettingStarted guide at: http://readthedocs.org/docs/django-dataforms/en/latest/

class dataforms.forms.BaseCollection(collection, forms, sections, current_section)

You shouldn’t need to instantiate this object directly, use create_collection.

When you have a collection, here are some tips:

# You can see what's next and what came before
collection.current_section
collection.next_section
collection.prev_section
errors()

List of errors for all forms in the collection

is_valid(check_required=True, *args, **kwargs)

Validate all contained forms

media
save()

Save all contained forms

class dataforms.forms.BaseDataForm(readonly=False, *args, **kwargs)
is_valid(check_required=True, *args, **kwargs)
Parameters:check_required – Whether or not to validate required fields. Default True.
media
save(collection=None)

Saves the validated, cleaned form data. If a submission already exists, the new data will be merged over the old data.

exception dataforms.forms.RequiredArgument
exception dataforms.forms.SectionDoesNotExist
dataforms.forms.create_collection(request, collection, submission, readonly=False, section=None, force_bind=False)

Based on a form collection slug, create a list of form objects.

Parameters:
  • requestrequired (object); the current page request object, so we can pull POST and other vars.
  • collectionrequired, (string or object); a Dataform collection slug or object
  • submissionrequired, (string or object); a create-on-use submission slug or object; passed in to retrieve Answers from an existing Submission, or to be the slug for a new Submission.
  • readonlyoptional (boolean); converts form fields to be readonly. Usefull for display only logic.
  • sectionoptional (string or object); allows a return of only forms on that section.
Return type:

a BaseCollection object, populated with the correct Dataforms and data

dataforms.forms.create_form(request, form, submission=None, title=None, description=None, section=None, readonly=False, answers=None, return_class=False, force_bind=False)

Instantiate and return a dynamic form object, optionally already populated from an already submitted form.

Usage:

# Get a dynamic form. If a Submission with slug "myForm" exists,
# this will return a bound form. Otherwise, it will be unbound.
create_form(request, form="personal-info", submission="myForm")

# Create a bound form to a previous submission object
create_form(request, slug="personal-info", submission=Submission.objects.get(...))
Parameters:
  • requestrequired (object); the current page request object, so we can pull POST and other vars.
  • formrequired (string or object); a Dataform slug or object
  • submissionoptional (string or object); a create-on-use submission slug or object; passed in to retrieve Answers from an existing Submission, or to be the slug for a new Submission.
  • titleoptional (string); a title for the form, pulled from DB by default
  • descriptionoptional (string); a description pulled from DB by default
  • sectionoptional (string or object); a section that will be added as an attr to the form instance
  • readonlyoptional (boolean) readonly converts form fields to be readonly. Usefull for display only logic. Does not work when return_class is True.
  • answersoptional (dictionary); a answer dictionary for the submission. It should follow the same format os get_answers().
  • return_classoptional (boolean); returns only the form class and decouples database saves Usefull for when you want to save the form somewhere else.
dataforms.forms.create_form_class_title(slug)

Transform “my-form-name” into “MyFormName” This is important because we need each form class to have a unique name.

Parameters:slug – the form slug from the DB
dataforms.forms.create_sections(collection)

Create sections of a form collection

Parameters:collection – a data form collection object
dataforms.forms.filter_qs(qs, id)
dataforms.forms.get_answers(submission, for_form=False, form=None, field=None)

Get the answers for a submission.

This function intentionally does not return the answers in the same form as request.POST data will have submitted them (ie, every element wrapped as a list). This is because this function is meant to provide data that can be instantly consumed by some FormClass(data=data) instantiation, as done by create_form.

Parameters:
  • submission – A Submission object or slug
  • for_form – whether or not these answers should be made unique for use on a form, ie. if every field slug should be prepended with the form’s slug. This can be annoying when just wanting to inspect answers from a submission, so it is set to False by default, but needs to be True when used the keys will be used as form element names.
  • form – Only get the answer for a specific form. Also accepts a data_form slug.
  • field – Only get the answer for a specific field. Also accepts a list of field_slugs.
Return type:

a dictionary of answers.

dataforms.forms.get_bindings(form)

Get the bindings for specific form

Returns:list of dictionaries, where each dictionary is a single binding.
dataforms.forms.get_db_field_names(form)
dataforms.forms.get_field_objects(submission)

Get a list of field objects for a particular submission/collection

dataforms.forms.get_form_media()

Model Diagram

_images/dataforms_models.png

Indices and tables