Showing form and handling it on the same URL

3 views
Skip to first unread message

thesamet

unread,
Dec 5, 2006, 2:26:35 PM12/5/06
to TurboGears
Hi,

I'd like to show a report in the root of my web site, and above it a
form to customize the report (i.e. restrict dates and so on).
Initially, the report is generated using default values.

It works well, until I start using validators. Then, initially, the
default values of the form are overridden by the variables of the
request, and error messages appear all over the form.

Is it by-design hard to do this? All examples I've seen use two
handlers for displaying the form and validating it.

Best,
Nadav

Alberto Valverde

unread,
Dec 5, 2006, 3:16:37 PM12/5/06
to turbo...@googlegroups.com
> I'd like to show a report in the root of my web site, and above it a
> form to customize the report (i.e. restrict dates and so on).
> Initially, the report is generated using default values.
>
> It works well, until I start using validators. Then, initially, the
> default values of the form are overridden by the variables of the
> request, and error messages appear all over the form.

Well... this is a feature :) Normally you'd like to inform your users of
the reason their form is not being accepted and pre-fill the values they
sent so they don't have to fill them again.


> Is it by-design hard to do this? All examples I've seen use two
> handlers for displaying the form and validating it.

You can handle the form in the same controller that displays it like this:

@validate(form=form)
@expose()
def index(self, tg_errors=None, **kw):
if tg_errors is None:
# validation succeeded, do something useful
# validation failed, let the form display errors and previous values
return(form=form, ...)

The important part is the 'tg_errors' keyword arg. It informs the
errorhandling mechanism that this method will be it's own error handler.
When validation fails inside the validate decorator it will pass the
resulting errors to your controller via that parameter.

The form will receive them magically along with previous input via
request-local storage @ cherrypy.request.

HTH,
Alberto

thesamet

unread,
Dec 5, 2006, 3:40:04 PM12/5/06
to TurboGears
Thanks for your answer.

Some fields in my form have a validator which requires them to be
non-empty. In that case, these errors appear on the first time the form
is displayed. Default values are lost as well - the form thinks the
user entered wrong values already.

Nadav

Alberto Valverde

unread,
Dec 6, 2006, 11:56:03 AM12/6/06
to turbo...@googlegroups.com

Hmmm, this is normal because the same controller method showing the
form is also validating it (with the decorator), therefore when
called with no request params required fields fail validation and the
form thinks it's being redisplayed so it prints errors and input
values (all blank).
I can think of three ways to circumventing it:

1) separate your form showing and handling methods in two.
2) keep a hidden field in your form with a StringBoolean validator
initialized like
StringBoolean(if_empty=False) which defaults to true. Then check for
it being True in FormValidator for the whole form.
3) Subclass Form and extend it's "validate" method so it only
filters the value through the validator if the request method is
a POST.

I'd personally go with 1 as it's easiest. If your app can't afford it
(due to requirement constraints, for example) then I'd try 3.

HTH,
Alberto

Matthew Bevan

unread,
Dec 6, 2006, 2:12:54 PM12/6/06
to turbo...@googlegroups.com
> Hmmm, this is normal because the same controller method showing the
> form is also validating it (with the decorator), therefore when
> called with no request params required fields fail validation and the
> form thinks it's being redisplayed so it prints errors and input
> values (all blank).
> I can think of three ways to circumventing it:

I wrote and submitted to the docs.turbogears.org server the following:

http://docs.turbogears.org/1.0/UnifiedControllers

It describes in full detail (with source code) how to combine form/
action controllers without any of problems associated with just
@exposing and @validating the entire thing. The code described also
has the added benefit of allowing validation of forms built at
execution time.

The gist of it is:

class MyController(Controller):
@expose(template="project.templates.genericform")
def index(self, **kw):
form = MyForm(fields=MyFields())

if kw:
@validate(form=form)
def get_errors(self, tg_errors=None, **data):
return tg_errors, data

tg_errors, data = get_errors(self, **kw)

