Problems with pylons.decorators.validate

7 views
Skip to first unread message

Tycon

unread,
Jan 3, 2009, 7:17:49 PM1/3/09
to pylons-discuss

I was trying to use this decorators like this:

@validate(schema=MySchema(), form='action')
def action(self):
if request.method=='GET':
return render('/form.mako')
else:
return _process_form(self.form_result)

But there are a few problems with this approach:

1. The first time the controller action is called, it renders the
template without removing the special error placement tags.
2. There is no way to set default values for the form other than
coding them into the template. It would be better to be able to set
default values that would be filled only the first time the form is
rendered.
3. There is no way to pass a dynamic state object to the decorator,
that would be passed to the form validators. This is because the
decorator is called at compile time, so the you can only pass to it
static objects as the "state".
4. You actually have to specify the name of the action method that
renders the form as an argument to the decorator, and this method
can't be private (so even if it's not the original action method for
this request, it is callable as an action method directly by itself).
5. The action method will be run every time the form has to be
rendered, e.g. after every failed validation,
so you can't use it to perform initial one-time checks.

All the above problems happen because using the decorator approach to
do too many things using a single method. Trying to work around the
limitations by breaking up the action method into several different
functions also doesn't work well when using this decorator. So it
seems like it's better to do the validation the "long" way, or at
least redesign this decorator to be a class with different methods
that can be overridden.

Tycon

unread,
Jan 4, 2009, 6:57:07 AM1/4/09
to pylons-discuss
To solve the problem with the supplied validation decorator, I came up
with a new validator the abstracts the different part of form handling
in the controller action. You use the new decorator on top of a action
method, in a similar way to the old decorator, but you pass a form
"handler" class in addition to the form schema class:

@validate(MySchema(), MyHandler())
def register(self):
c.info_for_template = f(request.params)
return render('my_form.mako')

Note that in the action method itself you only need to display the
form's page, and not do any pre- or post-processing. The decorator
will take care (using the handler class) of performing any checks,
validate the form (on POST reuests),
perform post processing if validation succeeded, or call your action
method to render the form and populate it with initial values or
values from the last POST request.

The schema class that is passed as the first argument to the decorator
is the same as for the old validate decorator - it's a class derived
from formencode.Schema that you define for your form, for example:

class MySchema(formencode.Schema):
email = formencode.validators.Email(not_empty=True)
name = formencode.validators.MinLength(2)

The real magic comes from the "form handler" class which you define as
a derived class of the new "FormHandler" class, for example:

class MyHandler(FormHandler):
def check(self):
if session.has_key('user'):
redirect_to(action='error') # can't register if already
logged-in
return None # everything OK

def process(self, result):
user=model.User(name=result['name'], email=result['email'])
user.save_to_db()
session['flash']='Thank you for registering'
session.save()
redirect_to('homepage')


The most important method to override is "process" which is called
after the form has been validated successfully. In this method you
will normally perform the actions which the form is designed for, such
as creating a new record in the database. The form's values are passed
in the argument "result".

You don't really need to define any of the other methods, but they are
useful in some situations.

The "check" method is called by the decorator in the beginning of
processing every request (the first GET request, and all subsequent
POST requests). Here you can check permissions and other pre-
conditions, and return "None" if everything is OK. If something is
wrong and you don't want the form to be displayed, then you can
redirect or return a different page (e.g. using mako_rander). Note
that this method is mostly for convenience, because you can achieve
the same thing by defining another method in your controller that
perfoms the same checks and then calls the method with the "validate"
decorator.

Another optional method is "defaults" which is called only once before
rendering the form on the first GET request, and is supposed to return
the initial values of the form's fields in a dict-like object. The
default "defaults" are taken from the request params.

The last method is "validation_ctx" and it's called by the "validate"
decorator just before performing form validation and is supposed to
return a "state" object that is passed to the form's (user-defined)
validators which were declared in the schema. Note that you can always
use the "c" global to pass information to the validators, but in any
case you can use this method to populate your chosen context.

