[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