django.contrib.formtools: High-level abstractions of common form tasks

38 views
Skip to first unread message

Adrian Holovaty

unread,
Dec 5, 2006, 3:59:59 PM12/5/06
to django-d...@googlegroups.com
I've just checked in a new bit of Django functionality I've been
developing for the past couple of days (extracted from some production
code).

It's an abstraction of the workflow "Display an HTML form, force a
preview, then do something with the submission." If you're familiar
with the forced-preview workflow of the django.contrib.comments app,
you'll see it's a generic version of that. You pass it a Form object
(using the django.newforms API), define a short subclass to tell the
framework what to do if the data is valid, and point your URLconf at
it. You can define some optional templates, too.

There's no official documentation yet, but check out the docstrings in
django/contrib/formtools/preview.py for a start. Comments welcome!

Also, this is only the beginning of django.contrib.formtools, which I
intend to be a collection of common high-level form operations such as
this one. What other sorts of things can we make abstractions for,
given a Form?

Adrian

--
Adrian Holovaty
holovaty.com | djangoproject.com

Ivan Sagalaev

unread,
Dec 5, 2006, 4:30:46 PM12/5/06
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> What other sorts of things can we make abstractions for,
> given a Form?

First thing that comes to mind is the same thing you describe but
without preview. In other words it's what update_object generic view
does now but it works only on models and often it is desired to work
this way with arbitrary form. But I'm sure you've already thought about
it...

Adrian Holovaty

unread,
Dec 5, 2006, 5:10:20 PM12/5/06
to django-d...@googlegroups.com

Yes, *definitely*...That one is next on the list.

Ivan Sagalaev

unread,
Dec 5, 2006, 5:13:09 PM12/5/06
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> Yes, *definitely*...That one is next on the list.

I just wanted to make sure :-). I'm about to create a forum for my site
and am waiting for this bit to use it and test it :-)

Rob Hudson

unread,
Dec 6, 2006, 2:04:23 PM12/6/06
to Django developers
This looks pretty cool. I'm excited by the new forms development and
hope to play with some of it soon.

It seems a little out of place to me to instantiate an object in
urls.py:

(r'^post/$', MyFormPreview(MyForm)),

(There was some thread talking about urls.py becoming more of a
"controller", but I never followed up on understanding that idea
fully.)

-Rob

Brantley Harris

unread,
Dec 6, 2006, 9:03:03 PM12/6/06
to django-d...@googlegroups.com
Next on the list should be a Login form.

Should be very simple.

Adrian Holovaty

unread,
Dec 6, 2006, 9:22:16 PM12/6/06
to django-d...@googlegroups.com
On 12/6/06, Brantley Harris <deadw...@gmail.com> wrote:
> Next on the list should be a Login form.

What would you envision for that?

Lachlan Cannon

unread,
Dec 7, 2006, 4:10:29 AM12/7/06
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> What other sorts of things can we make abstractions for,
> given a Form?

Wizards, maybe? Given a series of forms, run through them all appending data
from previous forms to next ones, or processing each in turn. Maybe an option to
have it do either, so you can process the data as you prefer?
--
Lach
Personal: http://illuminosity.net/

chad....@gmail.com

unread,
Dec 7, 2006, 9:47:08 AM12/7/06
to Django developers
Yes, something along the lines of the wizard control in ASP.NET.
Ideally, nothing is committed to the db unless all the pages in the
wizard are validated. Whether this is done by capturing the
intermediate form data in the session or by rolling back a db
transaction, I'm not sure. DB transactions seem the natural way to do
it.

Adrian Holovaty

unread,
Dec 7, 2006, 9:51:35 AM12/7/06
to django-d...@googlegroups.com

This would be a great addition. Rather than requiring sessions, what
do you think of passing intermediate form data in hidden fields? It's
simpler, it wouldn't require cookies and I can't immediately think of
any downsides to the approach.

Graham King

unread,
Dec 7, 2006, 10:43:29 AM12/7/06
to django-d...@googlegroups.com

+1 on avoiding sessions. Stateless makes clustering servers so much easier.

