diff --git a/gluon/form.py b/gluon/form.py
index ff50911a..a0164443 100644
--- a/gluon/form.py
+++ b/gluon/form.py
@@ -1,7 +1,13 @@
import cgi
-from gluon import current
+import copy_reg
+from gluon import current, URL, DAL
from gluon.storage import Storage
from gluon.utils import web2py_uuid
+from gluon.sanitizer import sanitize
+
+# ################################################################
+# New HTML Helpers
+# ################################################################
def xmlescape(text):
return cgi.escape(text, True).replace("'", "'")
@@ -30,7 +36,7 @@ class TAG(object):
b = ''.join(s.xml() if isinstance(s,TAG) else xmlescape(unicode(s))
for s in self.children)
return '<%s%s>%s%s>' %(name, a, b, name)
-
+
def __unicode__(self):
return self.xml()
@@ -73,7 +79,6 @@ class METATAG(object):
return lambda *children, **attributes: TAG(name, *children, **attributes)
tag = METATAG()
-
DIV = tag('div')
SPAN = tag('span')
LI = tag('li')
@@ -102,8 +107,99 @@ SELECT = tag('select')
OPTION = tag('option')
TEXTAREA = tag('textarea')
-def FormStyleDefault(table, readonly, vars, errors):
+# ################################################################
+# New XML Helpers
+# ################################################################
+class XML(TAG):
+ """
+ use it to wrap a string that contains XML/HTML so that it will not be
+ escaped by the template
+
+ Examples:
+
+ >>> XML('
Hello
').xml()
+ 'Hello
'
+ """
+
+ def __init__(
+ self,
+ text,
+ sanitize=False,
+ permitted_tags=[
+ 'a','b','blockquote','br/','i','li','ol','ul','p','cite',
+ 'code','pre','img/','h1', 'h2', 'h3', 'h4', 'h5', 'h6',
+ 'table', 'tr', 'td', 'div','strong', 'span'],
+ allowed_attributes={
+ 'a': ['href', 'title', 'target'],
+ 'img': ['src', 'alt'],
+ 'blockquote': ['type'],
+ 'td': ['colspan']},
+ ):
+ """
+ Args:
+ text: the XML text
+ sanitize: sanitize text using the permitted tags and allowed
+ attributes (default False)
+ permitted_tags: list of permitted tags (default: simple list of
+ tags)
+ allowed_attributes: dictionary of allowed attributed (default
+ for A, IMG and BlockQuote).
+ The key is the tag; the value is a list of allowed attributes.
+ """
+
+ if sanitize:
+ text = sanitize(text, permitted_tags, allowed_attributes)
+ if isinstance(text, unicode):
+ text = text.encode('utf8', 'xmlcharrefreplace')
+ elif not isinstance(text, str):
+ text = str(text)
+ self.text = text
+
+ def xml(self):
+ return self.text
+
+ def __str__(self):
+ return self.text
+
+ def __add__(self, other):
+ return '%s%s' % (self, other)
+
+ def __radd__(self, other):
+ return '%s%s' % (other, self)
+
+ def __cmp__(self, other):
+ return cmp(str(self), str(other))
+
+ def __hash__(self):
+ return hash(str(self))
+
+ def __getitem__(self, i):
+ return str(self)[i]
+
+ def __getslice__(self, i, j):
+ return str(self)[i:j]
+
+ def __iter__(self):
+ for c in str(self):
+ yield c
+
+ def __len__(self):
+ return len(str(self))
+
+def XML_unpickle(data):
+ return XML(marshal.loads(data))
+
+def XML_pickle(data):
+ return XML_unpickle, (marshal.dumps(str(data)),)
+copy_reg.pickle(XML, XML_pickle, XML_unpickle)
+
+# ################################################################
+# Simple Form Style Function (example for more complex styles)
+# ################################################################
+
+def FormStyleDefault(table, vars, errors, readonly, deletable):
+
form = FORM(TABLE(),_method='POST',_action='#',_enctype='multipart/form-data')
for field in table:
@@ -128,42 +224,92 @@ def FormStyleDefault(table, readonly, vars, errors):
elif field.type == 'boolean':
control = INPUT(_type='checkbox', _id=input_id, _name=field.name,
_value='ON', _checked = value)
+ elif field.type == 'upload':
+ control = DIV(INPUT(_type='file', _id=input_id, _name=field.name))
+ if value:
+ control.append(A('download',
+ _href=URL('default','download',args=value)))
+ control.append(INPUT(_type='checkbox',_value='ON',
+ _name='_delete_'+field.name))
+ control.append('(check to remove)')
+ elif hasattr(field.requires, 'options'):
+ multiple = field.type.startswith('list:')
+ value = value if isinstance(value, list) else [value]
+ options = [OPTION(v,_value=k,_selected=(k in value))
+ for k,v in field.requires.options()]
+ control = SELECT(*options, _id=input_id, _name=field.name,
+ _multiple=multiple)
else:
field_type = 'password' if field.type == 'password' else 'text'
control = INPUT(_type=field_type, _id=input_id, _name=field.name,
_value=value, _class=field_class)
-
+
form[0].append(TR(TD(LABEL(field.label,_for=input_id)),
TD(control,DIV(error,_class='error') if error else ''),
TD(field.comment or '')))
-
- form[0].append(TR(TD(),TD(INPUT(_type='submit')),TD()))
+
+ td = TD(INPUT(_type='submit',_value='Submit'))
+ if deletable:
+ td.append(INPUT(_type='checkbox',_value='ON',_name='_delete'))
+ td.append('(check to delete)')
+ form[0].append(TR(TD(),td,TD()))
return form
+# ################################################################
+# Form object (replaced SQLFORM)
+# ################################################################
+
class Form(object):
+ """
+ Usage in web2py controller:
+
+ def index():
+ form = Form(db.thing, record=1)
+ if form.accepted: ...
+ elif form.errors: ...
+ else: ...
+ return dict(form=form)
+
+ Arguments:
+ - table: a DAL table or a list of fields (equivalent to old SQLFORM.factory)
+ - record: a DAL record or record id
+ - readonly: set to True to make a readonly form
+ - deletable: set to False to disallow deletion of record
+ - formstyle: a function that renders the form using helpers (FormStyleDefault)
+ - dbio: set to False to prevent any DB write
+ - keepvalues: (NOT IMPLEMENTED)
+ - formname: the optional name of this form
+ - csrf: set to False to disable CRSF protection
+ """
+
def __init__(self,
table,
- record_id=None,
+ record=None,
readonly=False,
+ deletable=True,
formstyle=FormStyleDefault,
dbio=True,
keepvalues=False,
formname=False,
csrf=True):
- if record_id is None:
- self.record_id = self.record = None
+ if isinstance(table, list):
+ dbio = False
+ fields = table
+ table = DAL(None).define_table('tmp', *fields)
+
+ if isinstance(record, (int, long, basestring)):
+ record_id = int(str(record))
+ self.record = table[record_id]
else:
- try:
- self.record_id, self.record = int(record_id), None
- except TypeError:
- self.record_id, self.record = record_id.id, record_id
-
+ self.record = record
+
self.table = table
self.readonly = readonly
+ self.deletable = deletable and not readonly and self.record
self.formstyle = formstyle
self.dbio = dbio
- self.keepvalues = True if keepvalues or self.record_id else False
+ self.keepvalues = True if keepvalues or self.record else False
self.csrf = csrf
self.vars = Storage()
self.errors = Storage()
@@ -179,36 +325,47 @@ class Form(object):
post_vars = request.post_vars
if readonly or request.env.request_method=='GET':
- if self.record_id:
- if not self.record:
- self.record = self.table[self.record_id]
- if self.record:
- self.vars = self.record
+ if self.record:
+ self.vars = self.record
else:
- self.subitted = True
+ print post_vars
+ self.submitted = True
# check for CSRF
if csrf and self.formname in (session._formkeys or {}):
self.formkey = session._formkeys[self.formname]
# validate fields
if not csrf or post_vars._formkey == self.formkey:
- for field in self.table:
- if field.writable:
- value = post_vars.get(field.name)
- (value, error) = field.validate(value)
- if value:
+ if not post_vars._delete:
+ for field in self.table:
+ if field.writable:
+ value = post_vars.get(field.name)
+ (value, error) = field.validate(value)
+ if field.type == 'upload':
+ delete = post_vars.get('_delete_'+field.name)
+ if value is not None and hasattr(value,'file'):
+ value = field.store(value.file,
+ value.filename,
+ field.uploadfolder)
+ elif self.record and not delete:
+ value = self.record.get(field.name)
+ else:
+ value = None
self.vars[field.name] = value
if error:
self.errors[field.name] = error
- if not self.errors:
- self.accepted = True
- if dbio:
- n_rec = 0
- if self.record_id:
- query = table._id==self.record_id
- n_rec = table._db(query).update(**self.vars)
- if n_rec == 0:
- # warning, should we really insert if record_id
- self.vars.id = self.table.insert(**self.vars)
+ if self.record:
+ self.vars.id = self.record.id
+ if not self.errors:
+ self.accepted = True
+ if dbio:
+ if self.record:
+ self.record.update_record(**self.vars)
+ else:
+ # warning, should we really insert if record
+ self.vars.id = self.table.insert(**self.vars)
+ elif dbio:
+ self.deleted = True
+ self.record.delete_record()
# store key for future CSRF
if csrf:
if not session._formkeys:
@@ -225,10 +382,11 @@ class Form(object):
def helper(self):
if not self.cached_helper:
- cached_helper = self.formstyle(self.table,
- self.readonly,
+ cached_helper = self.formstyle(self.table,
self.vars,
- self.errors)
+ self.errors,
+ self.readonly,
+ self.deletable)
if self.csrf:
cached_helper.append(INPUT(_type='hidden',_name='_formkey',
_value=self.formkey))
@@ -243,6 +401,6 @@ class Form(object):
def __str__(self):
return self.xml().encode('utf8')
-
+
if __name__=='__main__':
- print(DIV(SPAN('this',STRONG('a test')),_id=1,_class="my class"))
+ print(DIV(SPAN('this',STRONG('a test'),XML('1<2')),_id=1,_class="my class"))