confused with widgets and expose.inputform arg

1 view
Skip to first unread message

Max Ischenko

unread,
Jan 11, 2006, 9:32:37 AM1/11/06
to turbo...@googlegroups.com
Hello,

I'm trying to use newfangled widgets and run into a problem. I'm not sure whether I'm doing something wrong or simply run into a bug.

I have defined widget-based form like this:


addBookForm = TableForm(name='addbook', submittext=_('Add Book'),
widgets=[...])

Now I have the following method in my controller class:

@turbogears.expose(content_type="text/html; charset=UTF-8",
inputform=addBookForm,
html="bookswap.templates.addbook")
def addbook(self, **kw):
if cherrypy.request.method == 'POST':
errors = cherrypy.request.form_errors
if errors:
turbogears.flash(_('please correct errors below'))
else:
turbogears.flash(_('book has been added'))
raise cherrypy.HTTPRedirect(turbogears.url("/addbook"))
return dict(form=addBookForm)

The logic is simple: on GET request simply present user with a form. On POST, validate form data and either redirect to the same form (/addbook) to enter next book or re-display form highlighting the errors.

Note that addBookForm is a static var (module-level global). Btw, is it OK? In other thread Kevin said that widgets are stateless so I suppose I'm doing right but it feels a bit awkward. Anyway.


When I'm trying to display form in a brower I got a traceback:

Traceback (most recent call last):
File "C:\Python24\Lib\site-packages\CherryPy-2.1.0-py2.4.egg\cherrypy\_cphttptools.py", line 271, in run
main()
File "C:\Python24\Lib\site-packages\CherryPy-2.1.0-py2.4.egg\cherrypy\_cphttptools.py", line 502, in main
body = page_handler(*args, **cherrypy.request.paramMap)
File "d:\projects\3rd-party\turbogears\turbogears\controllers.py", line 240, in newfunc
html, fragment, *args, **kw)
File "d:\projects\3rd-party\turbogears\turbogears\database.py", line 191, in run_with_transaction
retval = func(*args, **kw)
File "d:\projects\3rd-party\turbogears\turbogears\controllers.py", line 257, in _execute_func
output = func(self, *args, **kw)
File "d:\projects\3rd-party\turbogears\turbogears\controllers.py", line 111, in newfunc
inputform.input(kw)
File "d:\projects\3rd-party\turbogears\turbogears\widgets\forms.py", line 108, in input
widget.input(values)
File "d:\projects\3rd-party\turbogears\turbogears\widgets\base.py", line 366, in input
widget.input(values)
File "d:\projects\3rd-party\turbogears\turbogears\widgets\base.py", line 134, in input
params[self.name] = self.validator.to_python(
File "C:\Python24\Lib\site-packages\FormEncode-0.4-py2.4.egg\formencode\api.py", line 304, in to_python
value = tp(value, state)
File "C:\Python24\Lib\site-packages\FormEncode-0.4-py2.4.egg\formencode\validators.py", line 923, in _to_python
value = float(value)
TypeError: float() argument must be a string or a number

After I changed line 106 of the controllers.py from:
if not processed and form:
to:
if not processed and form and kw:
the problem gone.

That's using r491 of SVN.

Karl Guertin

unread,
Jan 11, 2006, 11:36:27 AM1/11/06
to turbo...@googlegroups.com
On 1/11/06, Max Ischenko <isch...@gmail.com> wrote:
> I'm trying to use newfangled widgets and run into a problem. I'm not sure whether I'm doing something wrong or simply run into a bug.
>
> After I changed line 106 of the controllers.py from:
> if not processed and form:
> to:
> if not processed and form and kw:
> the problem gone.
>
> That's using r491 of SVN.

Everything in the TG codebase uses different urls for the form and its
action. When the kw check isn't in controllers.py, the decorator
attempts to validate a non-existent form, which gives the error you
see.

I believe that you can safely keep the kw check in controllers.py
because behavior changing parameters like tg_format are popped out of
the kw dict before they get to the validator.

Kevin Dangoor

unread,
Jan 11, 2006, 6:13:46 PM1/11/06
to turbo...@googlegroups.com

The problem is that the framework has no way to know if the request is
being made because of a form submission or not. Maybe that method
needs to have validation run on it (like a "save" method that always
receives form submissions). Some forms are submitted via GET, so you
can't even count on that to tell you whether you should be running the
validators or not.

Kevin

Max Ischenko

unread,
Jan 12, 2006, 5:44:36 AM1/12/06
to TurboGears

OK, so the question is: is it possible to use the same method to both
show input form and process data? If yes, how'd I do it correctly?

Karl Guertin

unread,
Jan 12, 2006, 7:20:36 AM1/12/06
to turbo...@googlegroups.com
On 1/12/06, Max Ischenko <isch...@gmail.com> wrote:
> OK, so the question is: is it possible to use the same method to both
> show input form and process data? If yes, how'd I do it correctly?

It's possible using the change you made to controllers.py. What Kevin
is saying is that the change you've made depends on knowledge of your
application.

For example say you have the following function mounted on the root
(so /foobar gets you to here):

@expose(inputform=some_form, template=".show_form")
def foobar(self,*args,**params):
return dict(args = args, params = params)

And that the form in show_form has method POST and action '/foobar'.

If you make the changes you've made to controllers.py, it will work
like you expect as long as the query string portion of your url is
empty (no GET parameters). If you try to access it using
'/foobar?tg_format=json' because you're doing ajax requests, it should
still work because tg_format and tg_random are stripped before they
get to the validator. If you have anything else, like
'/foobar?action=edit', you'll get weird behavior because TG will try
to process the get params as if it's the values from inputform.

Passing inputform tells Turbogears that you want the params treated as
if they were that form. It makes no distinction between GET and POST
forms and only your app can know if there's a meaningful difference
between the two. It may be possible to separate GET from POST by
mucking around with cherrypy but you'll have to write your own version
of turbogears.expose.

Hopefully that makes sense.

Kevin Dangoor

unread,
Jan 12, 2006, 10:49:15 AM1/12/06
to turbo...@googlegroups.com
On 1/12/06, Max Ischenko <isch...@gmail.com> wrote:
>
>
> OK, so the question is: is it possible to use the same method to both
> show input form and process data? If yes, how'd I do it correctly?

In addition to what Karl said, I should note that there has been
discussion on the CherryPy list about ways to conveniently deal with
GET and POST separately. You might want to speak up on cherrypy-users
if that's something appealing to you.

The idea is something along these lines:

@turbogears.expose()
def somemethod_GET(self...):
...display the form

@turbogears.expose()
def somemethod_POST(self...):
...accept the data

So, you have two methods but one URL. With a setup like this, we can
comfortably do what you want.

Kevin

--
Kevin Dangoor
Author of the Zesty News RSS newsreader

email: k...@blazingthings.com
company: http://www.BlazingThings.com
blog: http://www.BlueSkyOnMars.com

Max Ischenko

unread,
Jan 12, 2006, 11:40:24 AM1/12/06
to TurboGears

Karl, Kevin -- thanks for your replies.

> In addition to what Karl said, I should note that there has been
> discussion on the CherryPy list about ways to conveniently deal with
> GET and POST separately. You might want to speak up on cherrypy-users
> if that's something appealing to you.

Well, I found that thread in cherrypy archive as well as ticket #102
it was referring to (http://www.cherrypy.org/ticket/102). Looks like
this feature will not be supported anytime soon. Sigh.

So how do people handle this? Use two handlers? I'd still want the user
to see only *one* url.

Max Ischenko

unread,
Jan 12, 2006, 11:43:02 AM1/12/06
to TurboGears
Karl, a bit off-the-topic but nevertheless. In the example you given
how is template is located/loaded? (I mean that ".show_form" syntax).

Kevin Dangoor

unread,
Jan 12, 2006, 11:55:46 AM1/12/06
to turbo...@googlegroups.com
On 1/12/06, Max Ischenko <isch...@gmail.com> wrote:
> > In addition to what Karl said, I should note that there has been
> > discussion on the CherryPy list about ways to conveniently deal with
> > GET and POST separately. You might want to speak up on cherrypy-users
> > if that's something appealing to you.
>
> Well, I found that thread in cherrypy archive as well as ticket #102
> it was referring to (http://www.cherrypy.org/ticket/102). Looks like
> this feature will not be supported anytime soon. Sigh.

Feel free to open a ticket in the TG trac. There may be a solution
possible in TurboGears. It won't happen for 0.9, but it could happen
after that.

> So how do people handle this? Use two handlers? I'd still want the user
> to see only *one* url.

Yep, that's the trick. The widgets stuff is still new, so questions
like this need to be answered... (thus far, I've been using two urls).

Kevin

Karl Guertin

unread,
Jan 12, 2006, 2:01:44 PM1/12/06
to turbo...@googlegroups.com
On 1/12/06, Max Ischenko <isch...@gmail.com> wrote:
> Karl, a bit off-the-topic but nevertheless. In the example you given
> how is template is located/loaded? (I mean that ".show_form" syntax).

TG svn is clever and knows how to look up templates without needing
the project name. You do need the directory, however, so it's:

@expose(inputform=some_form, template=".templates.show_form")


def foobar(self,*args,**params):
return dict(args = args, params = params)

I don't actually know how this works, but it's fun.

Martina Oefelein

unread,
Jan 12, 2006, 3:24:00 PM1/12/06
to turbo...@googlegroups.com

give the submit button a name and check whether that name is present
in the dict?

ciao
Martina

Max Ischenko

unread,
Jan 13, 2006, 10:34:33 AM1/13/06
to TurboGears
Here is how what I ended up with, in case of anyone interested:


@turbogears.expose(content_type="text/html; charset=UTF-8",


html="bookswap.templates.addbook")
def addbook(self, **kw):

### XXX: why this is needed?
for k,v in kw.items():
if isinstance(v, str): kw[k] = unicode(v, 'utf-8')
if kw: # feed data if any
addBookForm.input(kw)
cherrypy.request.input_values = kw # to display form with
data


if cherrypy.request.method == 'POST':
errors = cherrypy.request.form_errors

cherrypy.request.form_processed = True


if errors:
turbogears.flash(_('please correct errors below'))
else:

log.info("Adding book from %s", kw)


turbogears.flash(_('book has been added'))

turbogears.redirect('/addbook')
return dict(form=addBookForm)

Note the absense of inputform parameter in expose() and the need to set
cherrypy.request.input_values in order the form to be displayed filled
with user data.

A weird thing: yesterdays unicode data were handled OK by this
particular method but after today's SVN update it broke and I had to
add this hack in order to make it work again:

for k,v in kw.items():
if isinstance(v, str): kw[k] = unicode(v, 'utf-8')

Any ideas?

Kevin Dangoor

unread,
Jan 19, 2006, 9:50:58 AM1/19/06
to turbo...@googlegroups.com

Yeah, that's pretty ugly. You don't *have* to put the data in
input_values. You can alternatively pass a dictionary (or instance) to
the form from your template.

> A weird thing: yesterdays unicode data were handled OK by this
> particular method but after today's SVN update it broke and I had to
> add this hack in order to make it work again:

Hmm... We did have the UnicodeDecodeFilter included. I wonder if that
is not included any longer for some reason.

Kevin

Kevin Dangoor

unread,
Jan 19, 2006, 9:56:35 AM1/19/06
to turbo...@googlegroups.com
(context: we were talking about using one URL for form display and posting).

On 1/12/06, Martina Oefelein <Mar...@oefelein.de> wrote:
> give the submit button a name and check whether that name is present
> in the dict?

That could work as a stopgap, but I think my leaning is to ultimately
go with the plan for CherryPy to be able to dispatch to different
methods depending on GET or POST.

Kevin

Reply all
Reply to author
Forward
0 new messages