Ivan Sagalaev

unread,
Dec 7, 2006, 10:31:16 AM12/7/06
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> This would be a great addition. Rather than requiring sessions, what
> do you think of passing intermediate form data in hidden fields?

+1

It's cleaner anyway since all data accumulated in one place.

Honza Král

unread,
Dec 7, 2006, 10:49:47 AM12/7/06
to django-d...@googlegroups.com

+1 as well, some DB engines does not fully support transactions and
many users may wish to process the data as a whole once the wizard is
finished...

> >
>


--
Honza Král
E-Mail: Honza...@gmail.com
ICQ#: 107471613
Phone: +420 606 678585

Antonio Cavedoni

unread,
Dec 7, 2006, 11:01:36 AM12/7/06
to django-d...@googlegroups.com
On 12/7/06, Adrian Holovaty <holo...@gmail.com> wrote:
> This would be a great addition. Rather than requiring sessions, what
> do you think of passing intermediate form data in hidden fields?

Isn't that the way the dreaded ASP.NET "view state" works? Saving
marshaled temporary data in a hidden field?

Cheers.
--
Antonio

Adrian Holovaty

unread,
Dec 7, 2006, 11:13:16 AM12/7/06
to django-d...@googlegroups.com
On 12/7/06, Antonio Cavedoni <ant...@cavedoni.org> wrote:
> > This would be a great addition. Rather than requiring sessions, what
> > do you think of passing intermediate form data in hidden fields?
>
> Isn't that the way the dreaded ASP.NET "view state" works? Saving
> marshaled temporary data in a hidden field?

It won't even *approach* the level of evil of that .NET viewstate
thing -- no marshalling or JavaScript would be necessary.

Rob Hudson

unread,
Dec 7, 2006, 11:27:58 AM12/7/06
to django-d...@googlegroups.com
On 20061207.0851, Adrian Holovaty said ...

> This would be a great addition. Rather than requiring sessions, what
> do you think of passing intermediate form data in hidden fields? It's
> simpler, it wouldn't require cookies and I can't immediately think of
> any downsides to the approach.

One downside:

If the user closes their browser (or the OS crashes, or any number of
other things), if the state so far is in sessions tied to a cookie, they
can jump to where they left off. If all state is in the forms and not
stored anywhere, they cannot.


Depending on the situation, that could be a downside or that could be
intentional and wanted.

-Rob

Waylan Limberg

unread,
Dec 7, 2006, 11:24:22 AM12/7/06
to django-d...@googlegroups.com
On 12/7/06, Adrian Holovaty <holo...@gmail.com> wrote:
> This would be a great addition. Rather than requiring sessions, what
> do you think of passing intermediate form data in hidden fields? It's
> simpler, it wouldn't require cookies and I can't immediately think of
> any downsides to the approach.
>

Presumably each page would do validation on submit (we don't want to
send the user back to page one after completing 10 pages). If the
validated data is now in hidden fields, couldn't someone alter that
data (with evil intent) requiring re-validation? Wouldn't storing the
data in sessions avoid that?

That said, hidden fields certainly have their place and are probably
"good enough" for generic usage. Just a thought.


--
----
Waylan Limberg
way...@gmail.com

Antonio Cavedoni

unread,
Dec 7, 2006, 11:28:09 AM12/7/06
to django-d...@googlegroups.com
On 12/7/06, Waylan Limberg <way...@gmail.com> wrote:
> Presumably each page would do validation on submit (we don't want to
> send the user back to page one after completing 10 pages). If the
> validated data is now in hidden fields, couldn't someone alter that
> data (with evil intent) requiring re-validation? Wouldn't storing the
> data in sessions avoid that?

You could probably have a partial validation, per-page, and a complete
one on the final page, essentially re-validating all the fields.
HTML-escaping of these hidden fields values would be mandatory in all
cases anyway.

Cheers.
--
Antonio

Adrian Holovaty

unread,
Dec 7, 2006, 12:18:14 PM12/7/06
to django-d...@googlegroups.com
On 12/7/06, Antonio Cavedoni <ant...@cavedoni.org> wrote:

