CherryPy Project Download

FastFormward: forms.py

Line 
1 """
2 Cherry Forms - FastFormward
3
4 History:
5 0,3  @ 2005/05/07:
6     Remco:
7     - Support for fieldsets
8     - support for compound fields (proof is there)
9     - DateField implementation (look at how simple it is!)
10     - Changed a line or two in inline docs and samples
11 0,2  @ 2005/05/07:
12     Remco:
13     - Added submit button as SubmitField.
14     - Better support for submit buttons: they will not be displayed
15       when validating. Beware, if your submit button is part of your data entry
16       you might want to take good care of this :)
17     - Added support for form logic error msg: invalidForm should return
18       and error msg if the fields are not filled correctly. replaces validateForm
19     - the onExit function will no longer be called with the form data, but with
20       a dictionary where the key is the form name, and the value the pyValue(kwp)
21       for that field, with the value of kwp being extracted from the session
22       to avoid the entering new values hack.
23           
24 0.1  @ 2005/05/07:
25     Remco:
26     - gone public, still alfa, see wiki:
27       http://www.cherrypy.org/wiki/CherryForms
28
29 <not public>
30     Remco: first attempt at solving the problem
31     as described on the wiki page
32
33
34 Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
35 All rights reserved.
36
37 Redistribution and use in source and binary forms, with or without modification,
38 are permitted provided that the following conditions are met:
39
40     * Redistributions of source code must retain the above copyright notice,
41       this list of conditions and the following disclaimer.
42     * Redistributions in binary form must reproduce the above copyright notice,
43       this list of conditions and the following disclaimer in the documentation
44       and/or other materials provided with the distribution.
45     * Neither the name of the CherryPy Team nor the names of its contributors
46       may be used to endorse or promote products derived from this software
47       without specific prior written permission.
48
49 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
50 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
51 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
52 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
53 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
55 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
56 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
57 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
58 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59 """
60
61
62 __version__ = "0.1"
63 __authors__ = ['Remco Boerma: remco.boerma@gmail.com']
64 from cherrypy import cpg
65
66
67 ######################################################################
68 ##
69 ## ToSession and FromSession
70 ## from http://www.cherrypy.org/wiki/FromAndToSession
71 ##
72 ######################################################################
73
74 def toSession(**kwp):
75     """Stores the keyword parameters in the cpg.request.sessionMap.
76     like:
77        toSession(bla=bla) will store the variable bla as 'bla'
78     @returns: nothing.   
79     """
80     for k,v in kwp.items():
81         cpg.request.sessionMap[k] = v
82
83 def fromSession(*names):
84     """retreives the names from the cpg.request.sessionMap.
85     like:
86        bla,pir = fromSession('bla','pir') will load bla and pir
87     @returns: the values for the keys named in 'names'
88     @raises: nothing, None as value for a key that can't be found
89     """
90     values = []
91     for name in names:
92         try:
93             values.append(cpg.request.sessionMap.get(name,None))
94         except AttributeError:
95             values.append(None)
96     if len(values) ==1:
97             ## allow for easy element retreival
98             return values[0]
99     else:
100             return values
101
102
103
104 ######################################################################
105 ##
106 ## Fields
107 ##
108 ######################################################################
109
110 import new
111 class Field(object):
112     def __init__(self,
113                  type,          # type of input ('compound' for custom compound inputs)
114                  name,          # name of the input (or a list of names)
115                  label=None,    # the label to precede the input
116                  html=None,     # direct html entry
117                  id=None,       # the dom ID
118                  tip=None,      # tooltip, rendered probabely as title attribute
119                  style=None,    # any direct style input
120                  cssclass=None, # the CSS class
121                  helpmsg=None,  # any default help msg if input fails.
122                  validate=None, # specific validation method (made an instancemethod using module new)
123                  value=None,    # value if already known, can be anything, to support compound fields
124                  **extraAttrs): # support for unknown tags
125         """
126         Base Field type. Use derivatives..
127          type,          # type of input ('compound' for custom compound inputs)
128          name,          # name of the input (or a list of names)
129          label=None,    # the label to precede the input
130          html=None,     # direct html entry
131          id=None,       # the dom ID
132          tip=None,      # tooltip, rendered probabely as title attribute
133          style=None,    # any direct style input
134          cssclass=None, # the CSS class
135          helpmsg=None,  # any default help msg if input fails.
136          validate=None, # specific validation method (made an instancemethod using module new)
137          value=None,    # value if already known, can be anything, to support compound fields
138          **extraAttrs): # support for unknown tags
139         """       
140         assert type != None, 'Every input should be of _some_ type...'
141         if type != 'submit':
142             assert name != None, 'Every input should have _some_ name...'
143         self.type = type
144         self.name = name
145         if label:
146             self.label = label
147         if html:
148             self.html = html
149         if id:
150             self.id = id
151         else:
152             if name:
153                 self.id = "input."+name
154         if tip:
155             self.tip = tip
156         if style:
157             self.style = style
158         if cssclass:
159             self.cssclass = cssclass
160         if helpmsg:
161             self.helpmsg = helpmsg
162         if validate:
163             self.validate = new.instancemethod(validate,self)
164         if value:
165             self.value = value
166         self.extraAttrs = extraAttrs
167
168     def validate(self,form):
169         """
170         This means, that the Field will have to do it's own checking if there is a value
171         for it in the form.
172         Design decision: why not pass the corresponding value?
173             - well, that is an option. But when using multiple values using a compund
174               field, this can best be left to the specific Field.. """
175         return True
176        
177
178     def errorMsg(self,form):
179         "Override this: so you can write specific error msgs based on the data in form."
180         return self.helpmsg
181
182     def build(self,form,readonly=False):
183         """Called to build the html for this field, for the specified form.
184         Should return a string.
185         If readonly: the html should be rendered as readonly on the client side"""
186         if hasattr(self,'_html'):
187             return self._html
188         if form == None:
189             raise ValueError, 'Form cannot be None if no ._html was given for %r' % self
190         def attr(name,varname):
191             if not hasattr(self,varname):
192                 return ''
193             else:
194                 # test if the value is None,
195                 # if so, it's not shown
196                 value = getattr(self,varname)
197                 if value == None:
198                     return ''
199                 else:
200                     return '%s="%s"' % (name,value)
201         result = []
202         if hasattr(self,'label'):
203             result.append('<label %s >%s</label>' % (attr('for','id'), self.label))
204         result.append("<input %s %s %s %s %s %s " % (attr('type','type'),
205                                         attr('name','name'),
206                                         attr('id','id'),
207                                         attr('class','cssclass'),
208                                         attr('title','tip'),
209                                         attr('style','style')))
210         if form.keys() and not self.validate(form):
211             # if the input is invalid
212             # add the erroreous attribute
213             # so CSS can make it show a bit different
214             result.append(' erroreous ')
215             # an error msg could be put in the value...
216             result.append('value="%s:%s"' % (self.errorMsg(form),form.get(self.name,'')))
217         else:
218             result.append('value="%s"' % (form.get(self.name,'')))
219         # add extra key="value" pairs
220         result.extend(['%s="%s" ' % (pair) for pair in self.extraAttrs.items() if pair[1] != None])
221         # add extra key fields (like multiple etc.)
222         result.extend(['%s ' % key for key, value in self.extraAttrs.items() if value == None])
223         if readonly:
224             result.append(' readonly ')
225         result.append('>')
226         return "".join(result)
227            
228     def getHtml(self,form={}):
229         "Property get method for html. the form parameter is the dictionary with form parameters"
230         return self.build(form)
231
232     def _setHtml(self,html):
233         self._html = html
234
235     def _delHtml(self):
236         if hasattr(self,'_html'):
237             del self._html
238
239     html = property(getHtml,_setHtml,_delHtml,doc="Generated or static html for input..")
240     def pyValue(self,form):
241         "Override this: return the python variant of this input based on the form dictionary."
242         return None
243
244
245 class SubmitField(Field):
246     def __init__(self,name=None,label=None,value='Submit',**kwp):
247         "Create an integer entry"
248         super(SubmitField,self).__init__('submit',name,label,value=value,**kwp)
249
250     def validate(self,form):
251         return True
252     def build(self,kwp,readonly=False):
253         html = super(SubmitField,self).build(kwp,readonly)
254         # replace the value attribute, as it needs to be filled. .
255         # but as the self.name is not a key in the kwp, the Field.build
256         # method will not fill the value..
257         return html.replace('value=""','value="%s"' % self.value)
258
259     def pyValue(self,form):
260         "returns true if this button was used.."
261         keyAvailable = form.has_key(self.name)
262         if not keyAvailable:
263             return False
264         if getattr(self,'value',None):
265             return form[self.name] == self.value
266         else:
267             return keyAvailable
268
269 class IntField(Field):
270     def __init__(self,name,label,**kwp):
271         "Create an integer entry"
272         if 'helpmsg' not in kwp:
273             kwp['helpmsg'] = 'Enter digits only'
274         super(IntField,self).__init__('text',name,label,**kwp)
275
276     def validate(self,form):
277         value = form.get(self.name,'').strip()
278         return bool(len(value) and value.isdigit())
279
280     def pyValue(self,form):
281         if self.validate(form):
282             return int(form[self.name])
283         else:
284             return None
285
286 class SelectField(Field):
287     # UNTESTED, in progress
288     def __init__(self,name,label,multiple=False,tip=None,style=None,cssclass=None, values = None, **extraAttrs):
289         "Create a select (drop) list field. Values is a list of (value,label) pairs"
290         self.multiple = multiple
291         if multiple:
292             extraAttrs['multiple']=None
293         super(Select,self).__init__('text',name,label,
294                                       tip,style,cssclass
295                                       **extraAttrs
296                                       )
297         self.options = value
298
299     def build(self,form,readonly=False):
300         # get the value from there
301         val = super(SelectField,self).build(form,readonly)
302         val.replace('<input','<select') # make it a select
303         # add options
304         val += "".join(['<option label="%s">%s</option>' % (label,value) for value, label in self.options])
305         # add closing /select
306         val +='</select>'
307        
308
309
310 class RadioField(Field):
311     def __init__(self,name,label,value='',**extraAttrs):
312         "Create a field. "
313         super(RadioField,self).__init__('radio',name,label,
314                                       **extraAttrs)
315         self.value = value
316
317     def build(self,form,readonly=False):
318         # get the value from there
319         val = super(RadioField,self).build({},readonly)
320         if form.get(self.name,None)==self.value:
321             checked = 'CHECKED'
322         else:
323             checked = ''
324         return val.replace('value=""','value="%s" %s' % (self.value, checked))
325
326     def pyValue(self,form):
327         return form.get(self.name,None)
328
329
330 class CheckField(Field):
331     def __init__(self,name,label,value='',**extraAttrs):
332         "Create a check field. "
333         super(CheckField,self).__init__('checkbox',name,label,
334                                       **extraAttrs)
335         self.value = value
336
337     def build(self,form,readonly=False):
338         # get the value from there
339         val = super(CheckField,self).build({},readonly)
340         if form.get(self.name,None)==self.value:
341             checked = 'CHECKED'
342         else:
343             checked = ''
344         return val.replace('value=""','value="%s" %s' % (self.value, checked))
345
346     def pyValue(self,form):
347         vals = form.get(self.name,None)
348         if type(vals) == type([]):
349             return self.value in vals
350         if type(vals) == type(""):
351             return vals == self.value
352         else:
353             return False
354
355 ######################################################################
356 ##
357 ## FieldSet
358 ##
359 ######################################################################
360
361 class FieldSet(Field):
362     """Although a fieldset is not the same as a field,
363     it's an easy grouping of functions. Easiest to use
364     would be if you could use a fieldset as a field.
365     Fieldset should be quite easily converted to a comound field
366     structure though.
367     """
368     def __init__(self,name,fields, label=None):
369         "Enter a name, a list of fields, and an optional label."
370         # for a compound field, you should initialise the
371         # Field.__init__of course..
372         self.name = name
373         self.fields = fields
374         self.label = label
375
376     def validate(self,form):
377         for field in self.fields:
378             if not field.validate(form):
379                 return False
380         return True
381
382     def build(self,kwp,readonly=False):
383         yield '<fieldset>'
384         if self.label:
385             yield '<legend>%s</legend>' % self.label
386         if kwp and not self.validate(kwp):
387             yield self.errorMsg(kwp)
388         for field in self.fields:
389             yield field.build(kwp,readonly)
390         yield '</fieldset>'
391
392     def pyValue(self,form):
393         "returns a dictionary, like given to a form.onExit method..."
394         return dict([(field.name[len(self.name)+1:], # strip the compundname. part
395                       field.pyValue(form))
396                      for field in self.fields])
397
398     def errorMsg(self,form):
399         # a fieldset can't have an error.. fields will have that..
400         return ''
401
402 ######################################################################
403 ##
404 ## CompoundField Base
405 ##
406 ######################################################################
407 class CompoundField(FieldSet):
408     """Although a fieldset is not the same as a field,
409     it's an easy grouping of functions. Easiest to use
410     would be if you could use a fieldset as a field.
411     Fieldset should be quite easily converted to a comound field
412     structure though.
413     """
414     def __init__(self,name,fields, label=None):
415         "Enter a name, a list of fields, and an optional label."
416         super(CompoundField,self).__init__(name,fields,label)
417         # for a compound field, you should initialise the
418         # Field.__init__of course..
419         self.name = name
420         self.fields = fields
421         self.label = label
422         # make the fields children of this compund
423         for field in fields:
424             field.name= name+'.'+field.name
425             if hasattr(field,'id'):
426                 field.id= name+'.'+field.id
427             else:
428                 field.id = 'input'+name+'.'+field.name
429
430 class DateField(CompoundField):
431     def __init__(self,name, label=None):
432         "Enter a name, a list of fields, and an optional label."
433         self.day = IntField("day",'Day')
434         self.month = IntField("month",'Month')
435         self.year = IntField("year",'Year')
436         super(DateField,self).__init__(name,[self.day, self.month, self.year],label)
437
438     def validate(self,kwp):
439         "validates the date, using time module"
440         try:
441             self.pyValue(kwp)
442         except:
443             return False
444         return True
445
446     def errorMsg(self,kwp):
447         try:
448             self.pyValue(kwp)
449         except Exception, e:
450             return str(e)
451         return ''
452    
453     def pyValue(self,kwp):
454         vals = super(DateField,self).pyValue(kwp)
455         import datetime
456         return datetime.date(**vals)
457        
458        
459 ######################################################################
460 ##
461 ## Form Base class
462 ##
463 ######################################################################
464
465 class Form(object):
466     """A form is a cherrypy page...
467     it has the following states:
468         X. ...nothingness...
469         A. New Entry
470         B. Users Enters Data
471         C. Validate Entered Data
472         D. Getting Confirmation From User
473         E. ReEntry Filled With Entered Values
474         F. ReEntry Filled With Entered Values and error message
475         G. Entry Completed
476     Transitions: (obvious activity between () )
477
478         X --> A : born (form is setup, run only once!!)
479
480         A --> B : (html is brewn) show form on screen
481
482         B --> C : users posts results
483
484         C --> D : input is valid, but a user confirmation was set to be asked,
485                   (brew confirmation html)
486
487         C --> G : input is valid, but no user confirmation was set to be asked,
488
489         C --> F : input is not valid
490                   (brew html with data and error msgs)
491
492         F --> B : user posts new results
493
494         E --> B : user posts new results
495
496         G --> X : (fire another (external) method to build new screen)
497
498     The form entry should all be done using 1 url, and preferable no hidden
499     form inputs to differentiate the states.. Otherwise it would be to easy
500     to hack those values.. So keeping the state purely serverside (and there-
501     for also not using different urls for the different states) allows for
502     less hackable data entry.
503
504     The concept used to solve this, is to change the check the session for
505     the state for this form, and switch to the right function accordingly.
506     """
507
508     def __init__(self,
509                  name,    # form name
510                  onExit,  # onExit method, will be called with a dictionary
511                           # where key,value = key of the fields, pyValue of the
512                           # form fields.
513                  fields,  # list of fields (order, _is_ important)
514                  confirm, # user should confirm after good entry, field type
515                           # with the question
516                  submit = None, # optional submit input type (can be a combined)
517                           # will be left out in readonly data
518                  invalidForm = None, # specify to overwrite self.invalidForm
519                  encoding = 'application/x-www-form-urlencoded' # form encoding,
520                           # to support files etc...
521                           # see http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.3
522                  ):
523         """
524          name,    # form name
525          onExit,  # onExit method, will be called with a dictionary
526                   # where key,value = key of the fields, pyValue of the
527                   # form fields.
528          fields,  # list of fields (order, _is_ important)
529          confirm, # user should confirm after good entry, field type
530                   # with the question
531          submit = None, # optional submit input type (can be a combined)
532                   # will be left out in readonly data
533          invalidForm = None, # specify to overwrite self.invalidForm
534          encoding = 'application/x-www-form-urlencoded' # form encoding,
535                   # to support files etc...
536                   # see http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.3
537         """
538         self.name = name
539         self.onExit = onExit
540         self.confirm  = confirm
541         self.fields = fields
542         self.submit = submit
543         if invalidForm:
544             self.invalidForm= invalidForm
545         self.transitionsFor= {
546             'X' : (('A',1),),
547             'A' : (('B',1),),
548             'B' : (('C',1),),
549             'C' : (('F',self.invalidEntry), # if this fails, entry is valid!!!
550                    ('D',lambda x:self.confirm), # if self.confirm evaluates to to: D is entered
551                    ('G',1)), # valid input and no confirmation requested
552             'D' : (('G',self.confirmationOk), # has the user confirmed the entered data?
553                    ('E',1)), # if not, this is the only other option
554             'E' : (('B',1),),
555             'F' : (('B',1),),
556             'G' : (('X',1),),
557             }
558         # TODO: support all other and/or unknown attributes, like onsubmit and onreset
559         # TODO: as soon as default is not the only method to get parameters,
560         self.formHeader = '<form method="post" action="." enctype="%s">' % encoding
561         self.formFooter = "</form>"
562        
563     def invalidEntry(self,kwp):
564         for field in self.fields:
565             if not field.validate(kwp):
566                 return True
567         formFailureMsg=self.invalidForm(kwp)
568         if formFailureMsg:
569             # if there is a failure
570             # store the msg in the session
571             toSession(**{self.name+'.formFailureMsg':formFailureMsg})
572             return True
573         else:
574             return False
575
576     def invalidForm(self,kwp):
577         """InValid Form? kwp is the dict with fields
578         is called when all fields have valid input
579         but there needs to be constraints checking
580         to see if the logic between the fields is okay.
581         return False if the fields are OKAY, or return
582         an error msg to tell the user what's wrong.
583         """
584         return False
585    
586     def confirmationOk(self,kwp):
587         """Does the self.confirm field validate, and is bool(self.confirm.pyValue(kwp) ) true?
588         Cause if it is, the user has confirmed the entered data.
589         """
590         return self.confirm.validate(kwp) and self.confirm.pyValue(kwp)
591
592     def index(self,**kwp):
593         "CP2 helper to map /foo/bar/ to /foo/bar/default"
594         return self.default(**kwp)
595     index.exposed= True
596     def __call__(self,**kwp):
597         "CP2 helper to map /foo/bar to /foo/bar/default"
598         return self.default(**kwp)
599     __call__.exposed= True
600
601     def default(self,**kwp):
602         "CP2 method to produce HTML. Will determine the next state and call the onEnter function for that state."
603         # from and to session: http://www.cherrypy.org/wiki/FromAndToSession
604         #           toSession: cpg.request.sessionMap[k] = v
605         state = fromSession('%s_state' % self.name)
606         if state == None:
607             state = 'X' # start with A, not with X
608         # get the tests and tostate for this state
609         for toState, test in self.transitionsFor[state]:
610             # search first possitive test
611             if (test in [1,True]) or test(kwp):
612                 break
613         #print '>> Moving from %s to %s' % (state,toState)
614         # always use the toState set in the loop above
615         # either it was the last tested, or it was
616         # succesfully marked using the break
617
618         # if there, run the onleave method
619         if hasattr(self,'%s_onLeave' % state):
620             getattr(self,'%s_onLeave' % state)(kwp)
621         if hasattr(self,'doTrans_%s%s' % (state,toState)):
622             getattr(self,'doTrans_%s%s' % (state,toState))(kwp)
623         # after the new transition
624         # we can store the new state
625         toSession(**{'%s_state' % self.name:toState})
626         # next we return html
627         # and the onEnter method is available for every state!
628         # it's compulsary
629         return getattr(self,'%s_onEnter' % toState)(kwp)
630     default.exposed = True
631        
632     def A_onEnter(self,kwp):
633         "yield entry HTML, kwp is bluntly passed onto the fields .build call."
634         yield self.formHeader
635         for field in self.fields:
636             # if kwp is empty, every field will
637             # return a blank fill in field.
638             yield field.build(kwp)
639         # yield the submit, not the confirm
640         if self.submit:
641             yield self.submit.build(kwp)           
642         yield self.formFooter
643
644     def B_onEnter(self,kwp):
645         """state B isn't a state where HTML is produced..
646         it's entered just before the validation starts
647         in state C
648         Stores the KWP in the session!"""
649         toSession(**{self.name+'.CherryFormsFormData' : kwp})
650         return self.default(**kwp)
651
652     def C_onEnter(self,kwp):
653         """Let's move on"""
654         return self.default(**kwp)
655
656     def D_onEnter(self,kwp):
657         """same as A_onEnter, but now calls the .build's with 'readonly=True' and with a confirmation input.
658         """
659         yield self.formHeader
660         for field in self.fields:
661             yield field.build(kwp, readonly=True)
662         # yield the confirm, not the submit
663         yield self.confirm.html
664         yield self.formFooter
665
666     def E_onEnter(self,kwp):
667         """in coding, this is the same as A_onEnter, so it uses that."""
668         return self.A_onEnter(kwp)
669
670     def F_onEnter(self,kwp):
671         """If there is an invalid value in kwp
672         the html with warning should be displayed, this is done i each
673         .build() issued.."""
674         yield self.formHeader
675         formMsg = fromSession(self.name+'.formFailureMsg')
676         if formMsg:
677             # there was an error:
678             yield formMsg
679             del cpg.request.sessionMap[self.name+'.formFailureMsg']
680         for field in self.fields:
681             # if kwp is empty, every field will
682             # return a blank fill in field.
683             yield field.build(kwp)
684         # yield the submit, not the confirm
685         if self.submit:
686             yield self.submit.build(kwp)           
687         yield self.formFooter
688            
689
690     def G_onEnter(self,kwp):
691         """Let's move on"""
692         return self.default(**kwp)
693
694     def X_onEnter(self,kwp):
695         """If we _enter_ this, it means we have reached valid and possibly confirmed input.
696         Therefore, the onExit method is called (and it's result returned!), with the kwp from A_onEnter...
697         """
698         # get the data the way it was entered. Not how it was sent with the [possible] validation
699         kwp = fromSession( self.name+'.CherryFormsFormData' )
700         # remove that data from the session. it's useless from this point on.
701         del cpg.request.sessionMap[self.name+'.CherryFormsFormData']
702         return self.onExit(dict([(field.name, field.pyValue(kwp))for field in self.fields]))
703    
704    
705 class Root:
706     def __init__(self):
707         def onExit(kwp):
708             yield 'terminated with kwp: ',`kwp`
709         import forms
710         self.d = forms.Form(name='birthday',
711                             onExit = onExit,
712                             fields = [forms.DateField('birthday','Please enter your birthday')],
713                             submit = forms.SubmitField('next','Next: ',value='Go!'),
714                             confirm = None)
715         # first form: simply enter an integer
716         # [mind you, this will also work without a submit button,
717         # so the submit button will leave from the output]
718         self.f = forms.Form(name='formpje',
719                             onExit = onExit,
720                             fields = [forms.IntField('intje','geef intje:')],
721                             confirm = None)
722         # same as self.f
723         # but now with a cssclass for the input, and a confirmation request
724         # after valid data has been read.
725         self.g = forms.Form(name='formpje_with confirmation',
726                             onExit = onExit,
727                             fields = [forms.IntField('intje','geef intje:',cssclass="veldje")],
728                             confirm = forms.SubmitField('confirmed','Confirmed?', value='Confirmed'))
729
730         # this will produce 3 integer fields.
731         # so:
732         #  * the entry of each field must be valid
733         # there is a veldConfirm to request confirmation
734         #  * the form uses confirmation after valid data
735         # checkMultiply is used
736         #  * we add logic to test if the input values for this
737         #    form are correct. So we can test a bit more, if the input
738         #    relates to certain 'form'-logic constraints.. this is
739         #    typically where you would want to validate the entry
740         #    with databases etc.
741         veldA = forms.IntField('A','A:',helpMsg='Enter a natural number.')
742         veldB = forms.IntField('B','B:', helpMsg='Enter a natural number.')
743         veldMul = forms.IntField('mult','multiplied', helpMsg='Enter A * B.')
744         veldConfirm = forms.SubmitField('confirmed','Confirmed?', value='Confirmed')
745         submit = forms.SubmitField(value='Submit')
746
747         # this will check if the fields A * B == C
748         # if not, form validation will fail (though every field validation might
749         # have returned succesfully) and the error msg returned will be displayed
750         # on screen. (not in any div or so, you should do that yourself..)
751         #
752         def checkmultiply(kwp):
753             if veldA.pyValue(kwp) * veldB.pyValue(kwp) != veldMul.pyValue(kwp):
754                 return "A * B should be equal to C"
755
756         ## here comes the form, available under /h/
757         self.h = forms.Form(name='formpje_with confirmation',
758                             onExit = onExit,
759                             fields = [veldA,veldB,veldMul],
760                             invalidForm = checkmultiply,
761                             submit = submit,
762                             confirm = veldConfirm)
763         # now, ain't that easy?       
764 def test():
765     import forms
766     class X:pass
767     cpg.request = X()
768     cpg.request.sessionMap = {}
769     i = forms.IntField('intje','geef intje:',helpmsg='Geef alleen een getal op...')
770     def onExit(kwp):
771         print 'got the keyword parameters. redirecting to other page.. '
772         print 'kwp:',`kwp`
773         print 'done'
774     f = forms.Form(name='formpje', onExit = onExit, submit=SubmitField(), fields = [i], confirm = None)
775     # entry form
776     print "".join(f.default())
777     # next we fill it with a value
778     print "".join(f.default(intje='WRONG!!'))
779     # now fill it with a correct value
780     print f.default(intje='333')
781     # now we redefine the form, to have a confirm value as well...
782     confirmField = forms.CheckField('confirmed','Confirmed?', value='OK')
783     g = forms.Form(name='formpje', onExit = onExit, fields = [i], confirm = confirmField)
784     # entry again
785     print "".join(g.default())
786     # now fill it with a correct value and get the confirmation request
787     print "".join(g.default(intje='333'))
788     # confirm by entering the confirmed value with OK and go to the next page
789     print g.default(intje='333', confirmed = 'OK')
790    
791
792
793
794 #cpg.root.wiki = Wiki()
795 if __name__=='__main__':
796     cpg.root = Root()
797     cpg.server.start(configMap = {'socketPort': 82,
798                                    'threadPool':1,
799                                    'socketQueueSize':1,
800                                    'sessionStorageType' :'ram'
801                                    }
802                      )

Hosted by WebFaction

Log in as guest/cherrypy to create/edit wiki pages