Changeset 298

Show
Ignore:
Timestamp:
09/29/07 16:21:31 (1 year ago)
Author:
matt
Message:

Support deferred validators. Thanks to Marian Schubert for the patch.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/ChangeLog

    r293 r298  
     12007-09-29  Matt Goodall <matt@pollenation.net> 
     2 
     3        Add deferred validator support, many thanks to Marian Schubert. 
     4 
    152007-08-16  Tim Parkin <tim@pollenation.net> 
    26 
  • trunk/formal/examples/validator.py

    r196 r298  
    11from zope.interface import implements 
     2from twisted.internet import defer 
    23import formal 
    34from formal import iformal 
    45from formal.examples import main 
    56 
     7 
    68# A not-too-good regex for matching an IP address. 
    79IP_ADDRESS_PATTERN = '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' 
     10 
    811 
    912class ValidatorFormPage(main.FormExamplePage): 
     
    2629        # Check age is between 18 and 30 
    2730        form.addField('ohToBeYoungAgain', formal.Integer(validators=[formal.RangeValidator(min=18, max=30)])) 
     31        form.addField('deferred', formal.String(validators=[DeferredValidator()])) 
    2832        form.addAction(self.submitted) 
    2933        return form 
     
    3236        print form, data 
    3337         
     38 
    3439class SillyValidator(object): 
    3540    """ 
     
    4550        if value.lower() != self.word.lower(): 
    4651            raise formal.FieldValidationError(u'You must enter \'%s\''%self.word) 
     52 
     53 
     54class DeferredValidator(object): 
     55    """ 
     56    Just to demonstrate the validators can be deferred. 
     57    """ 
     58 
     59    def validate(self, field, value): 
     60        if value == 'fail': 
     61            return defer.fail(formal.FieldValidationError(u"You triggered a failure")) 
     62        return defer.succeed(None) 
     63 
  • trunk/formal/form.py

    r277 r298  
    55from zope.interface import Interface 
    66from twisted.internet import defer 
     7from twisted.internet import reactor 
    78from twisted.python.components import registerAdapter 
    89from nevow import appserver, context, loaders, inevow, rend, tags as T, url 
     
    130131 
    131132    def process(self, ctx, form, args, errors): 
    132  
    133133        # If the type is immutable then copy the original value to args in case 
    134134        # another validation error causes this field to be re-rendered. 
     
    138138 
    139139        # Process the input using the widget, storing the data back on the form. 
    140         try: 
    141             form.data[self.key] = self.makeWidget().processInput(ctx, self.key, args) 
    142         except validation.FieldError, e: 
     140        widget = self.makeWidget() 
     141        def _cbProcess(data): 
     142            form.data[self.key] = data 
     143 
     144        def _errProcess(failure): 
     145            failure.trap(validation.FieldError) 
     146            e = failure.value 
     147 
    143148            if e.fieldName is None: 
    144149                e.fieldName = self.key 
    145150            errors.add(e) 
    146  
     151             
     152        d = defer.maybeDeferred(widget.processInput, ctx, self.key, args) 
     153        d.addCallbacks(_cbProcess, _errProcess) 
     154        return d 
    147155 
    148156 
     
    409417        self.errors.data = args 
    410418 
     419        def _cbProcessingDone(_): 
     420            if self.errors and validate: 
     421                return self.errors 
     422 
     423            def _clearUpResources( r ): 
     424                if not self.errors: 
     425                    self.resourceManager.clearUpResources() 
     426                return r 
     427 
     428            d = defer.maybeDeferred(callback, ctx, self, self.data) 
     429            d.addCallback( _clearUpResources ) 
     430            d.addErrback(self._cbFormProcessingFailed, ctx) 
     431            return d 
     432 
    411433        # Iterate the items and collect the form data and/or errors. 
     434        dl = [] 
    412435        for item in self.items: 
    413             item.process(ctx, self, args, self.errors) 
    414  
    415         if self.errors and validate: 
    416             return self.errors 
    417  
    418         def _clearUpResources( r ): 
    419             if not self.errors: 
    420                 self.resourceManager.clearUpResources() 
    421             return r 
    422  
    423         d = defer.maybeDeferred(callback, ctx, self, self.data) 
    424         d.addCallback( _clearUpResources ) 
    425         d.addErrback(self._cbFormProcessingFailed, ctx) 
     436            dl.append(defer.maybeDeferred(item.process, ctx, self, args, self.errors)) 
     437 
     438        d = defer.gatherResults(dl) 
     439        d.addCallback(_cbProcessingDone) 
    426440        return d 
    427441 
  • trunk/formal/test/test_form.py

    r196 r298  
    11from twisted.trial import unittest 
     2from nevow import testutil 
     3from nevow import context 
     4 
    25import formal 
    3  
     6from formal.validation import FieldRequiredError 
    47 
    58class TestForm(unittest.TestCase): 
     
    1013        self.assertRaises(ValueError, form.addField, 'spaceAtTheEnd ', formal.String()) 
    1114        self.assertRaises(ValueError, form.addField, 'got a space in it', formal.String()) 
     15 
     16    def test_process(self): 
     17        form = formal.Form() 
     18        request = testutil.FakeRequest(args={'foo': ['bar', ]}) 
     19        ctx = context.RequestContext(tag=request) 
     20        form.addField('foo', formal.String()) 
     21        form.addAction(lambda *a, **kw: None) 
     22        d = form.process(ctx) 
     23        d.addCallback(self.failUnlessEqual, None) 
     24 
     25        def done(_): 
     26            self.failUnlessEqual(form.data['foo'], 'bar') 
     27 
     28        d.addCallback(done) 
     29        return d 
     30 
     31    def test_processError(self): 
     32        form = formal.Form() 
     33        request = testutil.FakeRequest() 
     34        ctx = context.RequestContext(tag=request) 
     35        form.addField('foo', formal.String(required=True)) 
     36        form.addAction(lambda *a, **kw: None) 
     37        d = form.process(ctx) 
     38 
     39        def done(errors): 
     40            self.failIfEqual(errors, None) 
     41            self.failUnless(isinstance(errors.getFieldError('foo'), FieldRequiredError)) 
     42 
     43        d.addCallbacks(done) 
     44        return d 
     45 
  • trunk/formal/test/test_types.py

    r196 r298  
    11from datetime import date, time 
    22try: 
    3     import decimal 
     3    from decimal import Decimal 
    44    haveDecimal = True 
    55except ImportError: 
    66    haveDecimal = False 
     7from twisted.internet import defer 
    78from twisted.trial import unittest 
    89import formal 
     
    3940class TestValidate(unittest.TestCase): 
    4041 
    41     def testString(self): 
    42         self.assertEquals(formal.String().validate(None), None) 
    43         self.assertEquals(formal.String().validate(''), None) 
    44         self.assertEquals(formal.String().validate(' '), ' ') 
    45         self.assertEquals(formal.String().validate('foo'), 'foo') 
    46         self.assertEquals(formal.String().validate(u'foo'), u'foo') 
    47         self.assertEquals(formal.String(strip=True).validate(' '), None) 
    48         self.assertEquals(formal.String(strip=True).validate(' foo '), 'foo') 
    49         self.assertEquals(formal.String(missing='bar').validate('foo'), 'foo') 
    50         self.assertEquals(formal.String(missing='bar').validate(''), 'bar') 
    51         self.assertEquals(formal.String(strip=True, missing='').validate(' '), '') 
    52         self.assertEquals(formal.String(missing='foo').validate('bar'), 'bar') 
    53         self.assertRaises(formal.FieldValidationError, formal.String(required=True).validate, '') 
    54         self.assertRaises(formal.FieldValidationError, formal.String(required=True).validate, None) 
    55  
    56     def testInteger(self): 
    57         self.assertEquals(formal.Integer().validate(None), None) 
    58         self.assertEquals(formal.Integer().validate(0), 0) 
    59         self.assertEquals(formal.Integer().validate(1), 1) 
    60         self.assertEquals(formal.Integer().validate(-1), -1) 
    61         self.assertEquals(formal.Integer(missing=1).validate(None), 1) 
    62         self.assertEquals(formal.Integer(missing=1).validate(2), 2) 
    63         self.assertRaises(formal.FieldValidationError, formal.Integer(required=True).validate, None) 
    64  
    65     def testFloat(self): 
    66         self.assertEquals(formal.Float().validate(None), None) 
    67         self.assertEquals(formal.Float().validate(0), 0.0) 
    68         self.assertEquals(formal.Float().validate(0.0), 0.0) 
    69         self.assertEquals(formal.Float().validate(.1), 0.1) 
    70         self.assertEquals(formal.Float().validate(1), 1.0) 
    71         self.assertEquals(formal.Float().validate(-1), -1.0) 
    72         self.assertEquals(formal.Float().validate(-1.86), -1.86) 
    73         self.assertEquals(formal.Float(missing=1.0).validate(None), 1.0) 
    74         self.assertEquals(formal.Float(missing=1.0).validate(2.0), 2.0) 
    75         self.assertRaises(formal.FieldValidationError, formal.Float(required=True).validate, None) 
     42 
     43    @defer.deferredGenerator 
     44    def runSuccessTests(self, type, tests): 
     45        for test in tests: 
     46            d = type(*test[0], **test[1]).validate(test[2]) 
     47            d = defer.waitForDeferred(d) 
     48            yield d 
     49            self.assertEquals(d.getResult(), test[3]) 
     50 
     51 
     52    @defer.deferredGenerator 
     53    def runFailureTests(self, type, tests): 
     54        for test in tests: 
     55            d = type(*test[0], **test[1]).validate(test[2]) 
     56            d = defer.waitForDeferred(d) 
     57            yield d 
     58            self.assertRaises(test[3], d.getResult) 
     59 
     60 
     61    def testStringSuccess(self): 
     62        return self.runSuccessTests(formal.String, [ 
     63                ([], {}, None, None), 
     64                ([], {}, '', None), 
     65                ([], {}, ' ',  ' '), 
     66                ([], {},  'foo', 'foo'), 
     67                ([], {}, u'foo', u'foo'), 
     68                ([], {'strip': True}, ' ', None), 
     69                ([], {'strip': True}, ' foo ', 'foo'), 
     70                ([], {'missing': 'bar'}, 'foo', 'foo'), 
     71                ([], {'missing': 'bar'}, '', 'bar'), 
     72                ([], {'strip': True, 'missing': ''}, ' ', ''), 
     73                ]) 
     74 
     75 
     76    def testStringFailure(self): 
     77        return self.runFailureTests(formal.String, [ 
     78            ([], {'required': True}, '', formal.FieldValidationError), 
     79            ([], {'required': True}, None, formal.FieldValidationError), 
     80            ]) 
     81 
     82 
     83    def testIntegerSuccess(self): 
     84        return self.runSuccessTests(formal.Integer, [ 
     85                ([], {}, None, None), 
     86                ([], {}, 0, 0), 
     87                ([], {}, 1, 1), 
     88                ([], {}, -1, -1), 
     89                ([], {'missing': 1}, None, 1), 
     90                ([], {'missing': 1}, 2, 2), 
     91                ]) 
     92 
     93 
     94    def testIntegerFailure(self): 
     95        return self.runFailureTests(formal.Integer, [ 
     96            ([], {'required': True}, None, formal.FieldValidationError), 
     97            ]) 
     98 
     99 
     100    def testFloatSuccess(self): 
     101        self.runSuccessTests(formal.Float, [ 
     102            ([], {}, None, None), 
     103            ([], {}, 0, 0.0), 
     104            ([], {}, 0.0, 0.0), 
     105            ([], {}, .1, .1), 
     106            ([], {}, 1, 1.0), 
     107            ([], {}, -1, -1.0), 
     108            ([], {}, -1.86, -1.86), 
     109            ([], {'missing': 1.0}, None, 1.0), 
     110            ([], {'missing': 1.0}, 2.0, 2.0), 
     111            ]) 
     112 
     113     
     114    def testFloatFailure(self): 
     115        self.runFailureTests(formal.Float, [ 
     116            ([], {'required': True}, None, formal.FieldValidationError), 
     117            ]) 
     118 
    76119 
    77120    if haveDecimal: 
    78         def testDecimal(self): 
    79             from decimal import Decimal 
    80             self.assertEquals(formal.Decimal().validate(None), None) 
    81             self.assertEquals(formal.Decimal().validate(Decimal('0')), Decimal('0')) 
    82             self.assertEquals(formal.Decimal().validate(Decimal('0.0')), Decimal('0.0')) 
    83             self.assertEquals(formal.Decimal().validate(Decimal('.1')), Decimal('0.1')) 
    84             self.assertEquals(formal.Decimal().validate(Decimal('1')), Decimal('1')) 
    85             self.assertEquals(formal.Decimal().validate(Decimal('-1')), Decimal('-1')) 
    86             self.assertEquals(formal.Decimal().validate(Decimal('-1.86')), 
    87                     Decimal('-1.86')) 
    88             self.assertEquals(formal.Decimal(missing=Decimal("1.0")).validate(None), 
    89                     Decimal("1.0")) 
    90             self.assertEquals(formal.Decimal(missing=Decimal("1.0")).validate(Decimal("2.0")), 
    91                     Decimal("2.0")) 
    92             self.assertRaises(formal.FieldValidationError, formal.Decimal(required=True).validate, None) 
    93  
    94     def testBoolean(self): 
    95         self.assertEquals(formal.Boolean().validate(None), None) 
    96         self.assertEquals(formal.Boolean().validate(True), True) 
    97         self.assertEquals(formal.Boolean().validate(False), False) 
    98         self.assertEquals(formal.Boolean(missing=True).validate(None), True) 
    99         self.assertEquals(formal.Boolean(missing=True).validate(False), False) 
    100  
    101     def testDate(self): 
    102         self.assertEquals(formal.Date().validate(None), None) 
    103         self.assertEquals(formal.Date().validate(date(2005,1,1)), date(2005,1,1)) 
    104         self.assertEquals(formal.Date(missing=date(2005,1,2)).validate(None), date(2005,1,2)) 
    105         self.assertEquals(formal.Date(missing=date(2005,1,2)).validate(date(2005,1,1)), date(2005,1,1)) 
    106         self.assertRaises(formal.FieldValidationError, formal.Date(required=True).validate, None) 
    107  
    108     def testTime(self): 
    109         self.assertEquals(formal.Time().validate(None), None) 
    110         self.assertEquals(formal.Time().validate(time(12,30,30)), time(12,30,30)) 
    111         self.assertEquals(formal.Time(missing=time(12,30,30)).validate(None), time(12,30,30)) 
    112         self.assertEquals(formal.Time(missing=time(12,30,30)).validate(time(12,30,31)), time(12,30,31)) 
    113         self.assertRaises(formal.FieldValidationError, formal.Time(required=True).validate, None) 
    114  
    115     def test_sequence(self): 
    116         self.assertEquals(formal.Sequence(formal.String()).validate(None), None) 
    117         self.assertEquals(formal.Sequence(formal.String()).validate(['foo']), ['foo']) 
    118         self.assertEquals(formal.Sequence(formal.String(), missing=['foo']).validate(None), ['foo']) 
    119         self.assertEquals(formal.Sequence(formal.String(), missing=['foo']).validate(['bar']), ['bar']) 
    120         self.assertRaises(formal.FieldValidationError, formal.Sequence(formal.String(), required=True).validate, None) 
    121         self.assertRaises(formal.FieldValidationError, formal.Sequence(formal.String(), required=True).validate, []) 
     121 
     122        def testDecimalSuccess(self): 
     123            return self.runSuccessTests(formal.Decimal, [ 
     124                ([], {}, None, None), 
     125                ([], {}, Decimal('0'), Decimal('0')), 
     126                ([], {}, Decimal('0.0'), Decimal('0.0')), 
     127                ([], {}, Decimal('.1'), Decimal('.1')), 
     128                ([], {}, Decimal('1'), Decimal('1')), 
     129                ([], {}, Decimal('-1'), Decimal('-1')), 
     130                ([], {}, Decimal('-1.86'), Decimal('-1.86')), 
     131                ([], {'missing': Decimal('1.0')}, None, Decimal('1.0')), 
     132                ([], {'missing': Decimal('1.0')}, Decimal('2.0'), Decimal('2.0')), 
     133                ]) 
     134 
     135 
     136        def testDecimalFailure(self): 
     137            return self.runFailureTests(formal.Decimal, [ 
     138                ([], {'required': True}, None, formal.FieldValidationError), 
     139                ]) 
     140 
     141 
     142    def testBooleanSuccess(self): 
     143        return self.runSuccessTests(formal.Boolean, [ 
     144            ([], {}, None, None), 
     145            ([], {}, True, True), 
     146            ([], {}, False, False), 
     147            ([], {'missing' :True}, None, True), 
     148            ([], {'missing': True}, False, False) 
     149            ]) 
     150 
     151 
     152    def testDateSuccess(self): 
     153        return self.runSuccessTests(formal.Date, [ 
     154            ([], {}, None, None), 
     155            ([], {}, date(2005, 1, 1), date(2005, 1, 1)), 
     156            ([], {'missing': date(2005, 1, 2)}, None, date(2005, 1, 2)), 
     157            ([], {'missing': date(2005, 1, 2)}, date(2005, 1, 1), date(2005, 1, 1)), 
     158            ]) 
     159 
     160 
     161    def testDateFailure(self): 
     162        return self.runFailureTests(formal.Date, [ 
     163            ([], {'required': True}, None, formal.FieldValidationError), 
     164            ]) 
     165 
     166 
     167    def testTimeSuccess(self): 
     168        self.runSuccessTests(formal.Time, [ 
     169            ([], {}, None, None), 
     170            ([], {}, time(12, 30, 30), time(12, 30, 30)), 
     171            ([], {'missing': time(12, 30, 30)}, None, time(12, 30, 30)), 
     172            ([], {'missing': time(12, 30, 30)}, time(12, 30, 31), time(12, 30, 31)), 
     173            ]) 
     174 
     175 
     176    def testTimeFailure(self): 
     177        self.runFailureTests(formal.Time, [ 
     178            ([], {'required': True}, None, formal.FieldValidationError), 
     179            ]) 
     180 
     181 
     182    def testSequenceSuccess(self): 
     183        self.runSuccessTests(formal.Sequence, [ 
     184            ([formal.String()], {}, None, None), 
     185            ([formal.String()], {}, ['foo'], ['foo']), 
     186            ([formal.String()], {'missing': ['foo']}, None, ['foo']), 
     187            ([formal.String()], {'missing': ['foo']}, ['bar'], ['bar']), 
     188            ]) 
     189 
     190 
     191    def testSequenceFailure(self): 
     192        self.runFailureTests(formal.Sequence, [ 
     193            ([formal.String()], {'required': True}, None, formal.FieldValidationError), 
     194            ([formal.String()], {'required': True}, [], formal.FieldValidationError), 
     195            ]) 
    122196 
    123197    def test_file(self): 
  • trunk/formal/types.py

    r289 r298  
    99    haveDecimal = False 
    1010from zope.interface import implements 
     11from twisted.internet import defer 
    1112 
    1213from formal import iformal, validation 
     
    4344 
    4445    def validate(self, value): 
     46        dl = [] 
    4547        for validator in self.validators: 
    46             validator.validate(self, value) 
    47         if value is None: 
    48             value = self.missing 
    49         return value 
     48            dl.append(defer.maybeDeferred(validator.validate, self, value)) 
     49 
     50        def _cbValidate(_, value): 
     51            if value is None: 
     52                value = self.missing 
     53            return value 
     54 
     55        def _err(failure): 
     56            failure.trap(defer.FirstError) 
     57            return failure.value.subFailure 
     58 
     59        d = defer.DeferredList(dl, consumeErrors=True, fireOnOneErrback=True) 
     60        d.addCallbacks(_cbValidate, _err, [value]) 
     61        return d 
    5062 
    5163    def hasValidator(self, validatorType): 
  • trunk/setup.py

    r294 r298  
    33setup( 
    44    name='formal', 
    5     version='0.15.4', 
     5    version='0.16.0', 
    66    description='HTML forms framework for Nevow', 
    77    author='Matt Goodall',