Yes, my thoughts exactly. Per-page validation, plus a final validation
after the last step of the wizard.

Is anybody interested in implementing this? If so, I could share some
more ideas on how it might work, so as to integrate nicely with
newforms.

sandro.dentella

unread,
Dec 7, 2006, 12:44:14 PM12/7/06
to Django developers
Adrian Holovaty wrote:
> Also, this is only the beginning of django.contrib.formtools, which I
> intend to be a collection of common high-level form operations such as
> this one. What other sorts of things can we make abstractions for,
> given a Form?

I do have an idea, almost an obsession: I implemented it in tcl/tk,
pyGtk, php and I'm willing to contribute it for django.

When you are in a template and you need to design a not trivial form,
you normally don't get the idea of the layout looking at you code. You
must be really carefull and probably you need to render it in the
browser.

But most of the time you could just use a compact syntax and expand
that syntax to whatever you want, so that you could use a variable
to pass to a template tag to expand into correct html code. Right
now I don't know how to set a variable with django template system
so I'm using cheetah. A simple example of such a description
language is:


#def layout

#= general data
user_name
first_name last_name
birth_date

#- address
address -
city zip_code
state h=my_hidden_field=3

s=submit

#end def

later called as:

$create_form($layout, $form)

here the syntax:
'#=' would be translated into a <legend>,
'#-' into a <h2> title
each string is looked for in the form to get the label part,
the possible errors and the input part.

s=text is rendered as submit input, h= is obviously a hydden field.
'-' is jus a way to span the previous field one more column.
Curly braces is a way to group more fields into one cell.

It is also pretty easy to make it produce internationalized versions
using gettext for labels (but that's more for newform, I'd say).

If you think that this is an interesting tool I'd be pleased to discuss
with you some details of the description language. Beside that I don't
really know how to pass a variable from the template back to a
templatetag.

sandro
*:-)

PS: You can have a look at tcl/tk and php implementations of such a
description language at http://www.tksql.org/layout-php.php, of
course we can do much better in django since we can use newform and
templates!

chad....@gmail.com

unread,
Dec 7, 2006, 1:29:53 PM12/7/06
to Django developers
I'm interested.

Ivan Sagalaev

unread,
Dec 7, 2006, 1:08:27 PM12/7/06
to django-d...@googlegroups.com
Waylan Limberg wrote:
> Presumably each page would do validation on submit (we don't want to
> send the user back to page one after completing 10 pages). If the
> validated data is now in hidden fields, couldn't someone alter that
> data (with evil intent) requiring re-validation?

Why would it be bad? If all data (including hidden fields) on the page
is validated then by altering some hidden fields a user will just get
validation errors for himself. I mean it's no different than altering
non-hidden fields.

chad....@gmail.com

unread,
Dec 7, 2006, 1:50:29 PM12/7/06
to Django developers

Handling of file uploads might be tricky. For most cases, hidden
fields should suffice.

Chad

koenb

unread,
Dec 7, 2006, 2:07:41 PM12/7/06
to Django developers
Isn't it useful to allow two-step validation in general: one by the
form and one by the model when saving (both optional of course). This
would allow to add constraints to a form that are not needed by the
model in general (interesting when using different forms on the same
model eg for different levels of authenticated users).
It would be easy to expand this for the wizard-style forms: each page
has its form validation (only the 'new' items) AND a validation by the
model when saving to the database (on everything).

Koen

Ian Maurer

unread,
Dec 7, 2006, 3:14:42 PM12/7/06
to django-d...@googlegroups.com
On 12/5/06, Adrian Holovaty <holo...@gmail.com> wrote:
> It's an abstraction of the workflow "Display an HTML form, force a
> preview, then do something with the submission."

Very slick.

> What other sorts of things can we make abstractions for,
> given a Form?

I wanted to come up with a standard way of preventing multiple
submissions without pre-creating ids or relying on JavaScript pattern
of "if (!busy) form.submit();"