if not tg_errors:
# Perform some action with the data in **data.
# **kw still contains the raw data.
pass

turbogears.flash("Some success message.")
turbogears.redirect("/")

else:
turbogears.flash("Some error message.")

else:
# Supply some default data, if required.
# Optionally supplying data allows you to combine
# create and modify actions in one.
data = dict(...)

return dict(title="Foo Bar", form=form, data=stock_data)

Top Floor has been using this structure extensively without
problems. Enjoy!

Matthew Bevan, Systems Administrator
Top Floor Computer Systems Ltd.


Ed Singleton

unread,
Dec 7, 2006, 7:17:55 AM12/7/06
to turbo...@googlegroups.com

I really like this. It's a good idea, and neatly done.

The one enhancement I'd like to make, and have been struggling to with
myself, is to turn it into a generic controller.

For me, I have to produce lots of forms, but there's few variables
between them (form, table, welcome message, thanbk you message, etc).
Most of which would work fine except that the validate decorator
complains if the form doesn't exist at the point when the function is
decorated. So I moved it into __init__:

class FormController(controllers.Controller):
def __init__(self, form, table, page, intro_text="",
thank_you="Thank you."):
super(FormController, self).__init__()
self.form = form
self.table = table
self.page = page
self.intro_text = intro_text
self.thank_you = thank_you

@expose()
@validate(form=self.form)
@error_handler(self.index)
def save(**kwargs):
table.save(**kwargs)
raise redirect('thank_you')

self.save = save

@expose(template='.templates.form')
def index(self):
page = self.page
form = self.form
intro_text = self.intro_text
return dict(page=page, form=form,
intro_text=intro_text)

@expose(template='.templates.form_thankyou')
def thank_you(self):
page = self.page
thank_you = self.thank_you
return dict(page=page, thank_you=
thank_you)

It doesn't yet work, as when you save the form you get the error:

Page handler: <function save at 0x2262030>
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/CherryPy-2.2.1-py2.4.egg/cherrypy/_cphttptools.py",
line 105, in _run
self.main()
File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/CherryPy-2.2.1-py2.4.egg/cherrypy/_cphttptools.py",
line 254, in main
body = page_handler(*virtual_path, **self.params)
File "<string>", line 3, in save
File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/TurboGears-1.0b1-py2.4.egg/turbogears/controllers.py",
line 326, in expose
output = database.run_with_transaction(
File "<string>", line 5, in run_with_transaction
File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/TurboGears-1.0b1-py2.4.egg/turbogears/database.py",
line 246, in so_rwt
retval = func(*args, **kw)
File "<string>", line 5, in _expose
File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/TurboGears-1.0b1-py2.4.egg/turbogears/controllers.py",
line 343, in <lambda>
mapping, fragment, args, kw)))
File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/TurboGears-1.0b1-py2.4.egg/turbogears/controllers.py",
line 367, in _execute_func
output = errorhandling.try_call(func, *args, **kw)
TypeError: try_call() takes at least 2 non-keyword arguments (1 given)

I'm wondering if this might be easier to do with your method of having
a single function that does everything. But I'm not too

Alberto Valverde

unread,
Dec 7, 2006, 7:53:40 AM12/7/06
to turbo...@googlegroups.com

On Dec 7, 2006, at 1:17 PM, Ed Singleton wrote:
...

> ....


> TypeError: try_call() takes at least 2 non-keyword arguments (1 given)
>
> I'm wondering if this might be easier to do with your method of having
> a single function that does everything. But I'm not too

When you're doing self.save = save your binding save as an unbound
method so when it's called 'self' is not passed as first positional
argument. You need to bind it as an instance method:

from new import instancemethod

def __init__(....):
...
def save(self, **kwargs): # don't forget to specify 'self' as
first parameter
....
self.save = instancemethod(save, self, self.__class__)

Alberto

Alberto Valverde

unread,
Dec 7, 2006, 8:05:20 AM12/7/06
to turbo...@googlegroups.com