Using this decorator has greatly simplified my form handling code, so
if there is interest I can publish the source code for this decorator/
handler architecture.

Mike Orr

unread,
Jan 4, 2009, 7:48:52 AM1/4/09
to pylons-...@googlegroups.com
On Sun, Jan 4, 2009 at 3:57 AM, Tycon <adi...@gmail.com> wrote:
> Using this decorator has greatly simplified my form handling code, so
> if there is interest I can publish the source code for this decorator/
> handler architecture.

Yes, please publish it and put a link in ticket #405
http://pylonshq.com/project/pylonshq/ticket/405

There has been a proposal to split @validate into three parts that can
be used independently in an action, while still keeping the decorator
compatible. Then Mike Bayer proposed a second solution which uses
Mako (and may be too much Mako code for a Pylons default?). But we
are still looking for other approaches. I use @validate but am not
that happy with it. I'm not sure that a validator is even the right
solution to this problem, because you can't pass action-specific state
through a decorator and that's often the most interesting state (the
database record corresponding to the request). One problem with
@validate currently is that it mixes code that's part of the basic
pattern with code to make the decorator flexible, and that makes it
cumbersome to inline "the equivalent code" in an action.

--
Mike Orr <slugg...@gmail.com>

Tycon

unread,
Jan 4, 2009, 11:21:05 AM1/4/09
to pylons-discuss
The validation "state" can be set using the "validation_ctx" method in
the FormHandler class. Here's the new handler and decorator:

from pylons.controllers.util import redirect_to
from pylons.decorators import PylonsFormEncodeState

import decorator
import formencode

class FormHandler(object):
def check(self):
return None
def defaults(self):
return request.GET
def validation_ctx(self):
return None
def process(self, result):
redirect_to(**dict(request.GET))

def validate(schema, handler, **htmlfill_kwargs):
def wrapper(func, *args, **kwargs):
errors = handler.check()
if errors:
return errors
if request.method=='GET':
defaults = handler.defaults()
else:
vctx = handler.validation_ctx() or PylonsFormEncodeState
try:
result = schema.to_python(request.params.mixed(),
vctx)
except formencode.Invalid, e:
errors = e.unpack_errors()
if not errors:
return handler.process(result)
defaults = request.POST
#display form
content = func(*args, **kwargs)
return formencode.htmlfill.render(content, defaults=defaults,
errors=errors, **htmlfill_kwargs)
return decorator.decorator(wrapper)



On Jan 4, 4:48 am, "Mike Orr" <sluggos...@gmail.com> wrote:
> On Sun, Jan 4, 2009 at 3:57 AM, Tycon <adie...@gmail.com> wrote:
> > Using this decorator has greatly simplified my form handling code, so
> > if there is interest I can publish the source code for this decorator/
> > handler architecture.
>
> Yes, please publish it and put a link in ticket #405http://pylonshq.com/project/pylonshq/ticket/405
>
> There has been a proposal to split @validate into three parts that can
> be used independently in an action, while still keeping the decorator
> compatible.  Then Mike Bayer proposed a second solution which uses
> Mako (and may be too much Mako code for a Pylons default?).  But we
> are still looking for other approaches.  I use @validate but am not
> that happy with it.  I'm not sure that a validator is even the right
> solution to this problem, because you can't pass action-specific state
> through a decorator and that's often the most interesting state (the
> database record corresponding to the request).  One problem with
> @validate currently is that it mixes code that's part of the basic
> pattern with code to make the decorator flexible, and that makes it
> cumbersome to inline "the equivalent code" in an action.
>
> --
> Mike Orr <sluggos...@gmail.com>

Tycon

unread,
Jan 4, 2009, 2:53:53 PM1/4/09
to pylons-discuss
This is the form template for the example code I gave previously. Note
that the form action is POST to the SAME URL as the original GET
request. This lets you define everything in a single controller action
method (together with the form-handler class and its "process"
method) :

