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".