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.
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.
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
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.
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
> 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.
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
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.
give the submit button a name and check whether that name is present
in the dict?
ciao
Martina
@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?
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
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