From: Ian Bicking <ianb@co...> - 2004-01-22 20:25:40
Brad Bollenbach wrote:
> [I've submitted a request to Sourceforge re: why I'm unable to post to
> sqlobject-discuss. Got the blanket response (from it being assigned to
> someone), but no word yet on a resolution.]
> Here's the unit test I wrote (including slightly modifying an existing
> test class) to demonstrate how instance-level validation should work. I
> include only the relevant parts from my copy of test.py:
> class Age2GreaterThanAge1(Validator.FancyValidator):
> def validatePython(self, values, state):
> cur_obj = state.soObject
> if values.has_key('age1'):
> age1 = int(values['age1'])
> age1 = cur_obj.age1
> if values.has_key('age2'):
> age2 = int(values['age2'])
> age2 = cur_obj.age2
> if not (age2 > age1):
> raise Validator.InvalidField(
> self.message('badAges', 'Age 2 must be greater than
> age1'), value, state)
> class SOValidation(SQLObject):
> _validator = Age2GreaterThanAge1()
> name = StringCol(validator=Validator.PlainText(), default='x',
> name2 = StringCol(validator=Validator.ConfirmType(str), default='y')
> name3 = IntCol(validator=Validator.Wrapper(fromPython=int),
> age1 = IntCol(notNone = True, default = 1)
> age2 = IntCol(notNone = True, default = 2)
> class ValidationTest(SQLObjectTest):
> classes = [SOValidation]
> ...snipped some field validation tests here...
> def testInstanceValidation(self):
> obj = SOValidation.new(age1 = 5, age2 = 18)
> self.assertRaises(Validator.InvalidField, setattr, obj, 'age2', 4)
> self.assertRaises(Validator.InvalidField, obj.set, age1 = 20,
> age2 = 19)
> self.assertRaises(Validator.InvalidField, SOValidation.new, age1
> = 7, age2 = 3)
> Does this look like a reasonable way for this functionality to work?
Yes, that looks good. Well, I've been using a plain "validator"
attribute in other places, but "_validator" fits better with the
SQLObject style (so far). It should take a list of validators as well,
in which case all validators must pass (in order).
> I started hunting through the code to implement but, of course, noticed
> that column setting seems to be happening in more than one place. It
> seems to me that set() should be the main method to use for setting
> values (e.g. a foo.bar = 1, would get ultimately end up in a call to
> foo.set(bar = 1)), and that there should be exactly this one place where
> all real database updates happen, so that we can also make this the
> precisely one place where all field- and instance-level validation takes
There should be a third, internal method that all sets go through.
Well... unless we wait until columns are implemented as descriptors,
which should simplify some of this stuff.
There's two setters right now, one for a batch set (.set()), and one for
setting individual columns (_SO_setValue). For now, they should both be
changed to call some third method.
One thing I'm not sure about -- the validator protocol expects a
dictionary of values, which I'm not sure that we have one available. It
also has to be the appropriate kind of values -- either the database
values or the Python values, depending on which way the conversion is going.
So... maybe this really should be done alongside the descriptor change.