Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Return to form after custom validation and keep form data?

154 views
Skip to first unread message

Seth

unread,
Jul 15, 2009, 4:56:44 AM7/15/09
to TurboGears
Hey guys,

So I've been messing around with tw.forms a little bit and maybe I'm
looking in the wrong places but I'm having a little bit of trouble
finding docs on some of the more advanced use cases.

The biggest thing I'm trying to figure out is how to return to the
form after some custom validation in my "save" method. So I've got my
"form" method that loads a template with the form and submits to the
"save" method. I've got my @validate decorator on the "save" method
which works great and goes back to the "form" method if any of the
tw.form validators throw an error. All of that is working splendidly.

However, I have some additional value-checking in the "save" method,
and want to redirect them back to the "form" method and use tg.flash()
to show something like "credit card declined". The bummer is that I
can't figure out how to make the form *keep the input* they've already
submitted. A redirect loses everything (tg.redirect converts POST to
GET), and forces the user to re-input all form data. Having the "save"
method 'return self.form(**kw)' doesn't seem to load the template from
the "form" method. Also, I don't really want to make the 'save' method
load a template because that's not its purpose.

Please help shine some light on the subject for me. Any documentation
links for more advanced tw.forms topics/examples would also be
helpful.

Thanks,
Seth

Diez B. Roggisch

unread,
Jul 15, 2009, 5:17:32 AM7/15/09
to turbo...@googlegroups.com

Two answers to this:

1) whatever you do to validate, do it in validators. That's the way we
currently go, and I don't see a reason not to do it. It's the clean road.

2) if you insist on doing it your way, I can only guess, but after a peek
into tg.controllers it appears as raising a formencode.Invalid-exception from
your controller *might* actually do what you want. Did you try that?

If you are successful with the second approach, it would be good if you could
come back here to tell us about that, so we know for future reference.


Diez

cd34

unread,
Jul 15, 2009, 11:33:48 AM7/15/09
to TurboGears
On Jul 15, 4:56 am, Seth <seedifferen...@gmail.com> wrote:
> Hey guys,
>
> So I've been messing around with tw.forms a little bit and maybe I'm
> looking in the wrong places but I'm having a little bit of trouble
> finding docs on some of the more advanced use cases.
>
> The biggest thing I'm trying to figure out is how to return to the
> form after some custom validation in my "save" method. So I've got my
> "form" method that loads a template with the form and submits to the

@expose('cp.templates.template')
def reform(self, **kw):
tmpl_context.form = ReForm()
return dict(template='form', value=kw)

@validate(ReForm(), error_handler=reform)
def reformcheck(self, **kw):
flash('great success')
redirect('reform',kw)

That method will hand you dirty URLs and is not PCI DSS compliant.
However, you probably want to use a chained validator to do something
like this:

class PaymentSchema(Schema):
chained_validators = [CreditCardValidator('cctype','ccnum'),
CreditCardExpires('expmon','expyear'),
CreditCardSecurityCode('cctype','cvv'),
RunCharge
('cctype','ccnum','expmon','expyear','cvv','addr1']

class PaymentForm(TableForm):
action = 'makepayment'
submit_text = 'make my payment'
validator = PaymentSchema


Then write your own validator for RunCharge:

class RunCharge(FancyValidator):
def _to_python(self, value, state):
# do magic
if card.declined:
flash(_('card declined'))
raise Invalid(
_('sorry, your card was declined'),
value, state)
return value

If you are using 2.0.1, you might take a look at http://trac.turbogears.org/ticket/2341

Seth

unread,
Jul 15, 2009, 6:22:03 PM7/15/09
to TurboGears
Diez and cd34,

Thanks! I was having a major mental block on Validators and your
comments helped me to implement the needed code as custom validators.
For future reference, this link was also helpful: http://formencode.org/Validator.html

However, there's one last thing I keep banging my head against the
wall over: How do I get the specific field errors to show up next to
the fields like they were before (preferably without hacking my own
form template). I thought I'd be able to pass
error=tmpl_context.form_errors into the form.display() call, but for
some reason it's expecting more than just a simple dict type (I'm
getting AttributeError: 'dict' object has no attribute 'error_dict').

I'm sure it's something simple that I'm missing, so I apologize in
advance.

Thanks,
Seth

Diez B. Roggisch

unread,
Jul 15, 2009, 6:35:13 PM7/15/09
to turbo...@googlegroups.com
Seth schrieb:

When using chained validators, you need return the error-message
properly. Right out of my head I can only say that you need to return
raise the Invalid-exception with not a string, but a dictionary as
error-message. And the keys of that dict need to match the form-fields.
Something along these lines. If that's not working, I try & dig some
code out, but that has to wait for work tomorrow.

Diez

Seth

unread,
Jul 15, 2009, 7:44:13 PM7/15/09
to TurboGears
Diez,

The error messages appear to be properly filling a dict of
tmpl_context.form_errors in the 'error_handler=' method, but the
messages such as "Please enter a value" aren't showing up next to the
fields anymore like they were when using old non-custom form
validators.

