
Hello *. I want to validate forminput against a database. For this reason, i wrote my own class: class Username(annotate.Typed): def coerce (self, val, configurable): [some code] def _eb(r): raise annotate.InputError('database error') # this works very well: # def _cb(r, val): # if len(r): # return r[0][0] # return '' # if the username already in use, i want to raise an exception: # the errormessage isn't placed correct in the rendered # 'form'page, but the whole exceptionpage is filled in # in the slot('error') of this input field def _cb(r, val): if len(r): raise annotate.InputError("username in use") return val d = dbpool.runQuery("""SELECT username FROM userdb WHERE username = %s""", str(val)) d.addCallback(_cb, val) d.addErrback(_eb) return d dbpool.runQuery returns a deferred. If i want to use the username returned by the query, the result is placed correctly in the formfield. But if an exception raised (here: InputError), the delay by the deferred is to long and the whole exceptionpage is placed in the slot('error'), not the message ('username in use') only. Is my way right? Is there a way to solve this problem? Stephan

Stephan Cieszynski wrote:
class Username(annotate.Typed): def coerce (self, val, configurable): ... d = dbpool.runQuery("""SELECT username FROM userdb WHERE username = %s""", str(val)) d.addCallback(_cb, val) d.addErrback(_eb)
return d
coerce cannot defer. To do that, move your validation into e.g. the start of an autocallable and do something like def foo(self, ctx, foo, bar): if not valid(foo): raise annotate.ValidateError( {'foo': 'Foo is bad.' }, partialForm=ctx.locate(inevow.IRequest).args) ... foo = annotate.autocallable(foo) where the dict is variablename->errormessage. See ValidateError for more.

Tommi Virtanen wrote:
coerce cannot defer. To do that, move your validation into e.g. the start of an autocallable and do something like
def foo(self, ctx, foo, bar): if not valid(foo): raise annotate.ValidateError( {'foo': 'Foo is bad.' }, partialForm=ctx.locate(inevow.IRequest).args) ... foo = annotate.autocallable(foo)
where the dict is variablename->errormessage. See ValidateError for more.
Sorry, but the following description lead me to use a deferred: formless/iformless.py: def coerce(self, val, configurable): """Coerce the input 'val' from a string into a value suitable for the type described by the implementor. If coercion fails, coerce should raise InputError with a suitable error message to be shown to the user. 'configurable' is the configurable object in whose context the coercion is taking place. May return a Deferred. -----------------------^ """ Your example not working: First, if 'foo' is a deferred the callback isn't fired at this time and validating isn't possible. Second, raising an exception in a autocallable like this def foo(self, ctx, bar): def _cb(r): if not valid(r): raise annotate.ValidateError(...) bar.addCallback(_cb) take no effect at rendering time and the slot('error') isn't filled with our errormessage. Stephan

Stephan Cieszynski wrote:
Sorry, but the following description lead me to use a deferred:
formless/iformless.py: def coerce(self, val, configurable): """Coerce the input 'val' from a string into a value suitable for the type described by the implementor. If coercion fails, coerce should raise InputError with a suitable error message to be shown to the user. 'configurable' is the configurable object in whose context the coercion is taking place.
May return a Deferred. -----------------------^ """
That's what the documents say, but sadly not what the code always does. It may even work in some cases, but definitely not in many cases. .coerce() is called from .process(), which returns the value directly through, and only does exception handling for immediately raised exceptions: try: result[binding.name] = iformless.IInputProcessor( binding.typedValue).process(context, boundTo, data.get(binding.name, [''])) except formless.InputError, e: result[binding.name] = data.get(binding.name, ['']) raise formless.ValidateError({binding.name: e.reason}, e.reason, result) ... return result A Deferred returning an errback in the above will go unnoticed by that code.
Your example not working: First, if 'foo' is a deferred the callback isn't fired at this time and validating isn't possible.
"foo" is a _function_. It cannot be a Deferred. Did you mean if "foo" _returns_ a Deferred, that is "if 'foo' is deferred", without "a"? I still can't parse that sentence.
Second, raising an exception in a autocallable like this def foo(self, ctx, bar): def _cb(r): if not valid(r): raise annotate.ValidateError(...)
bar.addCallback(_cb)
take no effect at rendering time and the slot('error') isn't filled with our errormessage.
bar.addCallback? If bar is Deferred, nevow should not call that function until it has a result for the Deferred. Though I wouldn't be surprised to see more bugs in this area.. Callers not seeing the raise ValidateError is only logical, because you raise it in a callback of a Deferred nothing waits on. That's like returning a value from function, but no one using or storing that value in any way. "return bar" may change things there.
participants (2)
-
Stephan Cieszynski
-
Tommi Virtanen