OpenSecurity/install/web.py-0.37/build/lib/web/form.py
author om
Mon, 02 Dec 2013 14:02:05 +0100
changeset 3 65432e6c6042
permissions -rwxr-xr-x
initial deployment and project layout commit
     1 """
     2 HTML forms
     3 (part of web.py)
     4 """
     5 
     6 import copy, re
     7 import webapi as web
     8 import utils, net
     9 
    10 def attrget(obj, attr, value=None):
    11     try:
    12         if hasattr(obj, 'has_key') and obj.has_key(attr): 
    13             return obj[attr]
    14     except TypeError:
    15         # Handle the case where has_key takes different number of arguments.
    16         # This is the case with Model objects on appengine. See #134
    17         pass
    18     if hasattr(obj, attr):
    19         return getattr(obj, attr)
    20     return value
    21 
    22 class Form(object):
    23     r"""
    24     HTML form.
    25     
    26         >>> f = Form(Textbox("x"))
    27         >>> f.render()
    28         u'<table>\n    <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>'
    29     """
    30     def __init__(self, *inputs, **kw):
    31         self.inputs = inputs
    32         self.valid = True
    33         self.note = None
    34         self.validators = kw.pop('validators', [])
    35 
    36     def __call__(self, x=None):
    37         o = copy.deepcopy(self)
    38         if x: o.validates(x)
    39         return o
    40     
    41     def render(self):
    42         out = ''
    43         out += self.rendernote(self.note)
    44         out += '<table>\n'
    45         
    46         for i in self.inputs:
    47             html = utils.safeunicode(i.pre) + i.render() + self.rendernote(i.note) + utils.safeunicode(i.post)
    48             if i.is_hidden():
    49                 out += '    <tr style="display: none;"><th></th><td>%s</td></tr>\n' % (html)
    50             else:
    51                 out += '    <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (i.id, net.websafe(i.description), html)
    52         out += "</table>"
    53         return out
    54         
    55     def render_css(self): 
    56         out = [] 
    57         out.append(self.rendernote(self.note)) 
    58         for i in self.inputs:
    59             if not i.is_hidden():
    60                 out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description))) 
    61             out.append(i.pre)
    62             out.append(i.render()) 
    63             out.append(self.rendernote(i.note))
    64             out.append(i.post) 
    65             out.append('\n')
    66         return ''.join(out) 
    67         
    68     def rendernote(self, note):
    69         if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
    70         else: return ""
    71     
    72     def validates(self, source=None, _validate=True, **kw):
    73         source = source or kw or web.input()
    74         out = True
    75         for i in self.inputs:
    76             v = attrget(source, i.name)
    77             if _validate:
    78                 out = i.validate(v) and out
    79             else:
    80                 i.set_value(v)
    81         if _validate:
    82             out = out and self._validate(source)
    83             self.valid = out
    84         return out
    85 
    86     def _validate(self, value):
    87         self.value = value
    88         for v in self.validators:
    89             if not v.valid(value):
    90                 self.note = v.msg
    91                 return False
    92         return True
    93 
    94     def fill(self, source=None, **kw):
    95         return self.validates(source, _validate=False, **kw)
    96     
    97     def __getitem__(self, i):
    98         for x in self.inputs:
    99             if x.name == i: return x
   100         raise KeyError, i
   101 
   102     def __getattr__(self, name):
   103         # don't interfere with deepcopy
   104         inputs = self.__dict__.get('inputs') or []
   105         for x in inputs:
   106             if x.name == name: return x
   107         raise AttributeError, name
   108     
   109     def get(self, i, default=None):
   110         try:
   111             return self[i]
   112         except KeyError:
   113             return default
   114             
   115     def _get_d(self): #@@ should really be form.attr, no?
   116         return utils.storage([(i.name, i.get_value()) for i in self.inputs])
   117     d = property(_get_d)
   118 
   119 class Input(object):
   120     def __init__(self, name, *validators, **attrs):
   121         self.name = name
   122         self.validators = validators
   123         self.attrs = attrs = AttributeList(attrs)
   124         
   125         self.description = attrs.pop('description', name)
   126         self.value = attrs.pop('value', None)
   127         self.pre = attrs.pop('pre', "")
   128         self.post = attrs.pop('post', "")
   129         self.note = None
   130         
   131         self.id = attrs.setdefault('id', self.get_default_id())
   132         
   133         if 'class_' in attrs:
   134             attrs['class'] = attrs['class_']
   135             del attrs['class_']
   136         
   137     def is_hidden(self):
   138         return False
   139         
   140     def get_type(self):
   141         raise NotImplementedError
   142         
   143     def get_default_id(self):
   144         return self.name
   145 
   146     def validate(self, value):
   147         self.set_value(value)
   148 
   149         for v in self.validators:
   150             if not v.valid(value):
   151                 self.note = v.msg
   152                 return False
   153         return True
   154 
   155     def set_value(self, value):
   156         self.value = value
   157 
   158     def get_value(self):
   159         return self.value
   160 
   161     def render(self):
   162         attrs = self.attrs.copy()
   163         attrs['type'] = self.get_type()
   164         if self.value is not None:
   165             attrs['value'] = self.value
   166         attrs['name'] = self.name
   167         return '<input %s/>' % attrs
   168 
   169     def rendernote(self, note):
   170         if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
   171         else: return ""
   172         
   173     def addatts(self):
   174         # add leading space for backward-compatibility
   175         return " " + str(self.attrs)
   176 
   177 class AttributeList(dict):
   178     """List of atributes of input.
   179     
   180     >>> a = AttributeList(type='text', name='x', value=20)
   181     >>> a
   182     <attrs: 'type="text" name="x" value="20"'>
   183     """
   184     def copy(self):
   185         return AttributeList(self)
   186         
   187     def __str__(self):
   188         return " ".join(['%s="%s"' % (k, net.websafe(v)) for k, v in self.items()])
   189         
   190     def __repr__(self):
   191         return '<attrs: %s>' % repr(str(self))
   192 
   193 class Textbox(Input):
   194     """Textbox input.
   195     
   196         >>> Textbox(name='foo', value='bar').render()
   197         u'<input type="text" id="foo" value="bar" name="foo"/>'
   198         >>> Textbox(name='foo', value=0).render()
   199         u'<input type="text" id="foo" value="0" name="foo"/>'
   200     """        
   201     def get_type(self):
   202         return 'text'
   203 
   204 class Password(Input):
   205     """Password input.
   206 
   207         >>> Password(name='password', value='secret').render()
   208         u'<input type="password" id="password" value="secret" name="password"/>'
   209     """
   210     
   211     def get_type(self):
   212         return 'password'
   213 
   214 class Textarea(Input):
   215     """Textarea input.
   216     
   217         >>> Textarea(name='foo', value='bar').render()
   218         u'<textarea id="foo" name="foo">bar</textarea>'
   219     """
   220     def render(self):
   221         attrs = self.attrs.copy()
   222         attrs['name'] = self.name
   223         value = net.websafe(self.value or '')
   224         return '<textarea %s>%s</textarea>' % (attrs, value)
   225 
   226 class Dropdown(Input):
   227     r"""Dropdown/select input.
   228     
   229         >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render()
   230         u'<select id="foo" name="foo">\n  <option value="a">a</option>\n  <option selected="selected" value="b">b</option>\n  <option value="c">c</option>\n</select>\n'
   231         >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render()
   232         u'<select id="foo" name="foo">\n  <option value="a">aa</option>\n  <option selected="selected" value="b">bb</option>\n  <option value="c">cc</option>\n</select>\n'
   233     """
   234     def __init__(self, name, args, *validators, **attrs):
   235         self.args = args
   236         super(Dropdown, self).__init__(name, *validators, **attrs)
   237 
   238     def render(self):
   239         attrs = self.attrs.copy()
   240         attrs['name'] = self.name
   241         
   242         x = '<select %s>\n' % attrs
   243         
   244         for arg in self.args:
   245             x += self._render_option(arg)
   246 
   247         x += '</select>\n'
   248         return x
   249 
   250     def _render_option(self, arg, indent='  '):
   251         if isinstance(arg, (tuple, list)):
   252             value, desc= arg
   253         else:
   254             value, desc = arg, arg 
   255 
   256         if self.value == value or (isinstance(self.value, list) and value in self.value):
   257             select_p = ' selected="selected"'
   258         else:
   259             select_p = ''
   260         return indent + '<option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc))
   261         
   262 
   263 class GroupedDropdown(Dropdown):
   264     r"""Grouped Dropdown/select input.
   265     
   266         >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render()
   267         u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish Cars">\n    <option value="Volvo">Volvo</option>\n    <option value="Saab">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    <option value="Mercedes">Mercedes</option>\n    <option selected="selected" value="Audi">Audi</option>\n  </optgroup>\n</select>\n'
   268         >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render()
   269         u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish Cars">\n    <option value="v">Volvo</option>\n    <option value="s">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    <option value="m">Mercedes</option>\n    <option selected="selected" value="a">Audi</option>\n  </optgroup>\n</select>\n'
   270 
   271     """
   272     def __init__(self, name, args, *validators, **attrs):
   273         self.args = args
   274         super(Dropdown, self).__init__(name, *validators, **attrs)
   275 
   276     def render(self):
   277         attrs = self.attrs.copy()
   278         attrs['name'] = self.name
   279         
   280         x = '<select %s>\n' % attrs
   281         
   282         for label, options in self.args:
   283             x += '  <optgroup label="%s">\n' % net.websafe(label)
   284             for arg in options:
   285                 x += self._render_option(arg, indent = '    ')
   286             x +=  '  </optgroup>\n'
   287             
   288         x += '</select>\n'
   289         return x
   290 
   291 class Radio(Input):
   292     def __init__(self, name, args, *validators, **attrs):
   293         self.args = args
   294         super(Radio, self).__init__(name, *validators, **attrs)
   295 
   296     def render(self):
   297         x = '<span>'
   298         for arg in self.args:
   299             if isinstance(arg, (tuple, list)):
   300                 value, desc= arg
   301             else:
   302                 value, desc = arg, arg 
   303             attrs = self.attrs.copy()
   304             attrs['name'] = self.name
   305             attrs['type'] = 'radio'
   306             attrs['value'] = value
   307             if self.value == value:
   308                 attrs['checked'] = 'checked'
   309             x += '<input %s/> %s' % (attrs, net.websafe(desc))
   310         x += '</span>'
   311         return x
   312 
   313 class Checkbox(Input):
   314     """Checkbox input.
   315 
   316     >>> Checkbox('foo', value='bar', checked=True).render()
   317     u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
   318     >>> Checkbox('foo', value='bar').render()
   319     u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
   320     >>> c = Checkbox('foo', value='bar')
   321     >>> c.validate('on')
   322     True
   323     >>> c.render()
   324     u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
   325     """
   326     def __init__(self, name, *validators, **attrs):
   327         self.checked = attrs.pop('checked', False)
   328         Input.__init__(self, name, *validators, **attrs)
   329         
   330     def get_default_id(self):
   331         value = utils.safestr(self.value or "")
   332         return self.name + '_' + value.replace(' ', '_')
   333 
   334     def render(self):
   335         attrs = self.attrs.copy()
   336         attrs['type'] = 'checkbox'
   337         attrs['name'] = self.name
   338         attrs['value'] = self.value
   339 
   340         if self.checked:
   341             attrs['checked'] = 'checked'            
   342         return '<input %s/>' % attrs
   343 
   344     def set_value(self, value):
   345         self.checked = bool(value)
   346 
   347     def get_value(self):
   348         return self.checked
   349 
   350 class Button(Input):
   351     """HTML Button.
   352     
   353     >>> Button("save").render()
   354     u'<button id="save" name="save">save</button>'
   355     >>> Button("action", value="save", html="<b>Save Changes</b>").render()
   356     u'<button id="action" value="save" name="action"><b>Save Changes</b></button>'
   357     """
   358     def __init__(self, name, *validators, **attrs):
   359         super(Button, self).__init__(name, *validators, **attrs)
   360         self.description = ""
   361 
   362     def render(self):
   363         attrs = self.attrs.copy()
   364         attrs['name'] = self.name
   365         if self.value is not None:
   366             attrs['value'] = self.value
   367         html = attrs.pop('html', None) or net.websafe(self.name)
   368         return '<button %s>%s</button>' % (attrs, html)
   369 
   370 class Hidden(Input):
   371     """Hidden Input.
   372     
   373         >>> Hidden(name='foo', value='bar').render()
   374         u'<input type="hidden" id="foo" value="bar" name="foo"/>'
   375     """
   376     def is_hidden(self):
   377         return True
   378         
   379     def get_type(self):
   380         return 'hidden'
   381 
   382 class File(Input):
   383     """File input.
   384     
   385         >>> File(name='f').render()
   386         u'<input type="file" id="f" name="f"/>'
   387     """
   388     def get_type(self):
   389         return 'file'
   390     
   391 class Validator:
   392     def __deepcopy__(self, memo): return copy.copy(self)
   393     def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
   394     def valid(self, value): 
   395         try: return self.test(value)
   396         except: return False
   397 
   398 notnull = Validator("Required", bool)
   399 
   400 class regexp(Validator):
   401     def __init__(self, rexp, msg):
   402         self.rexp = re.compile(rexp)
   403         self.msg = msg
   404     
   405     def valid(self, value):
   406         return bool(self.rexp.match(value))
   407 
   408 if __name__ == "__main__":
   409     import doctest
   410     doctest.testmod()