http://groups.google.com/group/django-users/browse_thread/thread/65b167f2623b8e64/4cb70d65ae77b370?lnk=gst&q=maurer&rnum=1#4cb70d65ae77b370

Just wondering if there was an obvious way to do this? And should it
be standardized.

-ian

JP

unread,
Dec 8, 2006, 1:10:51 PM12/8/06
to Django developers
> > You could probably have a partial validation, per-page, and a complete
> > one on the final page, essentially re-validating all the fields.
> > HTML-escaping of these hidden fields values would be mandatory in all
> > cases anyway.
>
> Yes, my thoughts exactly. Per-page validation, plus a final validation
> after the last step of the wizard.

What I've always done in these cases is carry a MAC along with the
hidden data and just validate that the hidden data hasn't changed by
re-hashing it after each form submit. You don't really need to
re-validate the already-validated data, you just need to ensure that it
hasn't changed since you validated it.

JP

Antonio Cavedoni

unread,
Dec 8, 2006, 4:20:28 PM12/8/06
to django-d...@googlegroups.com
On 8 Dec 2006, at 19:10, JP wrote:
> What I've always done in these cases is carry a MAC along with the
> hidden data and just validate that the hidden data hasn't changed by
> re-hashing it after each form submit. You don't really need to
> re-validate the already-validated data, you just need to ensure
> that it
> hasn't changed since you validated it.

MAC? An MD5/SHA1 hash, probably?
--
Antonio


chad....@gmail.com

unread,
Dec 8, 2006, 4:20:30 PM12/8/06
to Django developers
Here's a first attempt. As such, this code does per-page validation
only.

import cPickle as pickle
import base64

from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import render_to_response
from django.newforms.forms import SortedDictFromList
from django.newforms import *

class FormWizard(object):
'''
Formtools Wizard application

Given a list of django.newforms.Form objects, and a corresponding
list of step names,
it provides the following:

* Displays each form on a separate page in turn
* Marshals data between between pages in a hidden field
* Validates each form as it is submitted
* After the final form is validated, calls the done() hook that you
define

ToDo:
* Checksum marshalled data that's already been validated?
* Look at other ways to save state
* What's the most useful format for final data?
* Get feedback from people smarter than me
* Make this simpler

Usage:

Define your forms somwhere:

class PageOne(Form):
first_name = CharField()

class PageTwo(Form):
middle_name = CharField()

class PageThree(Form):
last_name = CharField()

Subclass FormWizard and define a done() method, overriding
form_template if you like:

class MyFormWizard(FormWizard):
form_template = MyApp/formwizard.html"

def done(self, request):
for clean_form_data in self.data:
...

In your urls.py:

(r"^mywizard/$",
MyFormWizard([PageOne,PageTwo,PageThree],["First","Middle","Last"])),

'''

form_template = "formtools/formwizard.html"

def __init__(self, form_steps, form_step_names):
'''
Store the wizard steps in a SortedDict
'''
form_steps = [(form_step_names[x], form_steps[x]) for x in
range(len(form_steps))]
self.form_steps = SortedDictFromList(form_steps)
self.data = [{} for x in range(len(form_steps))]

def serialize_data(self):
'''
Serialize our previously captured form data in a base64 encoded
pickle
'''
serialized_data =
base64.encodestring(pickle.dumps(self.data)).strip()
return serialized_data

def deserialize_data(self, serialized_data):
'''
Restore our previously captured data
'''
deserialized_data =
pickle.loads(base64.decodestring(serialized_data))
self.data = deserialized_data

def render_step(self, form, form_step_name, first=False,
last=False):
return render_to_response(self.form_template, {
"form": form,
"form_step_name": form_step_name,
"form_prev_state": self.serialize_data(),
"first": first,
"last": last,
})

def __call__(self, request, *args, **kwargs):
'''
Handle a request
'''
if request.method == "POST":
step_name = request.POST.get("form_step_name", "")
if step_name in self.form_steps.keys():
self.current_step_name = step_name
if request.POST.get("form_prev_state", None):

