Commits

Martín Mulone  committed d6d2df2

Add record and show table

  • Participants
  • Parent commits 3c946b0

Comments (0)

Files changed (11)

File controllers/default.py

 # Copyright (c) 2010- Mulone, Pablo Martin (http://martin.tecnodoc.com.ar/)
 
 #bottle
-from bottle import route, view, get, request, redirect, abort, local
+from bottle import route, view, get, post, request, redirect, abort, local
+
+#web2pylibs
+from web2pylibs.sqlhtml_bottle import SQLFORM, SQLTABLE
 
 #My models
 from models.app import * 
 def index():
     
     init_app()
+    init_db()
     
-    return render_layout()
+    db = local.app_response.db
+    rows = db(db.person.id > 0).select()
+    table = SQLTABLE(rows)    
+    
+    return render_layout(table=table)
+
+@get('/add')
+@post('/add')
+@view('add')
+def add():
+    
+    init_app()
+    init_db()   
+    
+    dal_bottle = local.app_response.dal_bottle
+    
+    message = 'Fill the form to add a new person'
+    form = SQLFORM(local.app_response.db.person)
+    if form.submit():        
+        name = request.forms.get('name')
+        age = request.forms.get('age')
+        email = request.forms.get('email')        
+        message = dal_bottle.validate_and_insert('person', commit=True, 
+                                                 name=name, 
+                                                 age=age, 
+                                                 email=email)    
+          
+            
+    return render_layout(form=form, message=message)
+
     
 

File models/app.py

 # -*- coding: utf-8 -*- 
 # Copyright (c) 2010- Mulone, Pablo Martin (http://martin.tecnodoc.com.ar/)
 
+#imports
+from datetime import datetime
+
 #bottle
 from bottle import local
 
 #web2py libs
 from web2pylibs.storage import Storage
+from web2pylibs.dal_bottle import DALBottle
+from web2pylibs.dal import Field
+from web2pylibs.validators import *
 
 #Models
 from models.config import *
     app_response.metanames['description']=app_settings.description
     app_response.metanames['keywords']=app_settings.keywords
     app_response.metanames['generator']=app_settings.generator
+    
+    
+def init_db():
+    
+    connect_db()
+    define_tables()    
+    
 
 def default_menu():
     
     app_response.menu = []    
     app_response.menu = [
         ('Home', False, '/',[])
+        ]      
+    
+    app_response.menu += [
+        ('Add', False, '/add',[])
         ]        
         
     app_response.cssfiles.append('/static/css/superfish.css')
     app_response.render_scriptsonbottom = layout.render_scriptsonbottom()
     
     return dict(app_response=app_response, **elements)
+
+
+def connect_db():        
+             
+    dal_bottle = DALBottle((app_settings.database_uri),folder='data')
+    local.app_response.dal_bottle = dal_bottle
+    local.app_response.db = dal_bottle.db
+            
+       
+def define_tables():
+    
+    db = local.app_response.db
+            
+    db.define_table('person',
+                    Field('name','string',label='Name'),
+                    Field('age','integer',default=0,label='Age'),
+                    Field('email', type='string',label='Email'),
+                    Field('created_on','datetime', default=datetime.now(),
+                          label='Created On',writable=False,readable=False),
+                    migrate=app_settings.migrate)
+        
+    db.person.name.requires = IS_NOT_EMPTY(error_message="Cannot be empty")
+    db.person.email.requires = (IS_EMAIL(error_message="Invalid email"),
+                                IS_NOT_IN_DB(db, db.person.email))
+    
+

File models/dalextend.py

-# -*- coding: utf-8 -*- 
-# Copyright (c) 2010- Mulone, Pablo Martin (http://martin.tecnodoc.com.ar/)
-
-#gluon
-from web2pylibs.dal import DAL
-
-class DalExtend(object):
-
-    def __init__(self, database_uri, folder='data'):             
-               
-        self.db=DAL((database_uri),folder=folder)
-                       
-        
-    def validate_and_insert(self, field, commit=False, **fields):
-        
-        db = self.db
-        
-        if commit:
-            try:
-                response = db[field].validate_and_insert(**fields)
-            except:                
-                db.rollback()
-            else:                
-                db.commit()
-        else:
-            response = db[field].validate_and_insert(**fields)
-            
-        if not response.id:   
-            strerror=""
-            for key, value in response.errors.items(): 
-                strerror += "- Field:%s Problem: %s \n"%(key,value)            
-            message = MARKMIN("## Cannot insert because: \n %s"%strerror)
-        else:
-            message = "Record added!"     
-            
-        return message
-    
-    

File models/data.py

-# -*- coding: utf-8 -*- 
-# Copyright (c) 2010- Mulone, Pablo Martin (http://martin.tecnodoc.com.ar/)
-
-#bottle
-from bottle import local
-
-#web2pylibs
-from web2pylibs.dal import Field
-from web2pylibs.validators import *
-
-#locals
-from models.config import *
-from models.dalextend import DalExtend
-
-class Data(object):
-
-    def __init__(self):
-        
-        pass               
-                
-        
-    def connect(self):        
-                
-        self.migrate = app_settings.migrate      
-        self.dalext = DalExtend((app_settings.database_uri),folder='data')
-        self.db = self.dalext.db
-        
-        #with this we make the connection local thread
-        local.app_response.db = self.dalext.db
-        
-       
-    def define_tables(self):
-        
-        db = self.db
-        
-        db.define_table('person',
-              Field('name'),
-              Field('birth','date'),
-              migrate=self.migrate)
-        
-        
-        db.define_table('dog',
-               Field('name'),
-               Field('birth','date'),
-               Field('owner',db.person),
-               migrate=self.migrate)
-        
-        