Somehow the errors aren't getting into the tw.forms mako template for
table_form in order to make the following lines trigger:

% if show_children_errors and error and not
field.show_error:
<span class="fielderror">${tw.content(error)}</span>
% endif


Any advice would be greatly appreciated.

Seth

cd34

unread,
Jul 15, 2009, 8:43:14 PM7/15/09
to TurboGears
On Jul 15, 6:22 pm, Seth <seedifferen...@gmail.com> wrote:
> However, there's one last thing I keep banging my head against the
> wall over: How do I get the specific field errors to show up next to
> the fields like they were before (preferably without hacking my own

class CheckPassword(FancyValidator):
def _to_python(self, value, state):
# do magic
if client.passwd != md5(value).hexdigest():
raise Invalid(
'That password doesn\'t match the existing password',
value, state)
return value

class PasswordSchema(Schema):
chained_validators = [FieldsMatch
('password','passwordverify')]

class PasswordForm(TableForm):
action = 'passwordsave'
validator = PasswordSchema
class fields(WidgetsList):
oldpassword = PasswordField(validator=CheckPassword)
password = PasswordField(validator=NotEmpty)
passwordverify = PasswordField(validator=NotEmpty)

With Turbogears 2.0.1, tw.forms 0.9.3dev_20090405-py2.5, Genshi 0.5.1,
a validation error for NotEmpty shows up next to the respective line,
the chained validator error shows up next to the Passwordverify line,
an invalid CheckPassword appears next to the oldpassword line.

Likewise in the credit card one snippet posted above, if I select just
the credit card type, the Card number displays please enter a value,
both the month and year fields state Invalid Expiration Date and the
CVV number reports Invalid Credit Card Security Code length. An
invalid length credit card number displays properly.

Offhand, I cannot get it to display improperly.

Seth

unread,
Jul 15, 2009, 10:10:40 PM7/15/09
to TurboGears
cd34,

I upgraded to TG 2.0.1 and tw.forms 0.9.3 and everything worked
perfectly.

Thank you all again!

Seth

Seth

unread,
Jul 16, 2009, 2:23:07 PM7/16/09
to TurboGears
So, I'm back again :(


My latest issue is not being able to make a field optional. It seems
that whatever I do, the form is requiring ALL fields. I want to make
the 'workshop' field optional, but when I leave it blank it is still
saying "missing value" in spite of setting is_required=False and
giving the validator a not_empty=False.

Here's the paste link to my code: http://pastebin.com/mcc6a31b

I couldn't figure out how to define RsvpField as a class like you were
with PasswordForm, and still make it work like I want with the spacers
and text labels. I'd like to clean this code up a little (seems a bit
repetitive to me), but I simply have not been able to find good
examples of complex tw.forms implementations.


Thanks again for all your help. Hopefully this thread will serve as a
lamppost for others in my position.

Seth

Seth

unread,
Jul 17, 2009, 12:56:28 PM7/17/09
to TurboGears
Using Google Source Code search, I finally managed to find a few
examples to help me figure out how to do this correctly.

The solution I came up with is as follows, please let me know if there
is a better way:

# FancyValidator to process the Credit Card using the authorize
package
class ProcessCard(validators.FancyValidator):
def _to_python(self, value, state):

# --snip authorize package stuff--

result_dict = aim.transaction(
# --snip--
)

if result_dict['code'] == '1':
# success
return value
else:
# failure
raise validators.Invalid(
result_dict['reason_text'],
value, state)

class AuthnetForm(TableForm):
submit_text='Purchase'

validator = validators.Schema(
chained_validators = [
validators.CreditCardValidator('ccType','ccNumber'),
validators.CreditCardSecurityCode('ccType','ccCode'),
ProcessCard()
]
)

children=[
TextField('name', validator=validators.String
(not_empty=True)),
# --snip--
Spacer(),
SingleSelectField('ccType', options=[('visa', 'Visa'),
('mastercard', 'Master Card'), ('discover', 'Discover'), ('amex',
'American Express')], validator=validators.NotEmpty),
CalendarDatePicker('ccExpires', date_format="%m/%Y",
validator=validators.NotEmpty),
TextField('ccNumber', label_text='Card #',
validator=validators.NotEmpty),
TextField('ccCode', label_text='CVV Code',
validator=validators.NotEmpty),
Spacer()
]

authnet_form = AuthnetForm('authnet_form')


class AuthnetController(BaseController):
@expose('vynetwork.templates.authnet.index')
def index(self, **kw):
if getattr(tmpl_context, 'form_errors', None):
if tmpl_context.form_errors.get('_the_form', None):
flash(tmpl_context.form_errors['_the_form'], 'error')
return dict(page='home', kw=kw, form=authnet_form)

@validate(authnet_form, error_handler=index)
@expose()
def process(self, **kw):
return 'Thank you!'
Reply all
Reply to author
Forward
0 new messages