Easy to Understand web2py Grid Custom Search

web2py Grid Custom Search WITHOUT specifying a custom search_widget

The custom_search.html view contains the EASIER TO UNDERSTAND customization code. Here is the technique.

  1. Make the SQLFORM.grid’s Standard Search Input hidden.
  2. Define Custom Search Input elements with onchange events that send their values to the to the hidden Standard Search Input.
  3. Insert the Custom Search Input elements after the Standard Search Input (“#w2p_keywords”) using jQuery .insertAfter().
    • This prevents them from showing up on Edit or View pages.
    • Insert them in reverse order of them appearing on the page.

You can find an older version of this on web2pyslices.com

Here is the Controller code. Note the absence of a custom search_widget argument in the grid function call.

# in default.py Controller
def custom_search():
    '''
    Implements SQLFORM.grid custom search 
        WITHOUT specifying a custom search_widget,
            and so needing to read & understand the clever web2py implementation source code.
    The custom_search.html view contains the EASIER TO UNDERSTAND customization code.
    The technique:
        1. Make the grid's Standard Search Input hidden.
        2. Define Custom Search Input elements 
            with onchange events that 
                send their values to the to the hidden Standard Search Input.
    '''
    query=((db.contact.id > 0))
    fields = (db.contact.id, 
        db.contact.l_name, 
        db.contact.f_name, 
        db.contact.prime_phone,
        db.contact.date_modified,
        )

    headers = {'contact.id':   'ID',
           'contact.l_name': 'Last Name',
           'contact.f_name': 'First Name',
           'contact.prime_phone': 'Primary Phone',
           'contact.date_modified': 'Info Last Updated',
           }    
    init_sort_order=[db.contact.l_name]   

    grid = SQLFORM.grid(query=query, 
        fields=fields, 
        headers=headers, 
        orderby=init_sort_order,
        searchable=True,  
        user_signature=False, 
        create=True, deletable=False, editable=True, maxtextlength=100, paginate=25)

    return dict(grid=grid)    

Here is the View code.

<!-- In custom_search.html view -->
{{extend 'layout.html'}}
{{block head}}
{{super}}
<script>

function phoneSrch(){
    var srch ='contact.prime_phone contains '+'"'+jQuery('#joephone').val()+'"';
    $("#w2p_keywords").val(srch);
}
function lnameSrch(){
    var srch ='contact.l_name starts with '+'"'+jQuery('#joelname').val()+'"';
    $("#w2p_keywords").val(srch);
}

$(document).ready(function(){
  // Make the Grid Standard Search Input hidden  
  $("#w2p_keywords").prop("type", "hidden");   

  // Insert the Custom Search Input elements after 
  //     the Standard Search Input ("#w2p_keywords")
  //     using jQuery .insertAfter().
  //     This prevents them from showing up on Edit or View pages.
  //     Insert them in reverse order of them appearing on the page.
  var input2Str  = '<div class="joeinputclass" style="padding-bottom:10px;" >';
  input2Str += '<span class="joelabelclass" >Primary Phone contains: ';
  input2Str += '</span><input name="joephone" id="joephone" type="text" ';
  input2Str += 'onchange="phoneSrch()" style="width:150px;" ><br/></div>';
  $(input2Str).insertAfter("#w2p_keywords");
  var input1Str  = '<div class="joeinputclass" style="padding-bottom:10px;">';
  input1Str += '<span class="joelabelclass" style="padding-right:18px;" >';
  input1Str += 'Last Name starts with: </span><input name="joelname" ';
  input1Str += 'id="joelname" type="text"  onchange="lnameSrch()" ';
  input1Str += 'style="width:150px;" ></div>';
  $(input1Str).insertAfter("#w2p_keywords");
});

</script>
{{end}}
<h2>Contacts</h2>
<div id="theweb2pygrid">
{{=grid}}
</div>

Good contact Table

##Contact Table Def
Here’s a good contact table def i want to remember.
- Automatic change for Info Last Updated datetime field.
- Uses represent

db.define_table("contact",
    Field("f_name", "string",  requires=IS_NOT_EMPTY() ),
    Field("l_name", "string", requires=IS_NOT_EMPTY() ),
    Field("m_initial", "string", default=None),
    Field('prime_phone', requires=IS_NOT_EMPTY(), comment='phone eg: (650) 555-1212'),
    Field('other_phone',   comment='phone eg: (650) 555-1212'),
    Field('email', requires=IS_EMPTY_OR(IS_EMAIL()) ),    
    #Field('ca_address', 'reference ca_address', writable=False, readable=False),
    Field("address_line1", "string", requires=IS_NOT_EMPTY() ),
    Field("address_line2", "string"),
    Field("city", "string", requires=IS_NOT_EMPTY() ),
    Field("state", "string", length=2, default='CA', writable=False),
    Field("zip", "string", length=5, default=None),
    Field("date_modified", "datetime", default=datetime.datetime.now(),
        update=request.now,   # working - needed below line
        writable=False, readable=True,
    # http://strftime.org/   AND http://stackoverflow.com/questions/9678172/modify-column-output-for-sqlform-grid-in-web2py
        represent=lambda x, row: x.strftime("%c"),
        comment='Last change date of contact info.'),

      ) 

Debug web2py Actions with iPython

My Notes re debugging web2py Actions with iPython

My system

  • Using web2py 2.9.5-stable+timestamp.2014.03.16.02.35.39 (Running on Rocket 1.2.6, Python 2.7.5)
  • On Windows XP in C:\web2py

iPython log

Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.

IPython 0.13.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: cd C:\web2py
C:\web2py

In [2]: from gluon import shell

In [3]: shell.run('fish/default/view_or_add_client_trips/?client_id=2', plain=True, import_models=True)
{'grid': <gluon.html.DIV object at 0x0289C930>}

In [4]:

Updated web2py DAL Basic Examples

DAL Updated Examples

Content

Table: Define [a Table], Insert, Select, Update, Count, Delete, Drop [a Table]

Also See in the web2py Book:

# make this function now for later result printing
def p(rows):
    for r in rows:
        print r.name, r.addr, r.city

# Content: Define, Insert, Select, Update, Count, Delete, Drop

# Define [a Table]
db.define_table('person', Field('name'), Field('addr'), Field('city') )   # person has 4 fields person.id + person.name .addr .city

# Insert
db.person.insert( **{'name':'joe', 'addr':'5 Blue St', 'city':'waco'} )
db.person.insert( **{'name':'jim', 'addr':'6 Red St', 'city':'waco'} )
db.person.insert( **{'name':'john', 'addr':'7 Green St', 'city':'waco'} )
db.person.insert( **{'name':'jack', 'addr':'7 Green St', 'city':'houston'} )
db.person.insert( **{'name':'jackie', 'addr':'7 Green St', 'city':'houston'} )
db.person.insert( **{'name':'jill', 'addr':'7 Green St', 'city':'austin'} )
db.person.insert( **{'name':'jane', 'addr':'9 Brown St', 'city':'austin'} )


# Select        
# select all rows in person
rows = db(db.person.id > 0).select()
In [55]: p(rows)
joe 5 Blue St waco
jim 6 Red St waco
john 7 Green St waco
jack 7 Green St houston
jackie 7 Green St houston
jill 7 Green St austin
jane 9 Brown St austin

# select rows in city is waco
rows = db(db.person.city == 'waco').select()
In [61]: p(rows)
joe 5 Blue St waco
jim 6 Red St waco
john 7 Green St waco

# select rows in city is 'waco' AND addr is '7 Green St'
rows = db( (db.person.city == 'waco') & (db.person.addr == '7 Green St') ).select()
In [73]: p(rows)
john 7 Green St waco

# select rows in city is 'houston' OR name is 'joe'
rows = db( (db.person.city == 'houston') | (db.person.name == 'joe') ).select()
In [83]: p(rows)
joe 5 Blue St waco
jack 7 Green St houston
jackie 7 Green St houston

# Update  ->   get an id;   do the update
# update 1    update spelling jackie's name to 'jaquay'
id = db( (db.person.name=='jackie') & (db.person.addr=='7 Green St') & (db.person.city=='houston') ).select().first().id 
rtn = db.person[id] = dict(name='jaquay', addr='7 Green St', city='houston')

# update 1 result
In [87]: rtn
Out[87]: {'addr': '7 Green St', 'city': 'houston', 'name': 'jaquay'}
In [88]: rows = db(db.person.id > 0).select()
In [89]: p(rows)
joe 5 Blue St waco
jim 6 Red St waco
john 7 Green St waco
jack 7 Green St houston
jaquay 7 Green St houston
jill 7 Green St austin
jane 9 Brown St austin

# update 2    all '7 Green St's in 'houston' to '7 Yellow St' in 'houston'
rows = db( (db.person.addr=='7 Green St') & (db.person.city=='houston') ).select()
for row in rows:
    rtn = db.person[row.id] = dict(addr='7 Yellow St', city='houston')
    print rtn
# update 2 result
rows = db(db.person.id > 0).select()
In [94]: p(rows)
joe 5 Blue St waco
jim 6 Red St waco
john 7 Green St waco
jack 7 Yellow St houston
jaquay 7 Yellow St houston
jill 7 Green St austin
jane 9 Brown St austin


# Count

# count 1    items in city is 'houston' OR name is 'joe'
In [97]: print db( (db.person.city == 'houston') | (db.person.name == 'joe') ).count()
3
# notice relationship to .select()
In [98]: print db( (db.person.city == 'houston') | (db.person.name == 'joe') ).select()
person.id,person.name,person.addr,person.city
1,joe,5 Blue St,waco
4,jack,7 Yellow St,houston
5,jaquay,7 Yellow St,houston

# count 2    items where name startswith 'ja'
In [99]: print db( (db.person.name.startswith('ja'))  ).count()
3
In [100]: print db( (db.person.name.startswith('ja'))  ).select()
person.id,person.name,person.addr,person.city
4,jack,7 Yellow St,houston
5,jaquay,7 Yellow St,houston
7,jane,9 Brown St,austin

# Delete   delete all items where name startswith('jo')  notice similarity to select()

rtn = db( (db.person.name.startswith('jo'))  ).delete()
In [105]: print rtn
2
rows = db(db.person.id > 0).select()
In [107]: p(rows)
jim 6 Red St waco
jack 7 Yellow St houston
jaquay 7 Yellow St houston
jill 7 Green St austin
jane 9 Brown St austin


# Drop [a Table]   drop the table from the db
db.person.drop()


see_also = '''
[Other operators](http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Other-operators)
[Combining rows](http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Combining-rows)
[list:<type> and contains](http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#list--type--and-contains)
'''

web2py Field Exegesis

web2py Field Exegesis

Joe Dorocak aka Joe Codeswell has written working Python code examples exploring “fully qualified” web2py Field constructors.

I wrote this to turn the web2py book chapter on the Field constructor into working, “fully qualified” examples, so I could understand the meaning and explore the features. There are 2 “fully qualified” examples in mytable for now:

  • fulldefaultstrfield – is my cut at fully qualifying the default settings for the Field constructor named parameters.
  • fullplay1strfield – is my 1st cut at playing around with the values of the named parameters in the Field constructor.

The results can be seen by implementing this locally and navigating to:

Content

  • In applications/fields/controllers/default.py
  • In applications/fields/models/db.py

In applications/fields/controllers/default.py

# -*- coding: utf-8 -*-
# In applications/fields/controllers/default.py

def gridmytable():
    grid = SQLFORM.grid(db.mytable)
    return locals()

In applications/fields/models/db.py

# -*- coding: utf-8 -*-
# In applications/fields/models/db.py

# web2py Field Exegesis
# Joe Dorocak aka Joe Codeswell has written working Python code examples exploring "fully qualified" web2py Field constructors. 
# I wrote this to turn the web2py book chapter on the Field constructor into working, "fully qualified" examples, so i could understand the meaning and explore the features. There are 2 "fully qualified" examples in mytable for now.
#  - fulldefaultstrfield - is my cut at fullyqualifying the default settings for the Field constructor named parameters.
#  - fullplay1strfield   - is my 1st cut at playing around with the values of the named parameters in the Field constructor.
# The results can be seen by implementing this locally and navigating to
#   -  inserting new records at   http://127.0.0.1:8000/fields/appadmin/select/db 
#   -  seeing the table grid at   http://127.0.0.1:8000/fields/default/gridmytable


# BOILERPLATE
if not request.env.web2py_runtime_gae:
    db = DAL('sqlite://storage.sqlite',pool_size=1,check_reserved=['all'])
else:
    db = DAL('google:datastore')
    session.connect(request, response, db=db)

response.generic_patterns = ['*'] if request.is_local else []

from gluon.tools import Auth, Crud, Service, PluginManager, prettydate
auth = Auth(db)
crud, service, plugins = Crud(db), Service(), PluginManager()

## create all tables needed by auth if not custom tables
auth.define_tables(username=False, signature=False)
# END BOILERPLATE



# START HERE
db.define_table('mytable',

        Field('myfield','string'),

        # see http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Field-constructor
        # a fully defined field example that will HOPEFULLY work
        # Not all of the parameters are relevant for every field type.
        Field(
         'fulldefaultstrfield',        # Field name positional arg
          type='string',               # Field type see field_types below

          # Sets the maximum length of a "string", "password" or "upload" field.
          #     See "2.6.1 - 2.6.4" in  http://www.web2py.com/init/default/changelog
          #         "MySQL users: The length of string fields changed from 255 to 512 bytes. 
          length=512,       

          default='',                  # Default value used to pre-populate forms, can instead be a [lambda] function 
          required=False,              # True => A val for field MUST be specified at record insertion.
          requires=IS_LENGTH(512),     # Is a validator or a list of validators. see book /forms-and-validators#Validators. see field_types below    # joeNote: bad_value == '<DEFAULT>'
          ondelete='CASCADE',          # Defines actions on record delete. CASCADE => delete all referring records
          notnull=False,               # True => fulldeffield can't be Null at record insertion.
          unique=False,                # True => must be unique in the table [not quite the same as IS_NOT_IN_DB]
          uploadfield=True,            # Applies only to fields of type "upload" [Even though True is default and works for type=='string']. See upload_field_type_notes below

          # Must be one of the available widget objects, including custom widgets. 
          # Each field type has a default widget. See widgetList below   
          # This is default for fulldeffield.type=='string'          
          widget=SQLFORM.widgets.string.widget, 

          label=None,                  # Is a string (or a helper or something that can be serialized to a string) that contains the label to be used for this field in auto-generated forms.
                                       #     JoeNote: There's a diff between label=None => default label & label='' => NO LABEL SHOWN!
          comment='',                  # Is a string (or a helper or something that can be serialized to a string) that contains a comment associated with this field, and will be displayed to the right of the input field in the autogenerated forms. 
                                       #      JoeNote: No diff between comment='' & comment=None
          # If a field is neither readable nor writable, it will not be displayed in create and update forms.
          writable=True,               # Declares whether a field is writable in forms.
          readable=True,               # Declares whether a field is readable in forms.

          update=None,                 # Contains the default value for this field when the record is updated.

          authorize=None,              # For "upload" fields only, can be used to require access control on the corresponding field. See Authentication and also Authorization http://web2py.com/books/default/chapter/29/09/access-control.
          autodelete=False,            # For "upload" fields only, determines if the corresponding uploaded file should be deleted when the record referencing the file is deleted. 

          represent=None,              # Can be None or can point to a function that takes a field value and returns an alternate representation for the field value. See representation_examples below.         
          compute=None,                # See compute_notes below.

          uploadfolder=None,           # See upload_field_type_notes below
          uploadseparate=None,         # See upload_field_type_notes below
          uploadfs=None                # See upload_field_type_notes below
        ), 

        # joe says do a diff on following & above to hilight what changed
        #    i think i changed: fieldname, length, requires, label, comment, represent
        # need to provide insert & rendering actions to see diffs in result

        # see http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Field-constructor
        # a fully defined field example that will HOPEFULLY work
        # Not all of the parameters are relevant for every field type.
        Field(
         'fullplay1strfield',          # Field name positional arg
          type='string',               # Field type see field_types below

          # Sets the maximum length of a "string", "password" or "upload" field.
          #     See "2.6.1 - 2.6.4" in  http://www.web2py.com/init/default/changelog
          #         "MySQL users: The length of string fields changed from 255 to 512 bytes. 
          length=4096,       

          default='',                  # Default value used to pre-populate forms, can instead be a [lambda] function 
          required=False,              # True => A val for field MUST be specified at record insertion.
          requires=IS_LENGTH(4096),    # Is a validator or a list of validators. see book /forms-and-validators#Validators. see field_types below    # joeNote: bad_value == '<DEFAULT>'
          ondelete='CASCADE',          # Defines actions on record delete. CASCADE => delete all referring records
          notnull=False,               # True => fulldeffield can't be Null at record insertion.
          unique=False,                # True => must be unique in the table [not quite the same as IS_NOT_IN_DB]
          uploadfield=True,            # Applies only to fields of type "upload" [Even though True is default and works for type=='string']. See upload_field_type_notes below

          # Must be one of the available widget objects, including custom widgets. 
          # Each field type has a default widget. See widgetList below   
          # This is default for fulldeffield.type=='string'          
          widget=SQLFORM.widgets.string.widget, 

          label='lblPlay1strfield',    # Is a string (or a helper or something that can be serialized to a string) that contains the label to be used for this field in auto-generated forms.
                                       #     JoeNote: There's a diff between label=None => default label & label='' => NO LABEL SHOWN!
          comment='cmtPlay1strfield',  # Is a string (or a helper or something that can be serialized to a string) that contains a comment associated with this field, and will be displayed to the right of the input field in the autogenerated forms. 
                                       #      JoeNote: No diff between comment='' & comment=None
          # If a field is neither readable nor writable, it will not be displayed in create and update forms.
          writable=True,               # Declares whether a field is writable in forms.
          readable=True,               # Declares whether a field is readable in forms.

          update=None,                 # Contains the default value for this field when the record is updated.

          authorize=None,              # For "upload" fields only, can be used to require access control on the corresponding field. See Authentication and also Authorization http://web2py.com/books/default/chapter/29/09/access-control.
          autodelete=False,            # For "upload" fields only, determines if the corresponding uploaded file should be deleted when the record referencing the file is deleted. 

          # Can be None or can point to a function that takes a field value and returns an alternate representation for the field value. See representation_examples below.         
          # represent=lambda fullplay1strfield,row: fullplay1strfield.capitalize(),              
          #     result ticket: File "C:/web2py/applications/fields/models/db.py", line 194, in <lambda>
          #         represent=lambda fullplay1strfield,row: fullplay1strfield.capitalize(),
          #         AttributeError: 'NoneType' object has no attribute 'capitalize'
          #    it was None

          represent=lambda fullplay1strfield,row: fullplay1strfield.capitalize(),
          compute=None,                # See compute_notes below.

          uploadfolder=None,           # See upload_field_type_notes below
          uploadseparate=None,         # See upload_field_type_notes below
          uploadfs=None                # See upload_field_type_notes below
        ), 



    )



# see http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Field-types
field_types = '''
field_type              default_field_validators
string                  IS_LENGTH(length) default length is 512
text                    IS_LENGTH(65536)
blob                    None
boolean                 None
integer                 IS_INT_IN_RANGE(-1e100, 1e100)
double                  IS_FLOAT_IN_RANGE(-1e100, 1e100)
decimal(n,m)            IS_DECIMAL_IN_RANGE(-1e100, 1e100)
date                    IS_DATE()
time                    IS_TIME()
datetime                IS_DATETIME()
password                None
upload                  None
reference <table>     IS_IN_DB(db,table.field,format)
list:string             None
list:integer            None
list:reference <table>    IS_IN_DB(db,table.field,format,multiple=True)
json                    IS_JSON()
bigint                  None
big-id                  None
big-reference           None
'''

upload_field_type_notes = '''
    uploadfield:
        applies only to fields of type "upload". A field of type "upload" stores the name of a file saved somewhere else, by default on the filesystem under the application "uploads/" folder. If uploadfield is set to True, then the file is stored in a blob field within the same table and the value of uploadfield is the name of the blob field. This will be discussed in more detail later in the context of SQLFORM.
    uploadfolder:
        defaults to the application's "uploads/" folder. If set to a different path, files will uploaded to a different folder. For example,
            Field(...,uploadfolder=os.path.join(request.folder,'static/temp'))
        will upload files to the "web2py/applications/myapp/static/temp" folder.
    uploadseparate:
        if set to True will upload files under different subfolders of the uploadfolder folder. This is optimized to avoid too many files under the same folder/subfolder. ATTENTION: You cannot change the value of uploadseparate from True to False without breaking links to existing uploads. web2py either uses the separate subfolders or it does not. Changing the behavior after files have been uploaded will prevent web2py from being able to retrieve those files. If this happens it is possible to move files and fix the problem but this is not described here.
    uploadfs:
        allows you specify a different file system where to upload files, including an Amazon S3 storage or a remote SFTP storage. This option requires PyFileSystem installed. uploadfs must point to PyFileSystem. uploadfs
'''

widgetList = [
# See http://web2py.com/books/default/chapter/29/07/forms-and-validators?search=widgets#Widgets
# Here is a list of available web2py widgets:
    SQLFORM.widgets.string.widget,         # default for corresonding type
    SQLFORM.widgets.text.widget,           # default for corresonding type
    SQLFORM.widgets.password.widget,       # default for corresonding type
    SQLFORM.widgets.integer.widget,        # default for corresonding type
    SQLFORM.widgets.double.widget,         # default for corresonding type
    SQLFORM.widgets.time.widget,           # default for corresonding type
    SQLFORM.widgets.date.widget,           # default for corresonding type
    SQLFORM.widgets.datetime.widget,       # default for corresonding type
    SQLFORM.widgets.upload.widget,         # default for corresonding type
    SQLFORM.widgets.boolean.widget,        # default for corresonding type

    # The next 2 widgets are used when a field's requires is IS_IN_SET or IS_IN_DB
    # The "options" widget is used with multiple=False (default behavior), when a field's requires is IS_IN_SET or IS_IN_DB.
    SQLFORM.widgets.options.widget,
    # The "multiple" widget is used with multiple=True, when a field's requires is IS_IN_SET or IS_IN_DB. 
    SQLFORM.widgets.multiple.widget,

    # The "radio" and "checkboxes" widgets are never used by default, but can be set manually. 
    SQLFORM.widgets.radio.widget,    
    SQLFORM.widgets.checkboxes.widget,

    # See http://web2py.com/books/default/chapter/29/07/forms-and-validators?search=widgets#Autocomplete-widget
    SQLFORM.widgets.autocomplete,
    ]

representation_examples = '''
db.mytable.name.represent = lambda name,row: name.capitalize()
db.mytable.other_id.represent = lambda id,row: row.myfield
db.mytable.some_uploadfield.represent = lambda value,row:     A('get it', _href=URL('download', args=value))

"blob" fields are also special. 
By default, binary data is encoded in base64 before being stored into the actual database field, and it is decoded when extracted. 
This has the negative effect of using 25% more storage space than necessary in blob fields, but has two advantages. 
On average it reduces the amount of data communicated between web2py and the database server, and it makes the communication independent of back-end-specific escaping conventions.
'''    

compute_notes = '''
from http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Computed-fields
Computed fields

DAL fields may have a compute attribute. This must be a function (or lambda) that takes a Row object and returns a value for the field. When a new record is modified, including both insertions and updates, if a value for the field is not provided, web2py tries to compute from the other field values using the compute function. Here is an example:

>>> db.define_table('item',
        Field('unit_price','double'),
        Field('quantity','integer'),
        Field('total_price',
            compute=lambda r: r['unit_price']*r['quantity']))
>>> r = db.item.insert(unit_price=1.99, quantity=5)
>>> print r.total_price
9.95

Notice that the computed value is stored in the db and it is not computed on retrieval, as in the case of virtual fields, described later. Two typical applications of computed fields are:

    in wiki applications, to store the processed input wiki text as HTML, to avoid re-processing on every request
    for searching, to compute normalized values for a field, to be used for searching.

Computed fields are evaluated in the order in which they are defined in the table definition. A computed field can refer to previously defined computed fields (new after v 2.5.1)
Virtual fields

Virtual fields are also computed fields ...
    see: http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#Virtual-fields
'''

grid_notes = '''

SQLFORM.grid signature

http://web2py.com/books/default/chapter/29/07/forms-and-validators#SQLFORM-grid-signature

The complete signature for the grid is the following:

SQLFORM.grid(
    query,
    fields=None,
    field_id=None,
    left=None,
    headers={},
    orderby=None,
    groupby=None,
    searchable=True,
    sortable=True,
    paginate=20,
    deletable=True,
    editable=True,
    details=True,
    selectable=None,
    create=True,
    csv=True,
    links=None,
    links_in_grid=True,
    upload='<default>',
    args=[],
    user_signature=True,
    maxtextlengths={},
    maxtextlength=20,
    onvalidation=None,
    oncreate=None,
    onupdate=None,
    ondelete=None,
    sorter_icons=(XML('↑'), XML('↓')),
    ui = 'web2py',
    showbuttontext=True,
    _class="web2py_grid",
    formname='web2py_grid',
    search_widget='default',
    ignore_rw = False,
    formstyle = 'table3cols',
    exportclasses = None,
    formargs={},
    createargs={},
    editargs={},
    viewargs={},
    buttons_placement = 'right',
    links_placement = 'right'
    )

...
JoeNite: below => see SQLFORM

http://web2py.com/books/default/chapter/29/07/forms-and-validators#SQLFORM


http://web2py.com/books/default/chapter/29/07/forms-and-validators#SQLFORM-and-insert-update-delete

            google: SQLFORM for select

https://groups.google.com/forum/#!topic/web2py/VKvw3Mn-mNQ

Because the edit/create form is an SQLFORM which extends FORM, these callbacks are essentially used in the same way as documented in the sections for FORM and SQLFORM.

Here is skeleton code:

def myonvalidation(form):
    print "In onvalidation callback"
    print form.vars
    form.errors= True  #this prevents the submission from completing

    #...or to add messages to specific elements on the form
    form.errors.first_name = "Do not name your child after prominent deities" 
    form.errors.last_name = "Last names must start with a letter"
    response.flash = "I don't like your submission" 

def myoncreate(form):
    print 'create!'
    print form.vars

def myonupdate(form):
    print 'update!'
    print form.vars

def myondelete(table, id):
    print 'delete!'
    print table, id

onupdate and oncreate are the same callbacks available to SQLFORM.process()    
'''    

web2py TO WordPress API with OAuth2

web2py TO WordPress API with OAuth2

This is an example of a web2py app using the WordPress.com REST API with OAuth2.

You need to go to developer.WordPress.com to setup your app.

N.B. I tested this on my website, however, if I messed up with writing this article, please let me know in the comments.

I found some hints on github Automattic

Love and peace,

Joe

controllers

default.py

import datetime, random, string
import urllib, urllib2
import requests

# TODO: Replace these globals with your own.
HOST      = 'yoursite.com'        # REGISTER with developer.wordpress.com
APPNAME   = 'yourappname'         # REGISTER with developer.wordpress.com
CLIENT_SECRET = 'BIG.LONG.STRING' # GET FROM developer.wordpress.com
CLIENT_ID = '12345'               # GET FROM developer.wordpress.com


def authorize_blog():
    """
    Initiates retrieval of OAuth2 credentials which 
        allows your user to aurhorize her blog for use by your app.   
    Generates an HTML page containing a WordPress Authorize button which 
        holds a redirect_uri callback to your app.
    When your user presses this button, she is sent to WordPress.com, where,
        IF she AUTHORIZES one of her blogs:
            WordPress.com will send OAuth2 credentials to YOUR callback URL.   
    see: http://developer.wordpress.com/docs/oauth2/            
    """

    # see [OAuth2 state Article on stackoverflow] (http://stackoverflow.com/questions/11071482/oauth2-0-server-stack-how-to-use-state-to-prevent-csrf-for-draft2-0-v20)
    state = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(30))
    state = str(datetime.datetime.now()).replace(':', '.').replace(' ', '') + '_' + state
    session.state = state


    params = {
        "response_type": "code",
        "client_id": CLIENT_ID,
        "redirect_uri": URL(f='connected', scheme='http', host=HOST, url_encode=False),
        "state": state,
    }

    authorize_url_0 = URL(a='oauth2', c='authorize', f='', vars=params, scheme='https', host='public-api.wordpress.com', url_encode=False)

    # The web2py URL function inserts an (in this case) UNNEEDED reference to the current web2py app.
    #     This string function gets rid of that reference. 
    authorize_url =  authorize_url_0.replace(APPNAME+'/', '',1)

    thehtml = XML(
        '<html><body><h2>Connect FROM YourWebsite.com TO WordPress.com</h2><a href="' +
        authorize_url +
        '"><img src="//s0.wp.com/i/wpcc-button.png" width="231" /></a></body></html>'
    ).xml()

    return thehtml