File static/css/layout.css

  *  HERE YOU CAN START TO WRITE YOUR OWN DIVS
  */
 
-#main-column { float: left; width: 640px;} 
-#left-column { float: right; width: 180px; } 
+#main-column { float: left; width: 580px;} 
+#left-column { float: right; width: 280px; } 

File views/add.tpl

+<div id="main-column">
+{{!form}}
+</div>
+
+<div id="left-column">
+{{!message}}
+</div>
+
+<div style="clear: both;"></div><!-- Clear the divs -->
+
+%rebase layout app_response=app_response

File views/index.tpl

 <div id="main-column">
-Main Column
+
+<h2>Persons</h2>
+
+{{!table}}
+
 </div>
 
-<div id="left-column">
-Some left column
-</div>
-
+<div style="clear: both;"></div><!-- Clear the divs -->
 
 %rebase layout app_response=app_response

File views/layout.tpl

 	</div><!-- page -->							
 	
 	
-	<div id="footer">
-	      <img src="/static/images/bottle.png" title="Bottle" style="float: left; padding-right: 6px;" />		
-		Powered by <a href="http://www.bottlepy.org/">bottlepy.org</a>
+	<div id="footer">	      
+		Powered by <a href="http://www.bottlepy.org/">Bottlepy Framework</a>
 		 <div style="clear: both;"></div><!-- Clear the divs -->	     
 	</div><!-- footer -->
 	

File web2pylibs/dal_bottle.py

+# -*- coding: utf-8 -*- 
+# Copyright (c) 2010- Mulone, Pablo Martin (http://martin.tecnodoc.com.ar/)
+
+# web2pylibs
+from dal import DAL
+from html import MARKMIN
+
+class DALBottle(object):
+
+    def __init__(self, database_uri, folder='data'):             
+               
+        self.db=DAL((database_uri),folder=folder)
+                       
+        
+    def validate_and_insert(self, field, commit=False, **fields):
+        
+        db = self.db
+        
+        if commit:
+            try:
+                response = db[field].validate_and_insert(**fields)
+            except:                
+                db.rollback()
+            else:                
+                db.commit()
+        else:
+            response = db[field].validate_and_insert(**fields)
+            
+        if not response.id:   
+            strerror=""
+            for key, value in response.errors.items(): 
+                strerror += "- Field:%s Problem: %s \n"%(key,value)            
+            message = MARKMIN("## Cannot insert because: \n %s"%strerror)
+        else:
+            message = "Record added!"     
+            
+        return message
+    
+    