self.deserialize_data(request.POST.get("form_prev_state"))
submit_type = request.POST.get("submit_type", None)
if submit_type == "Prev":
return self.handle_prev(request)
elif submit_type == "Next":
return self.handle_next(request)
elif submit_type == "Finish":
return self.handle_last(request)

## initial request, display first step with no data ##
name,form = self.form_steps.items()[0]
return self.render_step(form(), name, first=True)

def get_prev_step_offset(self):
'''
Determine the previous step
'''
prev_offset = 0
try:
prev_offset =
self.form_steps.keys().index(self.current_step_name)-1
except:
pass
first = False
if prev_offset < 1:
prev_offset = 0
first = True
return first,prev_offset

def handle_prev(self, request):
'''
Go back to the previous step
'''
## add the current step's data, but we don't yet care if its
valid ##
form = self.form_steps[self.current_step_name](request.POST)
form.is_valid()
self.data[self.form_steps.keys().index(self.current_step_name)]
= form.clean()

first,offset = self.get_prev_step_offset()
data = self.data[offset]
name,form = self.form_steps.items()[offset]
return self.render_step(form(data), name, first=first)

def get_next_step_offset(self):
'''
Determine the next step
'''
offset = 0
try:
offset =
self.form_steps.keys().index(self.current_step_name)+1
except:
pass
last = False
if offset >= len(self.form_steps)-1:
offset = len(self.form_steps)-1
last = True
return last,offset

def handle_next(self, request):
'''
Process this step and if valid, go to the next
'''
form = self.form_steps[self.current_step_name](request.POST)
if form.is_valid():
## add this step's data ##

self.data[self.form_steps.keys().index(self.current_step_name)] =
form.clean()

last,next_offset = self.get_next_step_offset()
name,form = self.form_steps.items()[next_offset]
if self.data[next_offset]:
form_instance = form(self.data[next_offset])
else:
form_instance = form()
return self.render_step(form_instance, name, last=last)
else:
return self.render_step(form_instance,
self.current_step_name, first=(self.current_step_name ==
self.form_steps.keys()[0]))

def handle_last(self, request):
'''
Handle the final POST
'''
form = self.form_steps[self.current_step_name](request.POST)
if form.is_valid():
## add this step's data ##

self.data[self.form_steps.keys().index(self.current_step_name)] =
form.clean()

## finished ##
self.done(request)
else:
return render_step(form, self.current_step_name,
first=(self.current_step_name == self.form_steps.keys()[0]))

def done(self, request):
#for form_data in self.data:
# print form_data
"Does something with the clean_data and returns an
HttpResponseRedirect."
raise NotImplementedError('You must define a done() method on
your %s subclass.' % self.__class__.__name__)

Chad Maine

unread,
Dec 8, 2006, 4:32:13 PM12/8/06
to Django developers
Here's an example template:

{% extends "base.html" %}

{% block content %}

{% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>{{ form_step_name }}</h1>{% endif %}

<form action="" method="post">
<table>
{{ form }}
</table>
<input type="hidden" name="form_prev_state" value="{{ form_prev_state }}" />
<input type="hidden" name="form_step_name" value="{{ form_step_name }}" />

{% if not first %}
  <p><input type="submit" name="submit_type" value="Prev" /></p>
{% endif %}

{% if not last %}
  <p><input type="submit" name="submit_type" value="Next" /></p>
{% endif %}

{% if last %}
  <p><input type="submit" name="submit_type" value="Finish" /></p>
{% endif %}
</form>

{% endblock %}

Honza Král

unread,
Dec 8, 2006, 5:39:45 PM12/8/06
to django-d...@googlegroups.com
Hi,
nice work. I have been trying to come up with a solution of my own,
but I ran into some problems/questions:

1) I think it would be better to allow (but not force) users to add
actions after every step and a final done() - I have NO idea how to do
this, at least no working idea...

2) I don't know what would be the best way to store data from previous steps
individual fields sound the best, but for that they would need to
have some prefix to make them unique accross forms (perhaps modify the
form.fields names in __init__() ?? )
or whole pickled steps