def connected():
    """
    1. Receive request.vars.code == a "time-limited code that your application can exchange for a full authorization token."
    see: http://developer.wordpress.com/docs/oauth2/   
        This is a time-limited code that your application can exchange for a full authorization token. 
        To do this you will need to pass the code to the token endpoint by making a POST request to the token endpoint: 
            You are required to pass 
                client_id, 
                client_secret, and 
                redirect_uri for web applications. 
                    These parameters have to match the details for your application, and 
                        the redirect_uri must match the redirect_uri used during the Authorize step (above). 
                grant_type has to be set to "authorization_code".   
    2. Convert time-limited code into  OAuth2 full authorization credentials.
        [NOT SHOWN: how to store credentials in db]    
    3. For demo purposes, list the blog with the credentials.    
    """

    # 1. Receive request.vars.code
    code = request.vars.code   # returned from WordPress.com
    if not code:
        redirect(URL('index'))

    # see [OAuth2 state Article on stackoverflow] (http://stackoverflow.com/questions/11071482/oauth2-0-server-stack-how-to-use-state-to-prevent-csrf-for-draft2-0-v20)    
    state = request.vars.state
    if not state:
        return dict(message='Warning! State variable missing after authorization.')

    if (not session.state):
        return dict(message='Warning! No session.atate! WHY NOT??? variable missing after authorization.')

    if state != session.state:
        return dict(message='Warning! State mismatch. Authorization attempt may have been compromised. This: ' + state + ' should be: ' + session.state)


    # 2. Convert time-limited code
    # TODO: Replace values client_id, client_secret with YOUR's, previously received from dev.WordPress.com setup.
    payload = { 'client_id': CLIENT_ID, 
                'client_secret': CLIENT_SECRET,
                'redirect_uri': 'http://'+HOST+'/'+APPNAME+'/default/connected',
                'grant_type': 'authorization_code',
                'code': request.vars.code,
    }

    # Call the token endpoint
    rsp_token_request = requests.post("https://public-api.wordpress.com/oauth2/token", data=payload)

    # 'Response [200]' means SUCCESS
    if not 'Response [200]' in str(rsp_token_request):     
        return dict(message='Problem! "Response [200]" not in response!')

    jsonDict     = rsp_token_request.json()

    access_token = jsonDict['access_token']
    blog_id      = jsonDict['blog_id']
    blog_url     = jsonDict['blog_url']

    # TODO: [NO SHOWN] Store access_token, blog_id, blog_url in the db for further use.   

    # For demo purposes, we will use these credentials here, to list this blog.
    base_url = 'https://public-api.wordpress.com/rest/v1/sites/' + blog_id
    payload = {}
    payload['http_envelope'] = 'true'
    url_values = urllib.urlencode(payload)
    full_url = base_url + '?' + url_values
    req = urllib2.Request(full_url)   
    req.add_header('Authorization', 'Bearer ' + access_token)
    rsp = urllib2.urlopen(full_url)
    the_info_page = rsp.read()

    m = ''    
    m += "the_info_page = %s||"%(the_info_page)

    content = BEAUTIFY(m.split('||'))   

    return dict(content=content)     

