Wizard like forms

25 views
Skip to first unread message

voltron

unread,
Oct 12, 2007, 5:14:39 AM10/12/07
to pylons-discuss
I have to collect a lot od data from users, the forms used are
separate but interconnected. This has me coding for every form, a
processing function and a "display form" function in the controllers
and then both functions have to be defined in the routes
configuration, is there some cleaner and less strenous way of doing
this in pylons? How do others code wizards?

Thanks

jgar...@jonathangardner.net

unread,
Oct 12, 2007, 4:15:34 PM10/12/07
to pylons-discuss
I would actually do things a little differently. Rather than using a
separate controller for each step, I would combine all the steps into
one controller because a lot of the data is going to be shared.

Each page would be rendered simply by rendering a different template.

Your program state will have to be stored in hidden variables, of
course.

Here's some of my thoughts (incomplete and rough) to describe why I
think this:

http://dervish.jonathangardner.net/tech/w/Web_Application/Wizards

voltron

unread,
Oct 12, 2007, 4:55:47 PM10/12/07
to pylons-discuss
Actually thats what I meant, different functions in one controller,
its just that I have two functions for each step, one to render the
form and one to save and process the data. Hidden variables? I have
been trying my best to avoid those. You mention in your blog this:

"Each page in the wizard would get its own template. Each page would
submit to the same URL"

Posting to the same URL? how is this meant? That would be the answer
to my question in the first place as as I said I would have to create
different URLs or each step( form)

Thanks

On Oct 12, 10:15 pm, "jgard...@jonathangardner.net"

aaaron

unread,
Oct 13, 2007, 2:41:30 PM10/13/07
to pylons-discuss
We've created a shuttle helper object that shuttle's form results
between the different steps of the wizard (ie, flow). Each shuttle has
an unique identifier that is used to persist the shuttle to the beaker
session between actions. In the final action of the wizard, we take
all the data that has been collected in the shuttle, then generate and
persist the appropriate model objects, etc.

The user interactions looks like this:

action1 -> collectaction1 (redirect) -> action2 -> collectaction2
(redirect) -> generate/persist

code (put this in your lib.helpers module)
-----------------------------------------------------------

def fetch_shuttle(id=None, action=None):
"""
To create a new shuttle, don't pass in an id
"""
if not id:
id = utils.uuid()

if id in session:
shuttle = session.get(id)
else:
shuttle = Shuttle(id)
session[id] = shuttle
session.save()

if action:
return shuttle[action]
return shuttle

class Shuttle(dict):
"""
We refer to a multi-page form as a "flow"
This helper class "shuttles" form result data between
the different pages of the flow
This class keeps track of the state of the flow
which includes form data, steps completed, etc
"""

def __init__(self, id, *args, **kw):
super(Shuttle, self).__init__(self, *args, **kw)
self.id = id

self.batch_save = False

def __getitem__(self, key):
return dict.__getitem__(self, key)

def start_batch(self):
"""
instead of saving each time a key/val is set,
we can batch them together, followed by a save()
"""
self.batch_save = True

def set(self, val):
"""
sets the form_result for the current action
"""
key = self._get_current_action()
return self.__setitem__(key, val)

def __setitem__(self, key, val):
"""
sets the form_result and saves the session
"""
dict.__setitem__(self, key, val)
if not self.batch_save:
session.save()

def current(self):
"""
gets the form_result for the current action
"""
key = self._get_current_action()
if not key in self:
return {} # return empty form result
return dict.__getitem__(self, key)

def __delitem__(self, key):
if key in self:
dict.__delitem__(self, key)

def _get_current_action(self):
return request.environ['pylons.routes_dict']['action']

def save(self):
self.batch_save = False
session.save()

usage: (in your controller)
-------------------------------------

def __before__(self):

id = request.environ['pylons.routes_dict'].get('id', None)
c.shuttle = h.fetch_shuttle(id)

def action1(self, id):

c.action = h.url_for(action='storeaction1', id=c.shuttle.id)

return render_response('/action1_form.html')

@validate(schema=action1schema, form='action1')
def storeaction1(self, id):

c.shuttle['action1'] = self.form_result

h.redirect_to(action='action2')

def action2(self, id):
... etc...

# eventually
def saveall(self, id):
# gen db objects from all the shuttle data
model_obj = action1schema.to_model(c.shuttle['action1'])
... etc...

If people are interested, I can articulate this more thoughtfully on
the hq wiki. I feel the "Form Handling" page (http://wiki.pylonshq.com/
display/pylonsdocs/Form+Handling) in the official pylons docs could
use an update. It doesn't give much insight into setting default
values on forms (htmlfill) or how <form:errors> works... still alludes
to the rise of a "Turbogears widget system".

Reply all
Reply to author
Forward
0 new messages