File web2pylibs/sqlhtml_bottle.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+This file is part of the web2py Web Framework
+Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
+License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
+
+Holds:
+
+- SQLFORM: provide a form for a table (with/without record)
+- SQLTABLE: provides a table for a set of records
+- form_factory: provides a SQLFORM for an non-db backed table
+
+"""
+
+from bottle import abort, request
+
+#from http import HTTP
+from html import XML, SPAN, TAG, A, DIV, UL, LI, TEXTAREA, BR, IMG, SCRIPT
+from html import FORM, INPUT, LABEL, OPTION, SELECT
+from html import TABLE, THEAD, TBODY, TR, TD, TH
+#from html import URL as Url
+from dal import DAL, Table, Row, CALLABLETYPES
+from storage import Storage
+from utils import md5_hash
+from validators import IS_EMPTY_OR
+
+import urllib
+import re
+import cStringIO
+
+
+table_field = re.compile('[\w_]+\.[\w_]+')
+widget_class = re.compile('^\w*')
+
+def safe_int(x):
+    try:
+        return int(x)
+    except ValueError:
+        return 0
+
+def safe_float(x):
+    try:
+        return float(x)
+    except ValueError:
+        return 0
+
+class FormWidget(object):
+    """
+    helper for SQLFORM to generate form input fields (widget),
+    related to the fieldtype
+    """
+
+    @staticmethod
+    def _attributes(field, widget_attributes, **attributes):
+        """
+        helper to build a common set of attributes
+
+        :param field: the field involved, some attributes are derived from this
+        :param widget_attributes:  widget related attributes
+        :param attributes: any other supplied attributes
+        """
+        attr = dict(
+            _id = '%s_%s' % (field._tablename, field.name),
+            _class = widget_class.match(str(field.type)).group(),
+            _name = field.name,
+            requires = field.requires,
+            )
+        attr.update(widget_attributes)
+        attr.update(attributes)
+        return attr
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates the widget for the field.
+
+        When serialized, will provide an INPUT tag:
+
+        - id = tablename_fieldname
+        - class = field.type
+        - name = fieldname
+
+        :param field: the field needing the widget
+        :param value: value
+        :param attributes: any other attributes to be applied
+        """
+
+        raise NotImplementedError
+
+class StringWidget(FormWidget):
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates an INPUT text tag.
+
+        see also: :meth:`FormWidget.widget`
+        """
+
+        default = dict(
+            _type = 'text',
+            value = (value!=None and str(value)) or '',
+            )
+        attr = StringWidget._attributes(field, default, **attributes)
+
+        return INPUT(**attr)
+
+
+class IntegerWidget(StringWidget):
+
+    pass
+
+
+class DoubleWidget(StringWidget):
+
+    pass
+
+
+class DecimalWidget(StringWidget):
+
+    pass
+
+
+class TimeWidget(StringWidget):
+
+    pass
+
+
+class DateWidget(StringWidget):
+
+    pass
+
+
+class DatetimeWidget(StringWidget):
+
+    pass
+
+
+class TextWidget(FormWidget):
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates a TEXTAREA tag.
+
+        see also: :meth:`FormWidget.widget`
+        """
+
+        default = dict(
+            value = value,
+            )
+        attr = TextWidget._attributes(field, default, **attributes)
+
+        return TEXTAREA(**attr)
+
+
+class BooleanWidget(FormWidget):
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates an INPUT checkbox tag.
+
+        see also: :meth:`FormWidget.widget`
+        """
+
+        default=dict(
+            _type='checkbox',
+            value=value,
+            )
+        attr = BooleanWidget._attributes(field, default, **attributes)
+
+        return INPUT(**attr)
+
+
+class OptionsWidget(FormWidget):
+
+    @staticmethod
+    def has_options(field):
+        """
+        checks if the field has selectable options
+
+        :param field: the field needing checking
+        :returns: True if the field has options
+        """
+
+        return hasattr(field.requires, 'options')
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates a SELECT tag, including OPTIONs (only 1 option allowed)
+
+        see also: :meth:`FormWidget.widget`
+        """
+        default = dict(
+            value=value,
+            )
+        attr = OptionsWidget._attributes(field, default, **attributes)
+
+        requires = field.requires
+        if not isinstance(requires, (list, tuple)):
+            requires = [requires]
+        if requires:
+            if hasattr(requires[0], 'options'):
+                options = requires[0].options()
+            else:
+                raise SyntaxError, 'widget cannot determine options of %s' \
+                    % field
+        opts = [OPTION(v, _value=k) for (k, v) in options]
+
+        return SELECT(*opts, **attr)
+
+class ListWidget(StringWidget):
+    @staticmethod
+    def widget(field,value,**attributes):
+        _id = '%s_%s' % (field._tablename, field.name)
+        _name = field.name
+        if field.type=='list:integer': _class = 'integer'
+        else: _class = 'string'
+        items=[LI(INPUT(_id=_id,_class=_class,_name=_name,value=v,hideerror=True)) \
+                   for v in value or ['']]
+        script=SCRIPT("""
+// from http://refactormycode.com/codes/694-expanding-input-list-using-jquery
+(function(){
+jQuery.fn.grow_input = function() {
+  return this.each(function() {
+    var ul = this;
+    jQuery(ul).find(":text").after('<a href="javascript:void(0)>+</a>').keypress(function (e) { return (e.which == 13) ? pe(ul) : true; }).next().click(function(){ pe(ul) });
+  });
+};
+function pe(ul) {
+  var new_line = ml(ul);
+  rel(ul);
+  new_line.appendTo(ul);
+  new_line.find(":text").focus();
+  return false;
+}
+function ml(ul) {
+  var line = jQuery(ul).find("li:first").clone(true);
+  line.find(':text').val('');
+  return line;
+}
+function rel(ul) {
+  jQuery(ul).find("li").each(function() {
+    var trimmed = jQuery.trim(jQuery(this.firstChild).val());
+    if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed);
+  });
+}
+})();
+jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();});
+""" % _id)
+        attributes['_id']=_id+'_grow_input'
+        return TAG[''](UL(*items,**attributes),script)
+
+
+class MultipleOptionsWidget(OptionsWidget):
+
+    @staticmethod
+    def widget(field, value, size=5, **attributes):
+        """
+        generates a SELECT tag, including OPTIONs (multiple options allowed)
+
+        see also: :meth:`FormWidget.widget`
+
+        :param size: optional param (default=5) to indicate how many rows must
+            be shown
+        """
+
+        attributes.update(dict(_size=size, _multiple=True))
+
+        return OptionsWidget.widget(field, value, **attributes)
+
+
+class RadioWidget(OptionsWidget):
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates a TABLE tag, including INPUT radios (only 1 option allowed)
+
+        see also: :meth:`FormWidget.widget`
+        """
+
+        attr = OptionsWidget._attributes(field, {}, **attributes)
+
+        requires = field.requires
+        if not isinstance(requires, (list, tuple)):
+            requires = [requires]
+        if requires:
+            if hasattr(requires[0], 'options'):
+                options = requires[0].options()
+            else:
+                raise SyntaxError, 'widget cannot determine options of %s' \
+                    % field
+
+        options = [(k, v) for k, v in options if str(v)]
+        opts = []
+        cols = attributes.get('cols',1)
+        totals = len(options)
+        mods = totals%cols
+        rows = totals/cols
+        if mods:
+            rows += 1
+
+        for r_index in range(rows):
+            tds = []
+            for k, v in options[r_index*cols:(r_index+1)*cols]:
+                tds.append(TD(INPUT(_type='radio', _name=field.name,
+                         requires=attr.get('requires',None),
+                         hideerror=True, _value=k,
+                         value=value), v))
+            opts.append(TR(tds))
+
+        if opts:
+            opts[-1][0][0]['hideerror'] = False
+        return TABLE(*opts, **attr)
+
+
+class CheckboxesWidget(OptionsWidget):
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates a TABLE tag, including INPUT checkboxes (multiple allowed)
+
+        see also: :meth:`FormWidget.widget`
+        """
+
+        # was values = re.compile('[\w\-:]+').findall(str(value))
+        if isinstance(value, (list, tuple)):
+            values = [str(v) for v in value]
+        else:
+            values = [str(value)]
+
+        attr = OptionsWidget._attributes(field, {}, **attributes)
+
+        requires = field.requires
+        if not isinstance(requires, (list, tuple)):
+            requires = [requires]
+        if requires:
+            if hasattr(requires[0], 'options'):
+                options = requires[0].options()
+            else:
+                raise SyntaxError, 'widget cannot determine options of %s' \
+                    % field
+
+        options = [(k, v) for k, v in options if k != '']
+        opts = []
+        cols = attributes.get('cols', 1)
+        totals = len(options)
+        mods = totals % cols
+        rows = totals / cols
+        if mods:
+            rows += 1
+
+        for r_index in range(rows):
+            tds = []
+            for k, v in options[r_index*cols:(r_index+1)*cols]:
+                if k in values:
+                    r_value = k
+                else:
+                    r_value = []
+                tds.append(TD(INPUT(_type='checkbox', _name=field.name,
+                         requires=attr.get('requires', None),
+                         hideerror=True, _value=k,
+                         value=r_value), v))
+            opts.append(TR(tds))
+
+        if opts:
+            opts[-1][0][0]['hideerror'] = False
+        return TABLE(*opts, **attr)
+
+
+class PasswordWidget(FormWidget):
+
+    DEFAULT_PASSWORD_DISPLAY = 8*('*')
+
+    @staticmethod
+    def widget(field, value, **attributes):
+        """
+        generates a INPUT password tag.
+        If a value is present it will be shown as a number of '*', not related
+        to the length of the actual value.
+
+        see also: :meth:`FormWidget.widget`
+        """
+
+        default=dict(
+            _type='password',
+            _value=(value and PasswordWidget.DEFAULT_PASSWORD_DISPLAY) or '',
+            )
+        attr = PasswordWidget._attributes(field, default, **attributes)
+
+        return INPUT(**attr)
+
+
+class UploadWidget(FormWidget):
+
+    DEFAULT_WIDTH = '150px'
+    ID_DELETE_SUFFIX = '__delete'
+    GENERIC_DESCRIPTION = 'file'
+    DELETE_FILE = 'delete'
+
+    @staticmethod
+    def widget(field, value, download_url=None, **attributes):
+        """
+        generates a INPUT file tag.
+
+        Optionally provides an A link to the file, including a checkbox so
+        the file can be deleted.
+        All is wrapped in a DIV.
+
+        see also: :meth:`FormWidget.widget`
+
+        :param download_url: Optional URL to link to the file (default = None)
+        """
+
+        default=dict(
+            _type='file',
+            )
+        attr = UploadWidget._attributes(field, default, **attributes)
+
+        inp = INPUT(**attr)
+
+        if download_url and value:
+            url = download_url + '/' + value
+            (br, image) = ('', '')
+            if UploadWidget.is_image(value):
+                br = BR()
+                image = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH)
+
+            requires = attr["requires"]
+            if requires == [] or isinstance(requires, IS_EMPTY_OR):
+                inp = DIV(inp, '[',
+                          A(UploadWidget.GENERIC_DESCRIPTION, _href = url),
+                          '|',
+                          INPUT(_type='checkbox',
+                                _name=field.name + UploadWidget.ID_DELETE_SUFFIX),
+                          UploadWidget.DELETE_FILE,
+                          ']', br, image)
+            else:
+                inp = DIV(inp, '[',
+                          A(UploadWidget.GENERIC_DESCRIPTION, _href = url),
+                          ']', br, image)
+        return inp
+
+    @staticmethod
+    def represent(field, value, download_url=None):
+        """
+        how to represent the file:
+
+        - with download url and if it is an image: <A href=...><IMG ...></A>
+        - otherwise with download url: <A href=...>file</A>
+        - otherwise: file
+
+        :param field: the field
+        :param value: the field value
+        :param download_url: url for the file download (default = None)
+        """
+
+        inp = UploadWidget.GENERIC_DESCRIPTION
+
+        if download_url and value:
+            url = download_url + '/' + value
+            if UploadWidget.is_image(value):
+                inp = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH)
+            inp = A(inp, _href = url)
+
+        return inp
+
+    @staticmethod
+    def is_image(value):
+        """
+        Tries to check if the filename provided references to an image
+
+        Checking is based on filename extension. Currently recognized:
+           gif, png, jp(e)g, bmp
+
+        :param value: filename
+        """
+
+        extension = value.split('.')[-1].lower()
+        if extension in ['gif', 'png', 'jpg', 'jpeg', 'bmp']:
+            return True
+        return False
+
+
+
+class SQLFORM(FORM):
+
+    """
+    SQLFORM is used to map a table (and a current record) into an HTML form
+
+    given a SQLTable stored in db.table
+
+    generates an insert form::
+
+        SQLFORM(db.table)
+
+    generates an update form::
+
+        record=db.table[some_id]
+        SQLFORM(db.table, record)
+
+    generates an update with a delete button::
+
+        SQLFORM(db.table, record, deletable=True)
+
+    if record is an int::
+
+        record=db.table[record]
+
+    optional arguments:
+
+    :param fields: a list of fields that should be placed in the form,
+        default is all.
+    :param labels: a dictionary with labels for each field, keys are the field
+        names.
+    :param col3: a dictionary with content for an optional third column
+            (right of each field). keys are field names.
+    :param linkto: the URL of a controller/function to access referencedby
+        records
+            see controller appadmin.py for examples
+    :param upload: the URL of a controller/function to download an uploaded file
+            see controller appadmin.py for examples
+
+    any named optional attribute is passed to the <form> tag
+            for example _class, _id, _style, _action, _method, etc.
+
+    """
+
+    # usability improvements proposal by fpp - 4 May 2008 :
+    # - correct labels (for points to field id, not field name)
+    # - add label for delete checkbox
+    # - add translatable label for record ID
+    # - add third column to right of fields, populated from the col3 dict
+
+    widgets = Storage(dict(
+        string = StringWidget,
+        text = TextWidget,
+        password = PasswordWidget,
+        integer = IntegerWidget,
+        double = DoubleWidget,
+        decimal = DecimalWidget,
+        time = TimeWidget,
+        date = DateWidget,
+        datetime = DatetimeWidget,
+        upload = UploadWidget,
+        boolean = BooleanWidget,
+        blob = None,
+        options = OptionsWidget,
+        multiple = MultipleOptionsWidget,
+        radio = RadioWidget,
+        checkboxes = CheckboxesWidget,        
+        list = ListWidget,
+        ))
+
+    FIELDNAME_REQUEST_DELETE = 'delete_this_record'
+    FIELDKEY_DELETE_RECORD = 'delete_record'
+    ID_LABEL_SUFFIX = '__label'
+    ID_ROW_SUFFIX = '__row'
+
+    def __init__(
+        self,
+        table,
+        record = None,
+        deletable = False,
+        linkto = None,
+        upload = None,
+        fields = None,
+        labels = None,
+        col3 = {},
+        submit_button = 'Submit',
+        delete_label = 'Check to delete:',
+        showid = True,
+        readonly = False,
+        comments = True,
+        keepopts = [],
+        ignore_rw = False,
+        record_id = None,
+        formstyle = 'table3cols',
+        buttons = ['submit'],
+        submit_name = 'submit',
+        **attributes
+        ):
+        """
+        SQLFORM(db.table,
+               record=None,
+               fields=['name'],
+               labels={'name': 'Your name'},
+               linkto=URL(r=request, f='table/db/')
+        """
+
+        self.ignore_rw = ignore_rw
+        self.formstyle = formstyle
+        nbsp = XML('&nbsp;') # Firefox2 does not display fields with blanks
+        FORM.__init__(self, *[], **attributes)
+        ofields = fields
+        keyed = hasattr(table,'_primarykey')
+        self.submit_name = submit_name
+
+        # if no fields are provided, build it from the provided table
+        # will only use writable or readable fields, unless forced to ignore
+        if fields == None:
+            fields = [f.name for f in table if (ignore_rw or f.writable or f.readable) and not f.compute]
+        self.fields = fields
+
+        # make sure we have an id
+        if self.fields[0] != table.fields[0] and \
+                isinstance(table,Table) and not keyed:
+            self.fields.insert(0, table.fields[0])
+
+        self.table = table
+
+        # try to retrieve the indicated record using its id
+        # otherwise ignore it
+        if record and isinstance(record, (int, long, str, unicode)):
+            if not str(record).isdigit():
+                raise abort(404, "Object not found")
+            record = table._db(table._id == record).select().first()
+            if not record:
+                raise abort(404, "Object not found")
+        self.record = record
+
+        self.record_id = record_id
+        if keyed:
+            if record:
+                self.record_id = dict([(k,record[k]) for k in table._primarykey])
+            else:
+                self.record_id = dict([(k,None) for k in table._primarykey])
+        self.field_parent = {}
+        xfields = []
+        self.fields = fields
+        self.custom = Storage()
+        self.custom.dspval = Storage()
+        self.custom.inpval = Storage()
+        self.custom.label = Storage()
+        self.custom.comment = Storage()
+        self.custom.widget = Storage()
+        self.custom.linkto = Storage()
+
+        for fieldname in self.fields:
+            if fieldname.find('.') >= 0:
+                continue
+
+            field = self.table[fieldname]
+            comment = None
+
+            if comments:
+                comment = col3.get(fieldname, field.comment)
+            if comment == None:
+                comment = ''
+            self.custom.comment[fieldname] = comment
+
+            if labels != None and fieldname in labels:
+                label = labels[fieldname]
+                colon = ''
+            else:
+                label = field.label
+                colon = ': '
+            self.custom.label[fieldname] = label
+
+            field_id = '%s_%s' % (table._tablename, fieldname)
+
+            label = LABEL(label, colon, _for=field_id,
+                          _id=field_id+SQLFORM.ID_LABEL_SUFFIX)
+
+            row_id = field_id+SQLFORM.ID_ROW_SUFFIX
+            if field.type == 'id':
+                self.custom.dspval.id = nbsp
+                self.custom.inpval.id = ''
+                widget = ''
+                if record:
+                    if showid and 'id' in fields and field.readable:
+                        v = record['id']
+                        widget = SPAN(v, _id=field_id)
+                        self.custom.dspval.id = str(v)
+                        xfields.append((row_id,label, widget,comment))
+                    self.record_id = str(record['id'])
+                self.custom.widget.id = widget
+                continue
+
+            if readonly and not ignore_rw and not field.readable:
+                continue
+
+            if record:
+                default = record[fieldname]
+            else:
+                default = field.default
+                if isinstance(default,CALLABLETYPES):
+                    default=default()
+
+            cond = readonly or \
+                (not ignore_rw and not field.writable and field.readable)
+
+            if default and not cond:
+                default = field.formatter(default)
+            dspval = default
+            inpval = default
+
+            if cond:
+
+                # ## if field.represent is available else
+                # ## ignore blob and preview uploaded images
+                # ## format everything else
+
+                if field.represent:
+                    inp = field.represent(default)
+                elif field.type in ['blob']:
+                    continue
+                elif field.type == 'upload':
+                    inp = UploadWidget.represent(field, default, upload)
+                elif field.type == 'boolean':
+                    inp = self.widgets.boolean.widget(field, default, _disabled=True)
+                else:
+                    inp = field.formatter(default)
+            elif field.type == 'upload':
+                if hasattr(field, 'widget') and field.widget:
+                    inp = field.widget(field, default, upload)
+                else:
+                    inp = self.widgets.upload.widget(field, default, upload)
+            elif hasattr(field, 'widget') and field.widget:
+                inp = field.widget(field, default)
+            elif field.type == 'boolean':
+                inp = self.widgets.boolean.widget(field, default)
+                if default:
+                    inpval = 'checked'
+                else:
+                    inpval = ''
+            elif OptionsWidget.has_options(field):
+                if not field.requires.multiple:
+                    inp = self.widgets.options.widget(field, default)
+                else:
+                    inp = self.widgets.multiple.widget(field, default)
+                if fieldname in keepopts:
+                    inpval = TAG[''](*inp.components)
+            elif field.type.startswith('list:'):
+                inp = self.widgets.list.widget(field,default)
+            elif field.type == 'text':
+                inp = self.widgets.text.widget(field, default)
+            elif field.type == 'password':
+                inp = self.widgets.password.widget(field, default)
+                if self.record:
+                    dspval = PasswordWidget.DEFAULT_PASSWORD_DISPLAY
+                else:
+                    dspval = ''
+            elif field.type == 'blob':
+                continue
+            else:
+                inp = self.widgets.string.widget(field, default)
+
+            xfields.append((row_id,label,inp,comment))
+            self.custom.dspval[fieldname] = dspval or nbsp
+            self.custom.inpval[fieldname] = inpval or ''
+            self.custom.widget[fieldname] = inp
+
+        # if a record is provided and found, as is linkto
+        # build a link
+        if record and linkto:
+            db = linkto.split('/')[-1]
+            for (rtable, rfield) in table._referenced_by:
+                if keyed:
+                    rfld = table._db[rtable][rfield]
+                    query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]]))
+                else:
+#                 <block>
+                    query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record.id))
+                lname = olname = '%s.%s' % (rtable, rfield)
+                if ofields and not olname in ofields:
+                    continue
+                if labels and lname in labels:
+                    lname = labels[lname]
+                widget = A(lname,
+                           _class='reference',
+                           _href='%s/%s?query=%s' % (linkto, rtable, query))
+                xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX,
+                                '',widget,col3.get(olname,'')))
+                self.custom.linkto[olname.replace('.', '__')] = widget
+#                 </block>
+
+        # when deletable, add delete? checkbox
+        self.custom.deletable = ''
+        if record and deletable:
+            widget = INPUT(_type='checkbox',
+                            _class='delete',
+                            _id=self.FIELDKEY_DELETE_RECORD,
+                            _name=self.FIELDNAME_REQUEST_DELETE,
+                            )
+            xfields.append((self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_ROW_SUFFIX,
+                            LABEL(
+                                delete_label,
+                                _for=self.FIELDKEY_DELETE_RECORD,
+                                _id=self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_LABEL_SUFFIX),
+                            widget,
+                            col3.get(self.FIELDKEY_DELETE_RECORD, '')))
+            self.custom.deletable = widget
+        # when writable, add submit button
+        self.custom.submit = ''
+        if (not readonly) and ('submit' in buttons):
+            widget = INPUT(_type='submit',
+                           _value=submit_button,
+                           _name=submit_name)
+            xfields.append(('submit_record'+SQLFORM.ID_ROW_SUFFIX,
+                            '', widget,col3.get('submit_button', '')))
+            self.custom.submit = widget
+        # if a record is provided and found
+        # make sure it's id is stored in the form
+        if record:
+            if not self['hidden']:
+                self['hidden'] = {}
+            if not keyed:
+                self['hidden']['id'] = record['id']
+
+        (begin, end) = self._xml()
+        self.custom.begin = XML("<%s %s>" % (self.tag, begin))
+        self.custom.end = XML("%s</%s>" % (end, self.tag))
+        table = self.createform(xfields)
+        self.components = [table]
+
+    def createform(self, xfields):
+        if self.formstyle == 'table3cols':
+            table = TABLE()
+            for id,a,b,c in xfields:
+                td_b = self.field_parent[id] = TD(b,_class='w2p_fw')
+                table.append(TR(TD(a,_class='w2p_fl'),
+                                td_b,
+                                TD(c,_class='w2p_fc'),_id=id))
+        elif self.formstyle == 'table2cols':
+            table = TABLE()
+            for id,a,b,c in xfields:
+                td_b = self.field_parent[id] = TD(b,_class='w2p_fw',_colspan="2")
+                table.append(TR(TD(a,_class='w2p_fl'),
+                                TD(c,_class='w2p_fc'),_id=id
+                                +'1',_class='even'))
+                table.append(TR(td_b,_id=id+'2',_class='odd'))
+        elif self.formstyle == 'divs':
+            table = TAG['']()
+            for id,a,b,c in xfields:
+                div_b = self.field_parent[id] = DIV(b,_class='w2p_fw')
+                table.append(DIV(DIV(a,_class='w2p_fl'),
+                                 div_b,
+                                 DIV(c,_class='w2p_fc'),_id=id))
+        elif self.formstyle == 'ul':
+            table = UL()
+            for id,a,b,c in xfields:
+                div_b = self.field_parent[id] = DIV(b,_class='w2p_fw')
+                table.append(LI(DIV(a,_class='w2p_fl'),
+                                div_b,
+                                DIV(c,_class='w2p_fc'),_id=id))
+        elif type(self.formstyle) == type(lambda:None):
+            table = TABLE()
+            for id,a,b,c in xfields:
+                td_b = self.field_parent[id] = TD(b,_class='w2p_fw')
+                newrows = self.formstyle(id,a,td_b,c)
+                if type(newrows).__name__ != "tuple":
+                    newrows = [newrows]
+                for newrow in newrows:
+                    table.append(newrow)
+        else:
+            raise RuntimeError, 'formstyle not supported'
+        return table
+
+
+    def submit(self):
+
+        """
+        similar FORM.accepts but also does insert, update or delete in DAL.
+        but if detect_record_change == True than:
+          form.record_changed = False (record is properly validated/submitted)
+          form.record_changed = True (record cannot be submitted because changed)
+        elseif detect_record_change == False than:
+          form.record_changed = None
+        """
+        
+        if request.POST.get('%s'%self.submit_name,'').strip():
+            return True
+        else:
+            return False       
+        
+        
+
+    @staticmethod
+    def factory(*fields, **attributes):
+        """
+        generates a SQLFORM for the given fields.
+
+        Internally will build a non-database based data model
+        to hold the fields.
+        """
+        # Define a table name, this way it can be logical to our CSS.
+        # And if you switch from using SQLFORM to SQLFORM.factory
+        # your same css definitions will still apply.
+
+        table_name = attributes.get('table_name', 'no_table')
+
+        # So it won't interfear with SQLDB.define_table
+        if 'table_name' in attributes:
+            del attributes['table_name']
+
+        return SQLFORM(DAL(None).define_table(table_name, *fields), **attributes)
+
+
+class SQLTABLE(TABLE):
+
+    """
+    given a Rows object, as returned by a db().select(), generates
+    an html table with the rows.
+
+    optional arguments:
+
+    :param linkto: URL (or lambda to generate a URL) to edit individual records
+    :param upload: URL to download uploaded files
+    :param orderby: Add an orderby link to column headers.
+    :param headers: dictionary of headers to headers redefinions
+                    headers can also be a string to gerenare the headers from data
+                    for now only headers="fieldname:capitalize",
+                    headers="labels" and headers=None are supported
+    :param truncate: length at which to truncate text in table cells.
+        Defaults to 16 characters.
+    :param columns: a list or dict contaning the names of the columns to be shown
+        Defaults to all
+
+    Optional names attributes for passed to the <table> tag
+
+    The keys of headers and columns must be of the form "tablename.fieldname"
+
+    Simple linkto example::
+
+        rows = db.select(db.sometable.ALL)
+        table = SQLTABLE(rows, linkto='someurl')
+
+    This will link rows[id] to .../sometable/value_of_id
+
+
+    More advanced linkto example::
+
+        def mylink(field, type, ref):
+            return URL(r=request, args=[field])
+
+        rows = db.select(db.sometable.ALL)
+        table = SQLTABLE(rows, linkto=mylink)
+
+    This will link rows[id] to
+        current_app/current_controlle/current_function/value_of_id
+
+
+    """
+
+    def __init__(
+        self,
+        sqlrows,
+        linkto=None,
+        upload=None,
+        orderby=None,
+        headers={},
+        truncate=16,
+        columns=None,
+        th_link='',
+        **attributes
+        ):
+
+        TABLE.__init__(self, **attributes)
+        self.components = []
+        self.attributes = attributes
+        self.sqlrows = sqlrows
+        (components, row) = (self.components, [])
+        if not sqlrows:
+            return
+        if not columns:
+            columns = sqlrows.colnames
+        if headers=='fieldname:capitalize':
+            headers = {}
+            for c in columns:
+                headers[c] = ' '.join([w.capitalize() for w in c.split('.')[-1].split('_')])
+        elif headers=='labels':
+            headers = {}
+            for c in columns:
+                (t,f) = c.split('.')
+                field = sqlrows.db[t][f]
+                headers[c] = field.label
+
+        if headers!=None:
+            for c in columns:
+                if orderby:
+                    row.append(TH(A(headers.get(c, c),
+                                    _href=th_link+'?orderby=' + c)))
+                else:
+                    row.append(TH(headers.get(c, c)))
+            components.append(THEAD(TR(*row)))
+
+        tbody = []
+        for (rc, record) in enumerate(sqlrows):
+            row = []
+            if rc % 2 == 0:
+                _class = 'even'
+            else:
+                _class = 'odd'
+            for colname in columns:
+                if not table_field.match(colname):
+                    if "_extra" in record and colname in record._extra:
+                        r = record._extra[colname]
+                        row.append(TD(r))
+                        continue
+                    else:
+                        raise KeyError("Column %s not found (SQLTABLE)" % colname)
+                (tablename, fieldname) = colname.split('.')
+                try:
+                    field = sqlrows.db[tablename][fieldname]
+                except KeyError:
+                    field = None
+                if tablename in record \
+                        and isinstance(record,Row) \
+                        and isinstance(record[tablename],Row):
+                    r = record[tablename][fieldname]
+                elif fieldname in record:
+                    r = record[fieldname]
+                else:
+                    raise SyntaxError, 'something wrong in Rows object'
+                r_old = r
+                if not field:
+                    pass
+                elif linkto and field.type == 'id':
+                    try:
+                        href = linkto(r, 'table', tablename)
+                    except TypeError:
+                        href = '%s/%s/%s' % (linkto, tablename, r_old)
+                    r = A(r, _href=href)
+                elif field.type.startswith('reference'):
+                    if linkto:
+                        ref = field.type[10:]
+                        try:
+                            href = linkto(r, 'reference', ref)
+                        except TypeError:
+                            href = '%s/%s/%s' % (linkto, ref, r_old)
+                            if ref.find('.') >= 0:
+                                tref,fref = ref.split('.')
+                                if hasattr(sqlrows.db[tref],'_primarykey'):
+                                    href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref:r}))
+                        if field.represent:
+                            r = A(field.represent(r), _href=str(href))
+                        else:
+                            r = A(str(r), _href=str(href))
+                    elif field.represent:
+                        r = field.represent(r)
+                elif linkto and hasattr(field._table,'_primarykey') and fieldname in field._table._primarykey:
+                    # have to test this with multi-key tables
+                    key = urllib.urlencode(dict( [ \
+                                ((tablename in record \
+                                      and isinstance(record, Row) \
+                                      and isinstance(record[tablename], Row)) and
+                                 (k, record[tablename][k])) or (k, record[k]) \
+                                    for k in field._table._primarykey ] ))
+                    r = A(r, _href='%s/%s?%s' % (linkto, tablename, key))
+                elif field.type.startswith('list:'):
+                    r = field.represent(r or [])
+                elif field.represent:
+                    r = field.represent(r)
+                elif field.type == 'blob' and r:
+                    r = 'DATA'
+                elif field.type == 'upload':
+                    if upload and r:
+                        r = A('file', _href='%s/%s' % (upload, r))
+                    elif r:
+                        r = 'file'
+                    else:
+                        r = ''
+                elif field.type in ['string','text']:
+                    r = str(field.formatter(r))
+                    ur = unicode(r, 'utf8')
+                    if truncate!=None and len(ur) > truncate:
+                        r = ur[:truncate - 3].encode('utf8') + '...'
+                row.append(TD(r))
+            tbody.append(TR(_class=_class, *row))
+        components.append(TBODY(*tbody))
+
+form_factory = SQLFORM.factory # for backward compatibility, deprecated
+
+

File web2pylibs/validators.py

     ]
 
 def translate(text):
-    if isinstance(text,(str,unicode)):
-        from globals import current
-        if hasattr(current,'T'):
-            return current.T(text)
+    #if isinstance(text,(str,unicode)):
+        #from globals import current
+        #if hasattr(current,'T'):
+            #return current.T(text)
     return text
 
 def options_sorter(x,y):