with security hash (as in preview.py) for every step. failing the
hash check would revert the wizard to the step that failed to validate

3) templates - I think some users will want to specify different
templates for each step (if you supply a template_name as a string -
use it throughout the form, if list - use templates[step] for each
step --- but this seems too magical to my liking.. :-/

if all this is done (and I still hope I will find the way to do it, if
only as an excersize), the Preview could be rewritten as a fairly
simple (just override the method responsible for selecting a form to
always return the supplied form, and some other minor tweaks) wrapper
around the Wizard (DRY)

What do you think?

Afternoon

unread,
Dec 10, 2006, 5:41:02 AM12/10/06
to Django developers
One downside to storing accumulated data in hidden fields is when file
uploads are allowed as part of the wizard. Re-uploading each time would
be far less than ideal.

We've built a wizard abstraction that stores data in a bin in the
session, keyed by an ID which is passed through in the request, either
in the query string or in a hidden field. This avoids the file upload
and re-validation problems and also allows multiple submissions to be
active in one browser. The data store is available to each step along
the way.

Our abstraction is based on manipulators currently, a list of
manipulators describes the entire wizard. A default view is provided
for displaying a step. These can be overridden with custom templates,
custom form views or custom submit views. That is, as each step is
submitted we can specify that something different is done, e.g.
displaying a preview page, or computing some result based on data
collected so far. No data is inserted into the DB by the abstraction,
that is left entirely the programmer to do, in a custom submit view for
the last step for example.

Adrian Holovaty

unread,
Dec 10, 2006, 12:35:29 PM12/10/06
to django-d...@googlegroups.com
On 12/10/06, Afternoon <afte...@uk2.net> wrote:
> We've built a wizard abstraction that stores data in a bin in the
> session, keyed by an ID which is passed through in the request, either
> in the query string or in a hidden field. This avoids the file upload
> and re-validation problems and also allows multiple submissions to be
> active in one browser. The data store is available to each step along
> the way.

That sounds like useful code. Is this proprietary, or would you be
willing to contribute it back to the project? No pressure either way
-- I just figured it's worth asking about.

Afternoon

unread,
Dec 11, 2006, 11:08:24 AM12/11/06
to Django developers
We are definitely interested in sharing, that was our initial
intention.

The code is by no means finished, but my colleague Tom will post an
interim version and some notes on our design soon. Hopefully it will be
interesting.

Kevin

unread,
Dec 11, 2006, 2:52:19 PM12/11/06
to Django developers
I like the idea of storing an encoded-pickled version of the form data
in a hidden field. I'm concerned about privacy implications with
sharing that data with the client. What about encrypting the contents
too? The server could have a private key that it encrypts the
serialized form data and decrypts on submission.

I'm mainly concerned with the scenario where credit cards are used as
part of the form. I haven't found too many supported cryptography
libraries for python though.

I'd envision:
base64.encodestring( crypto.encrypt(key, pickle.dumps(self.data)))

and
base64.loads( crypto.decrypt(key, base64.decodestring( form_data )))

*(I made up the crypto library for demonstration)

Rob Hudson

unread,
Dec 11, 2006, 3:01:14 PM12/11/06
to django-d...@googlegroups.com
Isn't the session a natural place to store these kinds of things? Is
there a reason for the avoidance of sessions? Are they buggy? Do they
require some sort of over-head people are trying to avoid?

Just curious,
Rob

On 20061211.1152, Kevin said ...

Chad Maine

unread,
Dec 11, 2006, 3:15:50 PM12/11/06
to django-d...@googlegroups.com
On 12/11/06, Kevin <kevin...@gmail.com> wrote:

I like the idea of storing an encoded-pickled version of the form data
in a hidden field.  I'm concerned about privacy implications with
sharing that data with the client.  What about encrypting the contents
too?  The server could have a private key that it encrypts the
serialized form data and decrypts on submission.
I'm mainly concerned with the scenario where credit cards are used as
part of the form.  I haven't found too many supported cryptography
libraries for python though.

I can't see a good reason for passing CC info in its entirety back and forth at all, no matter how encrypted.  That said, there might be good reasons why you may want to encrypt form data, but I would leave that up to the individual programmer.  Perhaps I could provide hooks into the serialization methods to allow for that.

Scott Paul Robertson

unread,
Dec 11, 2006, 7:03:36 PM12/11/06
to django-d...@googlegroups.com
On Mon, Dec 11, 2006 at 11:52:19AM -0800, Kevin wrote:
> I'm mainly concerned with the scenario where credit cards are used as
> part of the form. I haven't found too many supported cryptography
> libraries for python though.
>

As far as crypto libraries go, I've used the Python Cryptography Toolkit
(http://www.amk.ca/python/code/crypto) a number of times, and have been
pretty pleased with it.

Thought you might like to know.

--
Scott Paul Robertson
http://spr.mahonri5.net
GnuPG FingerPrint: 09ab 64b5 edc0 903e 93ce edb9 3bcc f8fb dc5d 7601

rassilon

unread,
Dec 15, 2006, 8:25:31 AM12/15/06
to Django developers
Hi,

I'm Ben's (Afternoon) collegue Tom,

As much as I was asked to be interesting, I don't do interesting, or at
least when I do, people fall asleep....

http://www.halfapenguin.com/djmultipartform.tar.gz

I've posted the code sample on my server, its not complete, but it is a
snapshot of where we are going.

Currently it stores both fields and files.

The form should be constructed using a query dict fed into the url in
as in the following snippet

from myforms import FormWithSections

urlpatterns+= patterns('',
(r'^testmultipartform/((?P<slug>\w*)/)?$',
'djmultipartform.views.index',{'manipulator_constructor':
FormWithSections}),
)

(with tabs in the right places of course).

I'll put up a further post with more details about what will be done in
the future to finish it.

Enjoy
From
Tom

rassilon

unread,
Dec 20, 2006, 10:54:41 AM12/20/06
to Django developers
One slight modification, there was debugging code left in, which I've
now fixed.

Honza Král

unread,
Dec 26, 2006, 1:05:56 PM12/26/06
to django-d...@googlegroups.com
Hello all,

I got bored during the holiday, so I put together a simple
implementation of django.contrib.formtools.wizard...

Features:
all data are kept in POST, nothing is stored on the server (this is
simply not very good for file uploads, but should be OK for the
majority)

security_hash from preview.py (by adrian) is used to ensure that
previously submitted data didn't change (if they have, returns to the
step in question), successful validation is only done once

old data are stored as individual fields not pickled by step

process_step() is called after successful validation of every step
or after verifying the step's hash.. it is not meant to change
something in the DB, it is meant as a hook to change the wizard's
state (for example after processing step 1, generate form for step 2).
That's also why it is called every time a form is submitted for all
submitted steps (except the current one if its invalid)

done() is THE method to override, it receives list of form instances
with valid data corresponding to the form_list, and the request
object, its output is returned directly...

Bugs:
there is no (or very little) way of introducing your own logic or
overriding some defaults (for example there is no way to supply the
step number vie URL), this will change (see TODO)

no documentation so far - I first want to make sure people are OK
with the state of things before I start doing some. However if someone
needs to know more than is written here to give it a try, let me
know...

Usage:
1) subclass it and supply a done() method, that will taje request
and a list of form instances with valid data
2) into urls enter:
( r'SOMETHING', MyWizard( [MyForm1, MyForm2, ...] ) ),
3) supply a template (default is test.html so far, or override it in
get_template() ) taht looks like this:
<form action="." method="POST">
FORM( {{ step }}): {{ form }}