Hmmm, and I think you might also need to pass the instance method
function to error_handler (but not sure):

@error_handler(self.index.im_func)
def save(...):

Please post back the solution when you find it... :)

Alberto

Ed Singleton

unread,
Dec 7, 2006, 9:25:56 AM12/7/06
to turbo...@googlegroups.com

Yes that did it. I had to do a log.debug(dir(self.index)) to be
really sure that you meant self.index.im_func as I'd never heard of
im_func before.

The solution below works fine except for the redirect to the thank you
page. I have to stop now but I'll play some more tomorrow. I'll
probably fix it by adapting Matthew's single form controller so that
there isn't a redirect.

**If anyone else wants to use this controller, a quick warning to note
that I add a save() method to my tables that mangles my data for me.
You may want to change it to read table(**kwargs)

class FormController(controllers.Controller):
def __init__(self, form, table, intro_text="", thank_you="Thank you."):


super(FormController, self).__init__()
self.form = form
self.table = table

self.intro_text = intro_text
self.thank_you = thank_you

@expose()
@validate(form=self.form)
@error_handler(self.index.im_func)
def save(self, **kwargs):


table.save(**kwargs)
raise redirect('thank_you')

self.save = new.instancemethod(save, self, self.__class__)

@expose(template='.templates.form')
def index(self):

form = self.form
intro_text = self.intro_text

return dict(form=form, intro_text=intro_text)

@expose(template='.templates.form_thankyou')
def thank_you(self):

thank_you = self.thank_you
return dict(thank_you=thank_you)

Alberto Valverde

unread,
Dec 7, 2006, 9:41:41 AM12/7/06
to turbo...@googlegroups.com

On Dec 7, 2006, at 3:25 PM, Ed Singleton wrote:
>
> Yes that did it. I had to do a log.debug(dir(self.index)) to be
> really sure that you meant self.index.im_func as I'd never heard of
> im_func before.

You did well, no one should believe my examples as I'm an expert at
introducing typos... :)

im_func is the unbound version of a bound method:

In [1]: from new import instancemethod

In [2]: def foo(self):
...: print self.msg
...:
...:

In [3]: class Test(object):
...: def __init__(self):
...: self.foo = instancemethod(foo, self, self.__class__)
...: self.msg = "Hello world"
...:
...:

In [4]: t = Test()

In [5]: t.foo()
Hello world

In [6]: t.foo.im_func is foo
Out[6]: True

In [7]: t.foo is foo
Out[7]: False


>
> The solution below works fine except for the redirect to the thank you
> page. I have to stop now but I'll play some more tomorrow. I'll
> probably fix it by adapting Matthew's single form controller so that
> there isn't a redirect.

What's the url being generated? you might need to put the full path
or '../thank_you' (mmmm, how I like Route's "url_for"... ;)

Alberto

Ed Singleton

unread,
Dec 7, 2006, 10:01:59 AM12/7/06
to turbo...@googlegroups.com
On 07/12/06, Alberto Valverde <alb...@toscat.net> wrote:
>
>

It was redirecting me to the site homepage even if I typed the url in
manually (/register/thank_you). I noticed I had a variable with the
same name as the method (both called thank_you) and renamed the
variable. Now it semi-correctly redirects me to the index method of
the controller (/register).

So the redirect itself is working fine, but the thank_you method isn't
being called for some reason.

Anyway, I'll look at it tomorrow.

Thanks for all the help with this by the way.

Ed

PS Where's the best place to import new? Should I do it in the
controller, or just remember to do it on every page where it is used?

Alberto Valverde

unread,
Dec 7, 2006, 10:18:52 AM12/7/06
to turbo...@googlegroups.com

On Dec 7, 2006, at 4:01 PM, Ed Singleton wrote:
>
> PS Where's the best place to import new? Should I do it in the
> controller, or just remember to do it on every page where it is used?

You can import it anywhere as long as it's in __init__'s scope, that is:

1) import it inside __init__
2) import it at the module your controller class is declared in
3) import it in anywhere and bind it to __builtins__ so it's
available everywhere