${h.form(h.url_for())}
Email ${h.text('email')} <form:error name="email">
<br>
Email ${h.text('email')} <form:error name="name">
<br>
${h.submit('register', 'Register')}
${h.end_form()}

Tycon

unread,
Jan 4, 2009, 5:45:55 PM1/4/09
to pylons-discuss
PylonsHQ ticket system is down (does it run on pylons?)

On Jan 4, 4:48 am, "Mike Orr" <sluggos...@gmail.com> wrote:
> On Sun, Jan 4, 2009 at 3:57 AM, Tycon <adie...@gmail.com> wrote:
> > Using this decorator has greatly simplified my form handling code, so
> > if there is interest I can publish the source code for this decorator/
> > handler architecture.
>
> Yes, please publish it and put a link in ticket #405http://pylonshq.com/project/pylonshq/ticket/405
>
> There has been a proposal to split @validate into three parts that can
> be used independently in an action, while still keeping the decorator
> compatible.  Then Mike Bayer proposed a second solution which uses
> Mako (and may be too much Mako code for a Pylons default?).  But we
> are still looking for other approaches.  I use @validate but am not
> that happy with it.  I'm not sure that a validator is even the right
> solution to this problem, because you can't pass action-specific state
> through a decorator and that's often the most interesting state (the
> database record corresponding to the request).  One problem with
> @validate currently is that it mixes code that's part of the basic
> pattern with code to make the decorator flexible, and that makes it
> cumbersome to inline "the equivalent code" in an action.
>
> --
> Mike Orr <sluggos...@gmail.com>

Mike Orr

unread,
Jan 4, 2009, 6:42:19 PM1/4/09
to pylons-...@googlegroups.com
On Sun, Jan 4, 2009 at 2:45 PM, Tycon <adi...@gmail.com> wrote:
>
> PylonsHQ ticket system is down (does it run on pylons?)

Added. It must have been down momentarily. The tickets are in a Trac
project. I think pylonshq.com is Pylons.

--
Mike Orr <slugg...@gmail.com>

Tycon

unread,
Jan 4, 2009, 7:18:02 PM1/4/09
to pylons-discuss
OK, thanks.

I have a small correction to the form I used in the example (used
email field twice, it should be email and name):

${h.form(h.url_for())}
Email ${h.text('email')} <form:error name="email">
<br>
Name ${h.text('name')} <form:error name="name">
<br>
${h.submit('register', 'Register')}
${h.end_form()}

Also as for dynamically setting up contextual information for the
validation, you can use the "validation_ctx" method of the derived
FormHandler class you define to store that info in the "c" global (or
your own validation state object) based on the request parameters. And
then you can use that info in your validators. The "validation_ctx"
method is called by the decorator just before invoking the schema
validation.




On Jan 4, 3:42 pm, "Mike Orr" <sluggos...@gmail.com> wrote:
> On Sun, Jan 4, 2009 at 2:45 PM, Tycon <adie...@gmail.com> wrote:
>
> > PylonsHQ ticket system is down (does it run on pylons?)
>
> Added.  It must have been down momentarily.  The tickets are in a Trac
> project.  I think pylonshq.com is Pylons.
>
> --
> Mike Orr <sluggos...@gmail.com>

Ben Bangert

unread,
Jan 5, 2009, 12:45:44 PM1/5/09
to pylons-...@googlegroups.com
On Jan 4, 2009, at 2:45 PM, Tycon wrote:

> PylonsHQ ticket system is down (does it run on pylons?)

Not that one. I was hacking Trac code to allow Trac to take auth data
from a Pylons app (the Pylons controller dispatches to Trac via WSGI)
for the new site, and accidentally left the tweaks in the old one. The
new site has Trac better integrated and uses a single sign-on across
the entire site, ie:

Logging in here:
http://beta.pylonshq.com/

Keeps you logged in when you access the Trac via the Pylons app here:
http://beta.pylonshq.com/project/pylonshq/roadmap

Cheers,
Ben

Reply all
Reply to author
Forward
0 new messages