previous_fields: {{ previous_fields }}

<input type="submit">
</form>

4) report bugs and enhancement requests here or to me personally

I am looking forward for any feedback
Thanks
Honza

On 12/20/06, rassilon <rassil...@gmail.com> wrote:
>
> One slight modification, there was debugging code left in, which I've
> now fixed.
>
>
> >
>

wizard.py

Honza Král

unread,
Dec 27, 2006, 2:00:10 PM12/27/06
to django-d...@googlegroups.com
So,
a little example with dynamic forms, I hope you don't mind
pseudo-code, I don't feel like writing a working standalone example
and I cannot publish my application (its in Czech anyway ;) )

urls.py:

urlpatterns = patterns('some.app.views',
(r'wizard/$', 'my_wizard' ),
)

some/app/views.py:
from django import newforms as forms
from django.contrib.formtools import wizard

# we always have to supply at least one form
class FirstForm( forms.Form ):
item_list = forms.MultipleChoiceField( choices=[ (1,'ONE'), ( 2, 'TWO) ] )

# function that will generate a second form for us dynamically:
def get_second_form( items ):
# second form contains only labels for items selected in FirstForm
class SecondForm( forms.Form ):
def __init__( self, **kwargs ):
super( SecondForm, self ).__init__( **kwargs )
for i in items:
self.fields['label_%s' % i ] = forms.CharField( max_length=100 )
return SecondForm

# our wizard
class LabelManyWizard( wizard.Wizard ):
# after submitting the first form, create the second and append it
to form_list
def process_step( self, request, form, step ):
if step == 0:
form.full_clean()
self.form_list.append( get_second_form( form.clean_data['item_list'] ) )
super(LabelManyWizard, self ).process_step( request, form, step )

# when both forms are validly filled, do what you always wanted to do
def done( self, request, form_list ):
first, second = form_list
first.full_clean()
second.full_clean()
for i in first.clean_data['item_list']:
print "Label for item %s is %s" % ( i, second.clean_data[
'label_%s' % i ])
return HttpresponseRedirect( '/' )


# a little wrapper function - we could put the LabelManyWizard(
[FirstForm] ) into urls as a callable,
# but since it modifies its form_list it wouldn't work, hence this
little work around
# (this is only neccessary for dynamic wizards)

def my_wizard( request ):
wiz = LabelManyWizard( [FirstForm] )
return wiz( request )

------------- DONE -------------------

in order for this to work, you will need patch from ticket #3193 -
that is needed to pass the values from FirstForm.item_list because
normal as_hidden() wouldn't work for MultipleChoiceField...

looking forward to any comments
Honza

Honza Král

unread,
Jan 2, 2007, 4:10:16 PM1/2/07
to django-d...@googlegroups.com
I created the ticket containing the wizard proposal, it includes new
cleaned up version that offers a lot more flexibility...

http://code.djangoproject.com/ticket/3218

Message has been deleted

rassilon

unread,
Jan 8, 2007, 8:32:25 AM1/8/07
to Django developers

Right, after a bit of black magic, a few false starts and a fair bit of

reworking, djmultipartform is actually working as its meant to more
than just an abstract concept which i posted last time.

* www.halfapenguin.com/djmultipartform-0.1.tar.gz

* www.halfapenguin.com/djmultipartform-0.1.zip
(in case someone doesn't have the tools to unzip tar.gz).


There have been a few changes in the way the system works.
1.there is now a complete method which is accessed through the
/complete (overidable when implementing a multipartform), this should
be implemented as complete_action.

2. Each page descriptor can have assigned a method to generate a view,
which has passed in the the slug, the_form request, and the filter
variable. This updates the info_dict passed to the template, by default

the method passes an empty dict

3. Stored files can be deleted by a post request to the page with a
fieldname and the post variable 'delete' (which should be the
fieldname).

4.When a file is uploaded into the store where there is a file
already for that field, the old file is deleted and the new file is
saved


5.Clear_store now exists and will clear out all the data in the
datastore, this should be run as part of the complete method.


6. It is now possible to go backwards without completing form data,
however should there be errors in validation of the data on the current

page, to avoid confusion the new data for the page will be junked --see

below

Future work
* adding in behaviour options for the multipartform with respect to
moving backwards and junking data

* Modularise the view code so that components of the view can be
overridden.

* updating the code to use newforms instead of manipulators

Reply all
Reply to author
Forward
0 new messages