deform: is it possible to invalidate a form / field manually ?

264 views
Skip to first unread message

Jonathan Vanasco

unread,
Feb 3, 2012, 3:52:20 PM2/3/12
to pylons-discuss
via FormEncode under Pylons, I was able to mark a valid field as
invalid, and then reprint.

i'm wondering if this is possible with deform.

a pseudocode example of this in action would be something like this:


def login(self):
formLogin = deform.Form(FormLogin(_)
posted = self.request.POST.items()
try:
appstruct = self.formLogin.validate(posted)
account =
model.useraccount.get_by_emailAddress(posted['email_address'])
if not account:
# mark a field as invalid
self.formLogin.mark_invalid( 'email address' , "This
email is not registered")
# invalidate the form to raise an exception
self.formLogin.invalidate()
except deform.ValidationFailure, e:
self.formLogin= e
return self._login_print()







cd34

unread,
Feb 3, 2012, 4:06:50 PM2/3/12
to pylons-discuss
You can set either a field validation:

Validation function:

database_username_re = re.compile('^[a-z0-9_]{2,16}$', re.IGNORECASE)
def validate_database_param(param):
if database_username_re.match(param):
return True
else:
return unicode('Value must be between 2 and 16 characters and
can only contain uppercase and lowercase alphanumeric characters or an
underscore')

Schema definition:

databasename = colander.SchemaNode(
colander.String(),
validator=colander.Function(validate_database_param),
title = 'Database Name',
)


Or, in your actual Schema call:

def validate_userpass_existing(form, value):
"""
checking to make sure we have a user/pass OR an existing user
"""
exc = None
if value['databasename']:
db = DBSession.query(cp_database). \
filter(cp_database.device_id==value['device_id']). \

filter(cp_database.databasename==value['databasename']).first()
if db:
exc = colander.Invalid(form, 'Database name already in
use.')
exc['databasename'] = 'Cannot create a duplicate database'
if exc:
raise exc

schema = DBAddSchema(validator=validate_userpass_existing). \
bind(user_list=grants, device_list=devices, \
csrf_token=request.session.get_csrf_token())

cd34

unread,
Feb 3, 2012, 4:08:49 PM2/3/12
to pylons-discuss
Forgot to add:

Validation on the field takes place on form submission. Validation on
the Schema takes place after all field validations are validated. So,
an error on a field where it has some constraint will not fire off
your check on the Schema. So, if someone put a short password in, and
you are checking to see if the user exists on the Schema validation,
it actually will not check the username until the password validation
passes.

Jonathan Vanasco

unread,
Feb 3, 2012, 4:20:12 PM2/3/12
to pylons-discuss
Thanks. I very much don't want to have that sort of pattern in
programming though -- where the application logic becomes shifted into
the form validation. I've run into too many issues with that making
maintenance a nightmare, or causing 'expensive' calls to needlessly be
made.

I really want / need the ability to manually mark a form and its
fields as valid or invalid.

If that can't be done with deform, then I'll just jump back to
formencode.

Jonathan Vanasco

unread,
Feb 3, 2012, 8:22:58 PM2/3/12
to pylons-discuss
hopefully someone will suggest a deform method...

until then, i ported my modified version of the old pylons validation
to pyramid...

https://gist.github.com/1734244

- i didn't port the decorator, as i rarely use it [ and i couldn't
figure out a good way to do it without mandating that a viewclass
always has a 'request' attribute ]

- it has a bunch of extras (setting form errors, etc), and a slightly
different naming system for arguments ( validate_get , validate_post,
etc )

- long ago, i stripped out lots of code that didn't seem to do much
good (ie compatibility, options i didn't like )

Mike Orr

unread,
Feb 3, 2012, 10:56:27 PM2/3/12
to pylons-...@googlegroups.com
On Fri, Feb 3, 2012 at 5:22 PM, Jonathan Vanasco <jona...@findmeon.com> wrote:
> hopefully someone will suggest a deform method...

This sounds like the same situation I've sometimes encountered, that
you need to do validation beyond what the field validator and schema
validator can do. It can only be done in the view because it depends
on state beyond the input data. FormEncode has a 'state' variable but
it's practically useless, and in any case you may not want to put
complex state logic in the schema validator. So the view has to be
able to set its own error messages and redisplay the form. You can do
this with FormEncode, and if you can't do it with Deform, it means
those applications won't be able to use Deform.

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

Jonathan Vanasco

unread,
Feb 4, 2012, 3:19:17 PM2/4/12
to pylons-discuss
Well... I could probably do these validations within the validators, I
just don't want to.

I've got the gist ( https://gist.github.com/1734244 ) working pretty
much perfectly to handle my needs right now. I dropped all the
'validators' handling, and am just limiting it to schemas -- because
its simpler and i don't need otherwise.

i'll package it up into something and toss it on pypi later.

Dirley

unread,
Feb 6, 2012, 12:31:18 PM2/6/12
to pylons-...@googlegroups.com
Excuse me if I'm missing something (I don't know formencode and also didn't
read all your code), but couldn't you declare a callback within your view and
set it as a validator of the field? Thus the validator could carry the
"context" of the view. The colander.Function validator could be used for this:

    def login(self):
      schema = FormSchema()

      def is_email_registered(value):
        return model.useraccount.get_by_emailAddress(value)

      schema['email_address'].validator = colander.Function(is_email_registered,
                                            message='This email is not registered')

      # ... deform form boilerplate


- D


2012/2/4 Jonathan Vanasco <jona...@findmeon.com>

--
You received this message because you are subscribed to the Google Groups "pylons-discuss" group.
To post to this group, send email to pylons-...@googlegroups.com.
To unsubscribe from this group, send email to pylons-discus...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/pylons-discuss?hl=en.


Eric Rasmussen

unread,
Feb 8, 2012, 7:19:51 PM2/8/12
to pylons-...@googlegroups.com
Hi everyone,

I believe the question here is, "how do I inject error messages into a Deform-rendered form from outside the Deform/Colander APIs?" I run into this most often when trying to gracefully handle errors (such as database integrity errors) that are thrown after I've validated the form.

It's actually straightforward to inject errors, but I couldn't find anything in the docs. I'm including a brief self-contained example below (untested), but also see the github gist at the bottom for a more complete version that I'm using in development:

import colander
import peppercorn
from deform import schema
from deform.form import Form
from deform.exception import ValidationFailure

# handy response type for illustrative purposes
class MaybeSuccess(object):
    def __init__(self, succeeded, field=None, error=None):
        self.succeeded = succeeded
        self.field = field
        self.error = error

schema = MySchema()
form = Form(schema)

if 'submit' in request.POST:
    controls = request.POST.items()
    try:
        deserialized = form.validate(controls)

        # do_something returns a MaybeSuccess object

        reply = do_something(deserialized)

        if reply.succeeded:

            return HTTPFound(location='http://example/success')
        else:
            schemanode = form.schema[reply.field]

            # manually create a colander.Invalid exception
            exc = colander.Invalid(schemanode, reply.error)

            # attach the exception object to the form node
            form[reply.field].error = exc

            # recreate the cstruct from the controls
            cstruct = form.deserialize(peppercorn.parse(controls))

            raise ValidationFailure(form, cstruct, None)

    except ValidationFailure, e:
        return {'form':e.render()}
else:
    return {'form':form.render()}

When you raise and catch the ValidationFailure in that example, the form that it renders will include the error message (reply.error) on the appropriate node/field (reply.field).

I'm working on a form-heavy application where this and a few other things came up frequently, so anyone interested should check out the small module in this gist: https://gist.github.com/1775626

It provides a standardized response type that your post-validation processing can use to indicate success (optionally returning an object) or failure (including one or more error messages and the nodes they should be attached to). I'm using it with SQLAlchemy right now and it's cut down on the code in my views considerably. I can add documentation and make it into an actual package if there's any interest.

Thanks!
Eric

Jonathan Vanasco

unread,
Feb 8, 2012, 8:10:19 PM2/8/12
to pylons-discuss
thanks eric.

that is what i'm talking about!

i've got my version working with formencode. in addition to the gist
above, i decided to just go ahead and set up
pyramid_formencode_classic on pypi and github (
https://github.com/jvanasco/pyramid_formencode_classic )

if i have some time this weekend, i'll try to see if I can switch to
deform. while I like formencode and the methods i've been using, i
really dislike how it relies on regexing to 'htmlfill' an invalid
form. i'd rather have a "render" method , and i think deform lets you
render fields individually (so you can control layout )

i use a similar concept as you. i have the form logic separated into
3 methods that essentially do the same as your if/else with a try.
- public dispatcher
- private form render
- private form submit

the dispatcher calls the render method by default. if it decides to
submit, I note the errors in a 'wrapper' for the formencode object,
then raise an exception to catch the form and reprint. this was
basically an approach I ended up with on pylons, as the easiest way to
manage formencode stuff.

I'm in total agreement that this general approach ( whether 1 action
or 3 ) is considerably easier to handle forms with than many of the
other approaches out there.

i made a quick diff (https://gist.github.com/1776061) with a few
suggestions:

added kwargs on init
-- submit_autodetect=True
-- render_state=None
-- params_source='POST'
it preserves your functionality, but allows the 'render first' or
other to be explicitly called, you can also validate on 'GET'


this may not even be necessary, but on init i also set
self.cstruct=None, and changed the cstruct to save there on
deserialize... i figured that on advanced forms you might want to
render fields individually.. and while i didn't dive into making a
wrapped function to render a specific element yet... that data needs
to be stashed somewhere.

Jonathan Vanasco

unread,
Feb 8, 2012, 9:10:23 PM2/8/12
to pylons-discuss
actually, the param_source should support:

GET
POST
params (GET & POST)

the idiomatic example would be an email verification routine- someone
is emailed a message that contains the form submission in a GET query
string, and a formerror would return to a 'blank' form that submits
via POST - allowing people to type in the verification manually ( in
the common event that an email client screwed up the text ).
Reply all
Reply to author
Forward
0 new messages