I'd go with 2), I would only do it in 1) if you need to deal with
circular imports (which wouldn't be the case as "new" won't be
importing anything from your app). 3 was more of a joke really... :P

Alberto

Marco Mariani

unread,
Jan 18, 2007, 11:37:39 AM1/18/07
to turbo...@googlegroups.com
Long ago, Matthew Bevan wrote:
> I wrote and submitted to the docs.turbogears.org server the following:
>
> http://docs.turbogears.org/1.0/UnifiedControllers
>
> It describes in full detail (with source code) how to combine form/
> action controllers without any of problems associated with just
> @exposing and @validating the entire thing. The code described also
> has the added benefit of allowing validation of forms built at
> execution time.
>
> The gist of it is:
>
> class MyController(Controller):
> @expose(template="project.templates.genericform")
> def index(self, **kw):
> form = MyForm(fields=MyFields())
>
> if kw:
> @validate(form=form)
> def get_errors(self, tg_errors=None, **data):
> return tg_errors, data
>
> tg_errors, data = get_errors(self, **kw)
[...]

I like it. Really. The two methods way has always been a pet peeve of mines.

But I don't want to copy-and-paste the inner function a few hundred of
times, so I extracted it, _and_ abused tg_errors:

def get_errors(self, form, **kw):
@validate(form=form)
def valid(self, tg_errors=None, **kw):
return tg_errors, kw
if not kw:
return {'xxx':'This message should not be displayed. Not an
error, really. The form is empty yet.'}, kw
return valid(self, **kw)


So, this is my controller for adding a user while validating the data:

@expose(template='erp.templates.users.add')
def add(self, **kw):
form = UserAddForm.form
tg_errors, data = get_errors(self, form, **kw)

if not tg_errors:
user = User.new(**data)
tg.flash("User '%s' has been created" % user.uid)
raise tg.redirect('/users/view', uid=user.uid))

return dict(form=form)

(User.new checks for duplicates and the like)

Seems to work, with default field values too, and... I have a unified
controller/error_handler that is shorter and simpler than the two
functions pattern.
Now, since I am not well-versed in decorator intricacies and magic, I
hope it won't explode on me, or break with python 2.5.

Too easy, is it?

I'm still looking for a nice way to avoid spurious "please insert a
value" when passing some default by GET method. uhm.

Nadav Samet

unread,
Jan 19, 2007, 4:47:37 PM1/19/07
to TurboGears
I've summarized the ideas in this thread and in
http://docs.turbogears.org/1.0/UnifiedControllers to a nice reusable
decorator. It also solves the "Please insert a value" problem by using
a hidden field.

Here is how a unified controller method looks like using this
decorator:

@expose(template='templates.add_form_template')
@unified_validate(form=my_form):
def add(self, **data, tg_errors=None):
if tg_errors is not None:
# there are errors or form was not submitted at all.
return dict(form=my_form)
do_add(data)
# return a dictionary or redirect

In order for this to work, you must add an hidden field to your form.
unified_validate() expects the name of this field to be 'submitted',
unless you specify another name as the submit_field argument.

The rest of the arguments to this decorator are just passed directly to
turbogears' validate. The idea is to actually perform the validation
only if the hidden field is given as argument, indicating that the form
was actually submitted.

Here is the decorator code:

def unified_validate(submit_field='submitted', *v_args, **v_kwargs):
@validate(*v_args, **v_kwargs)
def get_errors(self, tg_errors=None, *args, **kwargs):
return tg_errors, args, kwargs

def decorator(func):
def newfunc(self, *args, **kwargs):
if 'submitted' in kwargs:
tg_errors, args, kwargs = get_errors(self, *args,
**kwargs)
else:
tg_errors = {submit_field: 'Form not submitted.'}
return func(self, tg_errors=tg_errors, *args, **kwargs)
return newfunc
return decorator

Reply all
Reply to author
Forward
0 new messages