""" HTML forms (part of web.py) """ import copy, re import webapi as web import utils, net def attrget(obj, attr, value=None): try: if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr] except TypeError: # Handle the case where has_key takes different number of arguments. # This is the case with Model objects on appengine. See #134 pass if hasattr(obj, attr): return getattr(obj, attr) return value class Form(object): r""" HTML form. >>> f = Form(Textbox("x")) >>> f.render() u'\n \n
' """ def __init__(self, *inputs, **kw): self.inputs = inputs self.valid = True self.note = None self.validators = kw.pop('validators', []) def __call__(self, x=None): o = copy.deepcopy(self) if x: o.validates(x) return o def render(self): out = '' out += self.rendernote(self.note) out += '\n' for i in self.inputs: html = utils.safeunicode(i.pre) + i.render() + self.rendernote(i.note) + utils.safeunicode(i.post) if i.is_hidden(): out += ' \n' % (html) else: out += ' \n' % (i.id, net.websafe(i.description), html) out += "
%s
%s
" return out def render_css(self): out = [] out.append(self.rendernote(self.note)) for i in self.inputs: if not i.is_hidden(): out.append('' % (i.id, net.websafe(i.description))) out.append(i.pre) out.append(i.render()) out.append(self.rendernote(i.note)) out.append(i.post) out.append('\n') return ''.join(out) def rendernote(self, note): if note: return '%s' % net.websafe(note) else: return "" def validates(self, source=None, _validate=True, **kw): source = source or kw or web.input() out = True for i in self.inputs: v = attrget(source, i.name) if _validate: out = i.validate(v) and out else: i.set_value(v) if _validate: out = out and self._validate(source) self.valid = out return out def _validate(self, value): self.value = value for v in self.validators: if not v.valid(value): self.note = v.msg return False return True def fill(self, source=None, **kw): return self.validates(source, _validate=False, **kw) def __getitem__(self, i): for x in self.inputs: if x.name == i: return x raise KeyError, i def __getattr__(self, name): # don't interfere with deepcopy inputs = self.__dict__.get('inputs') or [] for x in inputs: if x.name == name: return x raise AttributeError, name def get(self, i, default=None): try: return self[i] except KeyError: return default def _get_d(self): #@@ should really be form.attr, no? return utils.storage([(i.name, i.get_value()) for i in self.inputs]) d = property(_get_d) class Input(object): def __init__(self, name, *validators, **attrs): self.name = name self.validators = validators self.attrs = attrs = AttributeList(attrs) self.description = attrs.pop('description', name) self.value = attrs.pop('value', None) self.pre = attrs.pop('pre', "") self.post = attrs.pop('post', "") self.note = None self.id = attrs.setdefault('id', self.get_default_id()) if 'class_' in attrs: attrs['class'] = attrs['class_'] del attrs['class_'] def is_hidden(self): return False def get_type(self): raise NotImplementedError def get_default_id(self): return self.name def validate(self, value): self.set_value(value) for v in self.validators: if not v.valid(value): self.note = v.msg return False return True def set_value(self, value): self.value = value def get_value(self): return self.value def render(self): attrs = self.attrs.copy() attrs['type'] = self.get_type() if self.value is not None: attrs['value'] = self.value attrs['name'] = self.name return '' % attrs def rendernote(self, note): if note: return '%s' % net.websafe(note) else: return "" def addatts(self): # add leading space for backward-compatibility return " " + str(self.attrs) class AttributeList(dict): """List of atributes of input. >>> a = AttributeList(type='text', name='x', value=20) >>> a """ def copy(self): return AttributeList(self) def __str__(self): return " ".join(['%s="%s"' % (k, net.websafe(v)) for k, v in self.items()]) def __repr__(self): return '' % repr(str(self)) class Textbox(Input): """Textbox input. >>> Textbox(name='foo', value='bar').render() u'' >>> Textbox(name='foo', value=0).render() u'' """ def get_type(self): return 'text' class Password(Input): """Password input. >>> Password(name='password', value='secret').render() u'' """ def get_type(self): return 'password' class Textarea(Input): """Textarea input. >>> Textarea(name='foo', value='bar').render() u'' """ def render(self): attrs = self.attrs.copy() attrs['name'] = self.name value = net.websafe(self.value or '') return '' % (attrs, value) class Dropdown(Input): r"""Dropdown/select input. >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render() u'\n' >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render() u'\n' """ def __init__(self, name, args, *validators, **attrs): self.args = args super(Dropdown, self).__init__(name, *validators, **attrs) def render(self): attrs = self.attrs.copy() attrs['name'] = self.name x = '\n' return x def _render_option(self, arg, indent=' '): if isinstance(arg, (tuple, list)): value, desc= arg else: value, desc = arg, arg if self.value == value or (isinstance(self.value, list) and value in self.value): select_p = ' selected="selected"' else: select_p = '' return indent + '%s\n' % (select_p, net.websafe(value), net.websafe(desc)) class GroupedDropdown(Dropdown): r"""Grouped Dropdown/select input. >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render() u'\n' >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render() u'\n' """ def __init__(self, name, args, *validators, **attrs): self.args = args super(Dropdown, self).__init__(name, *validators, **attrs) def render(self): attrs = self.attrs.copy() attrs['name'] = self.name x = '\n' return x class Radio(Input): def __init__(self, name, args, *validators, **attrs): self.args = args super(Radio, self).__init__(name, *validators, **attrs) def render(self): x = '' for arg in self.args: if isinstance(arg, (tuple, list)): value, desc= arg else: value, desc = arg, arg attrs = self.attrs.copy() attrs['name'] = self.name attrs['type'] = 'radio' attrs['value'] = value if self.value == value: attrs['checked'] = 'checked' x += ' %s' % (attrs, net.websafe(desc)) x += '' return x class Checkbox(Input): """Checkbox input. >>> Checkbox('foo', value='bar', checked=True).render() u'' >>> Checkbox('foo', value='bar').render() u'' >>> c = Checkbox('foo', value='bar') >>> c.validate('on') True >>> c.render() u'' """ def __init__(self, name, *validators, **attrs): self.checked = attrs.pop('checked', False) Input.__init__(self, name, *validators, **attrs) def get_default_id(self): value = utils.safestr(self.value or "") return self.name + '_' + value.replace(' ', '_') def render(self): attrs = self.attrs.copy() attrs['type'] = 'checkbox' attrs['name'] = self.name attrs['value'] = self.value if self.checked: attrs['checked'] = 'checked' return '' % attrs def set_value(self, value): self.checked = bool(value) def get_value(self): return self.checked class Button(Input): """HTML Button. >>> Button("save").render() u'' >>> Button("action", value="save", html="Save Changes").render() u'' """ def __init__(self, name, *validators, **attrs): super(Button, self).__init__(name, *validators, **attrs) self.description = "" def render(self): attrs = self.attrs.copy() attrs['name'] = self.name if self.value is not None: attrs['value'] = self.value html = attrs.pop('html', None) or net.websafe(self.name) return '' % (attrs, html) class Hidden(Input): """Hidden Input. >>> Hidden(name='foo', value='bar').render() u'' """ def is_hidden(self): return True def get_type(self): return 'hidden' class File(Input): """File input. >>> File(name='f').render() u'' """ def get_type(self): return 'file' class Validator: def __deepcopy__(self, memo): return copy.copy(self) def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals()) def valid(self, value): try: return self.test(value) except: return False notnull = Validator("Required", bool) class regexp(Validator): def __init__(self, rexp, msg): self.rexp = re.compile(rexp) self.msg = msg def valid(self, value): return bool(self.rexp.match(value)) if __name__ == "__main__": import doctest doctest.testmod()