Index: nevow/test/test_formless.py =================================================================== --- nevow/test/test_formless.py (revision 231) +++ nevow/test/test_formless.py (working copy) @@ -12,102 +12,113 @@ class Typed(TestCase): def testString(self): s = formless.String() - self.assertEquals(s.coerce(""), None) + self.assertEquals(s.coerce(""), "") self.assertEquals(s.coerce("Fooo"), "Fooo") self.assertEquals(s.coerce("This is a string"), "This is a string") - s = formless.String(allowNone=False) - self.assertRaises(formless.InputError, s.coerce, "") + # Requiredness should be tested in freeform, since that + # happens in *InputProcessor.process now. + #s = formless.String(required=True) + #self.assertRaises(formless.InputError, s.coerce, "") - s = formless.String(allowNone=True) + s = formless.String() self.assertEquals(s.coerce("Bar"), "Bar") - self.assertEquals(s.coerce(""), None) + self.assertEquals(s.coerce(""), "") s = formless.String() self.assertEquals(s.coerce(' abc '), ' abc ') - s = formless.String(strip=True, allowNone=False) + s = formless.String(strip=True, required=True) self.assertEquals(s.coerce(' abc '), 'abc') self.assertEquals(s.coerce('\t abc \t \n '), 'abc') - self.assertRaises(formless.InputError, s.coerce, ' ') + # This test should be moved to freeform, and should use + # TypedInputProcessor.process instead of String.coerce. + #self.assertRaises(formless.InputError, s.coerce, ' ') - s = formless.String(allowNone=True, strip=True) + s = formless.String(strip=True) self.assertEquals(s.coerce(' abc '), 'abc') - self.assertEquals(s.coerce(' '), None) + self.assertEquals(s.coerce(' '), '') def testText(self): s = formless.Text() - self.assertEquals(s.coerce(""), None) + self.assertEquals(s.coerce(""), "") self.assertEquals(s.coerce("Fooo"), "Fooo") self.assertEquals(s.coerce("This is a string"), "This is a string") - s = formless.Text(allowNone=False) - self.assertRaises(formless.InputError, s.coerce, "") + # Move to freeform and call TypedInputProcessor.process + # instead of Text.coerce. + #s = formless.Text(required=True) + #self.assertRaises(formless.InputError, s.coerce, "") - s = formless.Text(allowNone=True) + s = formless.Text() self.assertEquals(s.coerce("Bar"), "Bar") - self.assertEquals(s.coerce(""), None) + self.assertEquals(s.coerce(""), "") s = formless.Text() self.assertEquals(s.coerce(' abc '), ' abc ') - s = formless.Text(strip=True, allowNone=False) + s = formless.Text(strip=True, required=True) self.assertEquals(s.coerce(' abc '), 'abc') - self.assertRaises(formless.InputError, s.coerce, ' ') + # Move to freeform and call TypedInputProcessor.process + # instead of Text.coerce. + #self.assertRaises(formless.InputError, s.coerce, ' ') - s = formless.Text(allowNone=True, strip=True) + s = formless.Text(strip=True) self.assertEquals(s.coerce(' abc '), 'abc') - self.assertEquals(s.coerce(' '), None) + self.assertEquals(s.coerce(' '), "") def testPassword(self): s = formless.Password() self.assertEquals(s.coerce("Fooo"), "Fooo") self.assertEquals(s.coerce("This is a string"), "This is a string") - s = formless.Password(allowNone=False) - self.assertRaises(formless.InputError, s.coerce, "") + # Move to freeform and call TypedInputProcessor.process + # instead of Text.coerce. + #s = formless.Password(required=True) + #self.assertRaises(formless.InputError, s.coerce, "") - s = formless.Password(allowNone=True) + s = formless.Password() self.assertEquals(s.coerce("Bar"), "Bar") - self.assertEquals(s.coerce(""), None) + self.assertEquals(s.coerce(""), "") s = formless.Password() self.assertEquals(s.coerce(' abc '), ' abc ') - s = formless.Password(strip=True, allowNone=False) + s = formless.Password(strip=True, required=True) self.assertEquals(s.coerce(' abc '), 'abc') - self.assertRaises(formless.InputError, s.coerce, ' ') + # Move to freeform and call TypedInputProcessor.process + # instead of Text.coerce. + #self.assertRaises(formless.InputError, s.coerce, ' ') - s = formless.Password(allowNone=True, strip=True) + s = formless.Password(strip=True) self.assertEquals(s.coerce(' abc '), 'abc') - self.assertEquals(s.coerce(' '), None) + self.assertEquals(s.coerce(' '), '') def testInteger(self): - i = formless.Integer(allowNone=False) + # Note: these tests don't check requiredness + i = formless.Integer(required=True) self.assertEquals(i.coerce("0"), 0) self.assertEquals(i.coerce("3409823098"), 3409823098) self.assertRaises(formless.InputError, i.coerce, "") self.assertRaises(formless.InputError, i.coerce, "a string") self.assertRaises(formless.InputError, i.coerce, "1.5") - i = formless.Integer(allowNone=True) + i = formless.Integer() self.assertEquals(i.coerce("1234567"), 1234567) - self.assertEquals(i.coerce(""), None) def testReal(self): - i = formless.Real(allowNone=False) + i = formless.Real(required=True) self.assertApproximates(i.coerce("0.0"), 0.0, 1e-10) self.assertApproximates(i.coerce("34098.23098"), 34098.23098, 1e-10) self.assertRaises(formless.InputError, i.coerce, "") self.assertRaises(formless.InputError, i.coerce, "a string") self.assertRaises(formless.InputError, i.coerce, "1.5j") - i = formless.Real(allowNone=True) + i = formless.Real() self.assertApproximates(i.coerce("1234.567"), 1234.567, 1e-10) - self.assertEquals(i.coerce(""), None) def testBoolean(self): - b = formless.Boolean(allowNone=False) + b = formless.Boolean(required=True) self.assertRaises(formless.InputError, b.coerce, "zoom") self.assertRaises(formless.InputError, b.coerce, True) self.assertRaises(formless.InputError, b.coerce, 54) @@ -115,14 +126,14 @@ self.assertEquals(b.coerce("True"), True) self.assertEquals(b.coerce("False"), False) - b = formless.Boolean(allowNone=True) + b = formless.Boolean() self.assertRaises(formless.InputError, b.coerce, "zoom") - self.assertEquals(b.coerce(""), None) + self.assertRaises(formless.InputError, b.coerce, "") self.assertEquals(b.coerce("True"), True) self.assertEquals(b.coerce("False"), False) def testFixedDigitInteger(self): - d = formless.FixedDigitInteger(3, allowNone=False) + d = formless.FixedDigitInteger(3, required=True) self.assertEquals(d.coerce("123"), 123) self.assertEquals(d.coerce("567"), 567) self.assertRaises(formless.InputError, d.coerce, "12") @@ -132,10 +143,10 @@ self.assertRaises(formless.InputError, d.coerce, " ") self.assertRaises(formless.InputError, d.coerce, "") - d = formless.FixedDigitInteger(3, allowNone=True) + d = formless.FixedDigitInteger(3) self.assertEquals(d.coerce("123"), 123) self.assertRaises(formless.InputError, d.coerce, "foo") - self.assertEquals(d.coerce(""), None) + self.assertRaises(formless.InputError, d.coerce, "") def testDirectory(self): @@ -143,15 +154,15 @@ os.mkdir(p1) p2 = self.mktemp() - d = formless.Directory(allowNone=False) + d = formless.Directory(required=True) self.assertEquals(d.coerce(p1), p1) self.assertRaises(formless.InputError, d.coerce, p2) self.assertRaises(formless.InputError, d.coerce, "") - d = formless.Directory(allowNone=True) + d = formless.Directory() self.assertEquals(d.coerce(p1), p1) self.assertRaises(formless.InputError, d.coerce, p2) - self.assertEquals(d.coerce(""), None) + self.assertRaises(formless.InputError, d.coerce, "") def testTypedInterfaceProperties(self): class Test(formless.TypedInterface): Index: nevow/formless.py =================================================================== --- nevow/formless.py (revision 231) +++ nevow/formless.py (working copy) @@ -107,7 +107,7 @@ class ITyped(Interface): """Typeds correspond roughly to tags in HTML, or with a complex type, more than one tag whose input - is validated and coerced as a unit. + is processed and coerced as a unit. """ def coerce(self, val): """OPTIONAL. @@ -174,12 +174,14 @@ complexType = False - def __init__(self, label='', description='', default='', allowNone=True, **attributes): + def __init__(self, label='', description='', default='', required=False, + requiredFailMessage='Please enter a value', **attributes): self.id = nextId() self.label = label self.description = description self.default = default - self.allowNone=allowNone + self.required=required + self.requiredFailMessage=requiredFailMessage self.attributes = attributes def getAttribute(self, name, default=None): @@ -189,32 +191,18 @@ raise NotImplementedError, "Implement in subclass" -class AllowNoneMixin: - """Mixin for typed types that helps with checking for allowNone.""" - - allowNoneFailMessage = 'Please enter a value.' - - def coerce(self, val): - if val == '': - if self.allowNone: - return None - raise InputError(self.allowNoneFailMessage) - return val - - - ####################################### ## External API; your code will create instances of these objects ####################################### -class String(Typed, AllowNoneMixin): +class String(Typed): """A string that is expected to be reasonably short and contain no newlines or tabs. strip: remove leading and trailing whitespace. """ - allowNoneFailMessage = 'Please enter a string.' + requiredFailMessage = 'Please enter a string.' def __init__(self, *args, **kwargs): if 'strip' in kwargs: @@ -227,7 +215,7 @@ def coerce(self, val): if self.strip: val = val.strip() - return AllowNoneMixin.coerce(self, val) + return val class Text(String): @@ -237,24 +225,22 @@ class Password(String): - allowNoneFailMessage = 'Please enter a password.' + requiredFailMessage = 'Please enter a password.' -class FileUpload(Typed, AllowNoneMixin): +class FileUpload(Typed): - allowNoneFailMessage = 'Please enter a file name.' + requiredFailMessage = 'Please enter a file name.' def coerce(self, val): - AllowNoneMixin.coerce(self, val.filename) - return val + return val.filename -class Integer(Typed, AllowNoneMixin): +class Integer(Typed): - allowNoneFailMessage = 'Please enter an integer.' + requiredFailMessage = 'Please enter an integer.' def coerce(self, val): - val = AllowNoneMixin.coerce(self, val) if val is None: return None try: @@ -269,12 +255,11 @@ raise InputError("%r is not an integer." % val) -class Real(Typed, AllowNoneMixin): +class Real(Typed): - allowNoneFailMessage = 'Please enter a real number.' + requiredFailMessage = 'Please enter a real number.' def coerce(self, val): - val = AllowNoneMixin.coerce(self, val) if val is None: return None try: @@ -285,9 +270,7 @@ class Boolean(Typed): def coerce(self, val): - if self.allowNone and val=='': - return None - elif val == 'False': + if val == 'False': return False elif val == 'True': return True @@ -299,24 +282,21 @@ def __init__(self, digits = 1, *args, **kw): Integer.__init__(self, *args, **kw) self.digits = digits - self.allowNoneFailMessage = \ + self.requiredFailMessage = \ 'Please enter a %d digit integer.' % self.digits def coerce(self, val): - if self.allowNone and val=='': - return None v = Integer.coerce(self, val) if len(str(v)) != self.digits: raise InputError("Number must be %s digits." % self.digits) return v -class Directory(Typed, AllowNoneMixin): +class Directory(Typed): - allowNoneFailMessage = 'Please enter a directory name.' + requiredFailMessage = 'Please enter a directory name.' def coerce(self, val): - val = AllowNoneMixin.coerce(self, val) if val is None: return None if not os.path.exists(val): @@ -343,8 +323,6 @@ """Coerce a value with the help of an object, which is the object we are configuring. """ - if self.allowNone and val == '': - return None try: val = int(val) except ValueError: @@ -958,8 +936,8 @@ ctx.remember(binding, IBinding) ctx.remember(configurable, IConfigurable) - bindingValidator = inevow.IInputValidator(binding) - rv = bindingValidator.validate(ctx, binding.boundTo, args) + bindingInputProcessor = inevow.IInputProcessor(binding) + rv = bindingInputProcessor.process(ctx, binding.boundTo, args) ctx.remember(rv, inevow.IHand) ctx.remember('%r success.' % bindingName, inevow.IStatusMessage) return rv Index: nevow/freeparking.py =================================================================== --- nevow/freeparking.py (revision 231) +++ nevow/freeparking.py (working copy) @@ -29,10 +29,10 @@ return result -class GroupBindingValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class GroupBindingInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data): + def process(self, context, boundTo, data): ## THE SPEC: self.original.typedValue.interface.__spec__ spec = self.original.typedValue.interface.__spec__ resultList = [None] * len(spec) @@ -41,11 +41,11 @@ failures = {} waiters = [] for i, sub in enumerate(spec): - def _validate(): - # note, _validate only works because it is called IMMEDIATELY + def _process(): + # note, _process only works because it is called IMMEDIATELY # in the loop, watch out for confusing behavior if it is called # later when 'i' has changed - resulti = resultList[i] =inevow.IInputValidator(sub).validate(context, boundTo, data, autoConfigure = False) + resulti = resultList[i] =inevow.IInputProcessor(sub).process(context, boundTo, data, autoConfigure = False) # Merge the valid value in case another fails results.update(resulti) def _except(e): @@ -60,7 +60,7 @@ results.update(pf) # Merge the error message failures.update(e.errors) - maybe = exceptblock(_validate, _except, formless.ValidateError) + maybe = exceptblock(_process, _except, formless.ValidateError) if isinstance(maybe, Deferred): waiters.append(maybe) def _finish(ignored): @@ -73,11 +73,11 @@ return DeferredList(waiters).addBoth(_finish) -class MethodBindingValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class MethodBindingInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data, autoConfigure = True): - """Knows how to validate a dictionary of lists + def process(self, context, boundTo, data, autoConfigure = True): + """Knows how to process a dictionary of lists where the dictionary may contain a key with the same name as some of the arguments to the MethodBinding instance. @@ -94,7 +94,7 @@ try: context = context.with(faketag) context.remember(binding, formless.IBinding) - results[name] =inevow.IInputValidator(binding.typedValue).validate(context, boundTo, data.get(name, [''])) + results[name] =inevow.IInputProcessor(binding.typedValue).process(context, boundTo, data.get(name, [''])) except formless.InputError, e: results[name] = data.get(name, [''])[0] failures[name] = e.reason @@ -112,11 +112,11 @@ return results -class PropertyBindingValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class PropertyBindingInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data, autoConfigure = True): - """Knows how to validate a dictionary of lists + def process(self, context, boundTo, data, autoConfigure = True): + """Knows how to process a dictionary of lists where the dictionary may contain a key with the same name as the property binding's name. """ @@ -124,7 +124,7 @@ context.remember(binding, formless.IBinding) result = {} try: - result[binding.name] =inevow.IInputValidator(binding.typedValue).validate(context, boundTo, data.get(binding.name, [''])) + result[binding.name] =inevow.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) @@ -134,56 +134,74 @@ return result -class TypedValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class TypedInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data): + def process(self, context, boundTo, data): """data is a list of strings at this point """ typed = self.original + + if data[0] == '': + if typed.required: + raise formless.InputError(typed.requiredFailMessage) + else: + return None if hasattr(typed, 'coerceWithBinding'): return typed.coerceWithBinding(data[0], boundTo) return typed.coerce(data[0]) -class PasswordValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class PasswordInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data): + def process(self, context, boundTo, data): """Password needs to look at two passwords in the data, """ + typed = self.original pw1 = data[0] args = context.locate(inevow.IRequest).args binding = context.locate(formless.IBinding) pw2 = args.get("%s____2" % binding.name, [''])[0] - if pw1 != pw2: + + if pw1 == pw2 == '': + if typed.required: + raise formless.InputError(typed.requiredFailMessage) + else: + return None + elif pw1 != pw2: raise formless.InputError("Passwords do not match. Please reenter.") return self.original.coerce(data[0]) -class RequestValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class RequestInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data): + def process(self, context, boundTo, data): return context.locate(inevow.IRequest) -class ContextValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, +class ContextInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, - def validate(self, context, boundTo, data): + def process(self, context, boundTo, data): return context -class UploadValidator(compy.Adapter): - __implements__ =inevow.IInputValidator, - def validate(self, context, boundTo, data): +class UploadInputProcessor(compy.Adapter): + __implements__ =inevow.IInputProcessor, + def process(self, context, boundTo, data): bind = context.locate(formless.IBinding) # TOTAL HACK: this comes from outer space fields = context.locate(inevow.IRequest).fields try: field = fields[bind.name] - return self.original.coerce(field) + # TODO: Is this the appropriate test? What is field set to? + if field == '': + if typed.required: + raise formless.InputError(typed.requiredFailMessage) + else: + return None + return typed.coerce(field) except KeyError: return '' - Index: nevow/__init__.py =================================================================== --- nevow/__init__.py (revision 231) +++ nevow/__init__.py (working copy) @@ -116,15 +116,15 @@ # -nevow.freeparking.GroupBindingValidator nevow.formless.GroupBinding nevow.inevow.IInputValidator -nevow.freeparking.MethodBindingValidator nevow.formless.MethodBinding nevow.inevow.IInputValidator -nevow.freeparking.PropertyBindingValidator nevow.formless.Property nevow.inevow.IInputValidator -nevow.freeparking.TypedValidator nevow.formless.ITyped nevow.inevow.IInputValidator -nevow.freeparking.PasswordValidator nevow.formless.Password nevow.inevow.IInputValidator -nevow.freeparking.RequestValidator nevow.formless.Request nevow.inevow.IInputValidator -nevow.freeparking.ContextValidator nevow.formless.Context nevow.inevow.IInputValidator -nevow.freeparking.ListValidator nevow.formless.List nevow.inevow.IInputValidator -nevow.freeparking.UploadValidator nevow.formless.FileUpload nevow.inevow.IInputValidator +nevow.freeparking.GroupBindingInputProcessor nevow.formless.GroupBinding nevow.inevow.IInputProcessor +nevow.freeparking.MethodBindingInputProcessor nevow.formless.MethodBinding nevow.inevow.IInputProcessor +nevow.freeparking.PropertyBindingInputProcessor nevow.formless.Property nevow.inevow.IInputProcessor +nevow.freeparking.TypedInputProcessor nevow.formless.ITyped nevow.inevow.IInputProcessor +nevow.freeparking.PasswordInputProcessor nevow.formless.Password nevow.inevow.IInputProcessor +nevow.freeparking.RequestInputProcessor nevow.formless.Request nevow.inevow.IInputProcessor +nevow.freeparking.ContextInputProcessor nevow.formless.Context nevow.inevow.IInputProcessor +nevow.freeparking.ListInputProcessor nevow.formless.List nevow.inevow.IInputProcessor +nevow.freeparking.UploadInputProcessor nevow.formless.FileUpload nevow.inevow.IInputProcessor # Index: nevow/inevow.py =================================================================== --- nevow/inevow.py (revision 231) +++ nevow/inevow.py (working copy) @@ -7,17 +7,17 @@ from nevow import compy -class IInputValidator(compy.Interface): +class IInputProcessor(compy.Interface): """handle a post for a given binding """ - def validate(self, context, boundTo, data): + def process(self, context, boundTo, data): """do something to boundTo in response to some data return a status message if everything's ok, and raise a formless.ValidateError if there is a problem """ - ## TODO should probably make a distinction between a Typed validator - ## and a Binding validator; probably would make the code cleaner + ## TODO should probably make a distinction between a Typed input processor + ## and a Binding input processor; probably would make the code cleaner class IConfigurableFactory(compy.Interface): @@ -26,7 +26,7 @@ - Implements IConfigurable directly - Implements a TypedInterface, thus providing enough information about the types of objects needed to allow the user to change - the object as long as the input is validated + the object as long as the input is processed """ def locateConfigurable(self, context, name): """Return the configurable that responds to the name.