models

I omit model code in this article, assuming you can store your credentials for further use. If you needd assistance, contact me by a comment.

views/default

connected.html

{{left_sidebar_enabled,right_sidebar_enabled=False,False}}
{{extend 'layout.html'}}

{{if 'message' in globals():}}
<h3>{{=message}}</h3>
{{elif 'content' in globals():}}
{{=content}}

{{pass}}

Download Web Files with lxml and urllib

Download Web Files Example

Step-1

Save the page at http://joecodeswell.org/examples/dlwebfiles/index.html to your local machine into a folder named something like “example_folder”. This is the page we will be scraping using lmxl, to find files to download, using urllib. Here is what it looks like.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=windows-1252">
    <title>dlwebfiles</title>
  </head>
  <body>
    <h1>Index of Joe Codeswell examples - dlwebfiles</h1>
    <ul>
      <li><a href="http://joecodeswell.org/examples/dlwebfiles/aveverum.mid">aveverum.mid</a></li>
      <li><a href="http://joecodeswell.org/examples/dlwebfiles/carol.mid">carol.mid</a></li>
      <li><a href="http://joecodeswell.org/examples/dlwebfiles/steiner.mid">steiner.mid</a></li>
    </ul>
  </body>
</html>

Step-2

Put the following Python file into your “example_folder”. Name it “retrieveMidis.py”.

