[debexpo-devel] Example with formencode state

Christoph Haas email at christoph-haas.de
Tue Jun 24 10:39:41 CEST 2008


Hi, Jonny...

on IRC you asked for a real-life example of formencode's somewhat magical
"state" thing. Let me quote from a validator I wrote for my network
administration application:

========================================================
class NetworkDoesNotExistYet(formencode.FancyValidator):
    """Validator that makes sure there is not yet such a network"""
    messages = {
            'exists_contains' : \
                u'This network would contain the already existing network %(contains)s.',
            'exists_contained_in' : \
                u'This network is contained in the existing network %(contained_in)s.',
            }

    def _to_python(self, value, state=None):
        log.debug("validator:NetworkDoesNotExistYet value=%s state=%r" % (value, state))

	# state.old_network_id optionally contains the ID of an existing network.
	# This is used during editing so that the collision detection of two
	# networks prevent seeing a collision of the old with the (same) new
	# network.
        if hasattr(state, 'old_network_id'):
            old_network = model.Network.q().get(state.old_network_id)
        else:
            old_network = None

	# A new network would only be unique if it didn't collide with any
	# other network.

        contains = model.Network.q().filter(model.Network.c.inet.op('<<=')(value))
        contained_in = model.Network.q().filter(model.Network.c.inet.op('>>=')(value))

	# Make sure that collisions with state.old_network_id are not taken into
	# account.
        if old_network:
            contains = contains.filter(model.Network.c.inet!=old_network.inet)
            contained_in = contains.filter(model.Network.c.inet!=old_network.inet)

        if contains.count():
            raise formencode.Invalid(self.message("exists_contains",
                        state, contains=contains.first().inet), value, state)

        if contained_in.count():
            raise formencode.Invalid(self.message("exists_contained_in",
                        state, contained_in=contained_in.first().inet), value, state)

        return value
========================================================

The state I use comes from a trivial class:

========================================================
class State(object):
    """Triviale Klasse von State-Objekten zur Übergabe an Formencode"""
    def __init__(self, **kw):
        for key in kw:
            setattr(self, key, kw[key])
========================================================

Which is just the more comfortable form of...

========================================================
class State(object): pass
========================================================

...which takes argument upon initialisation. Example:

some_state = State(old_network=...)

So an example schema here is:

========================================================
class ValidateNetwork(formencode.Schema):
    allow_extra_fields = True
    ignore_key_missing = True

    inet = formencode.compound.All(
        validators.NetworkDoesNotExistYet(),
        validators.ExistsReverseDnsZoneForInet(),
        validators.ValidInet(not_empty=True)
        )
    info = formencode.validators.String(if_empty='')
========================================================

I didn't get this to work with the @validate decorator. But I'm
doing the validation manually anyway:

========================================================
            try:
                fields = my.validate(ValidateNetwork, old_network_id=id)
            except formencode.Invalid, e:
                return my.htmlfill(self.edit(id=id), e)
========================================================

And my.validate and my.htmlfill are custom functions I wrote that
do this:

========================================================
def validate(schema, **state_kwargs):
    # create simple state object
    if state_kwargs:
        state = State(**state_kwargs)
    else:
        state = None

    return schema.to_python(pylons.request.params, state)
========================================================

...and...

========================================================
def htmlfill(html, exception_error=None):
    log.debug('my.htmlfill formencode exception: %s' % (exception_error,))
    # add error messages to a HTML page with errors from the
    # formencode exception.
    return formencode.htmlfill.render(
        form=html,
        defaults=pylons.request.params,
        errors=(exception_error and exception_error.unpack_errors()),
        encoding=pylons.response.determine_charset()
    )
========================================================

I hope you don't feel swamped with code too much now. :) For simple
cases the @validate decorator and a simple validation schema with
built-in validators might be easy. But in my current application I
have a dozen own validators and some validations are quite complex.
Sometimes I wonder if I should code that myself instead of using
formencode. I'm not really a fan of it. But it's the official
validation solution that Pylons is using. Perhaps it's time for a
rewrite.

Let me know how much of this is unclear.

Cheers
 Christoph
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part.
Url : http://workaround.org/pipermail/debexpo-devel/attachments/20080624/151b0d62/attachment.pgp 


More information about the debexpo-devel mailing list