Problem
=======
The FormPreview class provided by contrib.formtools helps automate a common workflow. You display a form, then force a preview, then finally allow a submit. If the form gets tampered with, the original form gets redisplayed.
Unfortunately, this class can only be used when you have an html form that is backed by exactly one Django form. No formsets, no html forms backed by more than one Django form.
Solution
========
I was asked to create exactly this sort of workflow for a highly complex form + formset. The ComplexFormPreview class provides a base class to help with this problem. As with FormPreview, you must override a few functions.
Code
====
The abstract ComplexFormPreview class can live anywhere on your python path. Import it and subclass is exactly like you would contrib.formtools FormPreview.
The self.state dictionary is passed to all response calls as the context for your templates. Add any objects you need in your template to this dictionary. This includes all forms, formsets, and any additional variables you want in your template context.
Override the parse_params if you need to get any args/kwargs from your url. Save these values in self.state if you want them in your template context.
Override the init_forms method to do setup for all of your forms and formsets. Save all your forms in self.state. You should provide a unique prefix for all forms and formsets on the page to avoid id collisions in html.
*VERY IMPORTANT NOTE*: init_forms is called with a kwargs dictionary. You need to pass **kwargs to all of your form definations in init_forms. This is how the POST data is going to be passed to your forms and formsets.
*VERY IMPORTANT NOTE No. 2*: all of the validation is handled inside the class - all forms will be found and validated, and we will only proceed when everything is found to be valid. This means that you can use the class as a view directly, or provide a thin wrapper function around it if you want.
Override the done method to handle what should be done once your user has successfully previewed and submitted the form. Usually, this will involve calling one or more save() calls to your various forms and formsets.
Because you now have multiple forms, the default contrib.formtools templates don't work. You must make custom templates that reference all of your various forms. The stage_field, hash_field, and hash_value fields are used exactly like the formtools examples. Follow the basic layout demonstrated in the example templates, and substitute your custom forms for the default form.
Example views.py
================
The views.py demonstrated here has many hooks into my project, including using some [complex formset classes](http://www.djangosnippets.org/snippets/1290/). It won't work for you without being customized, but it will demonstrate how to override the default ComplexFormPreview.
Background
==========
Edit: This snippet doesn't make a lot of sense when Malcolm's blog is down. Read on for some history, or go [here](http://www.djangosnippets.org/snippets/1955/) to see the new trick that Malcolm taught me.
A year ago, Malcolm Tredinnick put up an excellent post about doing complex Django forms [here](http://www.pointy-stick.com/blog/2008/01/06/django-tip-complex-forms/).
I ended up reinventing that wheel, and then some, in an attempt to create a complex formset. I'm posting my (partial) solution here, in hopes that it will be useful to others.
Edit: I should have known - just as soon as I post this, Malcolm comes back with a solution to the same problem, and with slightly cleaner code. Check out his complex formset post [here](http://www.pointy-stick.com/blog/2009/01/23/advanced-formset-usage-django/).
I'll use Malcolm's example code, with as few changes as possible to use a formset. The models and form don't change, and the template is almost identical.
Problem
=======
In order to build a formset comprised of dynamic forms, you must build the forms outside the formset, add them, and then update the management form. If any data (say from request.POST) is then passed to the form, it will try to create forms inside the formset, breaking the dynamically created form.
Code
====
To use this code:
* Copy `BaseDynamicFormSet` into your forms.py
* Create a derived class specific to your needs (`BaseQuizDynamicFormSet` in this example).
* Override `__init__`, and keep a reference to your object that you need to build your custom formset (`quiz`, in this case).
* Call the parent `__init__`
* Call your custom add forms logic
* Call the parent `_defered_init`
To write your custom add_forms logic, remember these things:
* You've got to pass any bound data to your forms, and you can find it in self.data.
* You've got to construct your own unique prefixes by doing an enumerate, as shown in the example above. This is the same way it is usually handled by the formset.
Add a `formset_factory` call, and specify your derived dynamic formset as the base formset - we now have a `QuizFormSet` class that can instantiated in our view.
The view and template code look identical to a typical formset, and all of the dynamic code is encapsulated in your custom class.
Warning
=======
This solution does not yet handle forms that work with files, use the ordering/delete features, or adding additional forms to the set via javascript. I don't think that any of these would be that hard, but don't assume that they'll just work out of the box.
Sometimes we need divide forms in fieldsets, but this make us declare all fields in HTML template manually.
This class is to help you to do this by a easy way.
**How to use**
First, download this file as name "sectioned_form.py"
Later, turn your form inherited from the class **SectionedForm**, override method "_html_output" and declare fieldsets and fieldset_template attribute, like below:
from sectioned_form import SectionedForm
class MyForm(forms.ModelForm, SectionedForm):
fieldsets = (
(None, ('name','age','date')),
(_('Documents'), ('number','doc_id')),
)
fieldset_template = "<h2>%s</h2>"
def _html_output(self, *args, **kwargs):
return SectionedForm._html_output(self, *args, **kwargs)