# -*- coding: UTF-8 -*-

# prep the input output 
import os
inHtmlFilePath = os.path.normpath('dlwebfiles.htm')
outDataFolderPath = os.path.join('out', 'mid' )
f = open(inHtmlFilePath, 'r'); htmlStr = f.read(); f.close()

# scrape/parse the html
import lxml.html
tree = lxml.html.fromstring(htmlStr)
theLiList = tree.xpath('/html/body/ul/li')

# prep the download opener
import urllib
opener = urllib.URLopener()


for li in theLiList:
    # see http://www.w3schools.com/xpath/xpath_syntax.asp
    theHref = li.xpath('a')[0].attrib.get('href') 
    theBasename = os.path.basename(theHref)
    theExtension = os.path.splitext(theBasename)[1]
    if len(theBasename) != 0:
        print "theHref = %s"%(theHref)
        print "theBasename = %s"%(theBasename)
        print "len(theBasename) = %s"%(len(theBasename))
        print "theExtension = %s"%(theExtension)
        print "os.path.join(outDataFolderPath,theBasenme) = %s"%(os.path.join(outDataFolderPath,theBasename))
        print 
        print 
        opener.retrieve(theHref, os.path.join(outDataFolderPath,theBasename))

# the end

Step-3

Under your “example_folder” make a sub-directory structure to contain the output as follows. Then run retrieveMidis.py.

example_folder/out/mid