My top secret (non-commercial) project is getting places, through no reason beyond that Python and its cohorts Webware, SQLObject, Cheetah, and FunFormKit really helps overcome procrastination by turning the most minimal effort into something useful. Back in the mood to explore, I investigated using an adapter registry to build FunFormKit forms.

I'm not sure if this is the best way to approach the problem, but I realised I was using the exact same method to list entities, and build forms to allow a user to create/edit them, and code to save them once done. I'm always happy if I can turn 80 lines of code per page into 4.

from sqlobject import *

from zope.interface.adapter import AdapterRegistry
from zope.interface import classImplements, implements, Interface

registry = AdapterRegistry()

class SiteSQLObject(SQLObject):
    columnsInOrder = []

    def ffkFields(cls):
        ret = []
        columnDict = {}
        for col in cls._columns:
            columnDict[col.kw['name']] = col

        for colName in cls.columnsInOrder:
            col = columnDict[colName]
            display = registry.queryAdapter(col, IColumnDisplay, '')
            ret.append(display.getField())
        return ret

    ffkFields = classmethod(ffkFields)

class ICol(Interface):
    pass

class IBoolCol(Interface):
    pass

class IStringCol(Interface):
    pass

class IColumnDisplay(Interface):
    pass

from FunFormKit.Form import *
from FunFormKit.Field import *

class ColumnDisplay:
    implements(IColumnDisplay)

    def __init__(self, context):
        print "DefaultColumnDisplay: %s" % (context)
        self.context = context

class DefaultColumnDisplay(ColumnDisplay):
    def getField(self):
        return TextField(self.context.kw['name'], size=20, maxLength=100)

class BoolColumnDisplay(ColumnDisplay):
    def getField(self):
        return CheckboxField(self.context.kw['name'])

class StringColumnDisplay(ColumnDisplay):
    def getField(self):
        length = self.context.kw.get('length', None)
        if not length:
            return TextareaField(self.context.kw['name'], rows=10, cols=40, wrap="SOFT")
        size = 20
        if length < size:
            size = length
        return TextField(self.context.kw['name'], size=size, maxLength=length)


classImplements(Col, ICol)
classImplements(BoolCol, IBoolCol)
classImplements(StringCol, IStringCol)
registry.register([ICol], IColumnDisplay, '', DefaultColumnDisplay)
registry.register([IBoolCol], IColumnDisplay, '', BoolColumnDisplay)
registry.register([IStringCol], IColumnDisplay, '', StringColumnDisplay)

I'm pondering just using a dictionary of functions using the Col classes as keys in this case. Perhaps adapters are a bit overkill for this.

The objects just inherit from SiteSQLObject and set up columnsInOrder:

from sitesqlobject import *
from sql import connection

class User(SiteSQLObject):
    _connection = connection
    username = StringCol(length=100, alternateID=True)
    name = StringCol(length=100, alternateID=True)
    password = StringCol(length=100)
    admin = BoolCol(default=False)
    columnsInOrder = ['username', 'name', 'password', 'admin']

User.createTable(ifNotExists = True)
if User.select().count() == 0:
    u = User(username="admin", password="admin", name="Mr.  Administrator", admin=True)
from sitesqlobject import *
from sql import connection

class User(SiteSQLObject):
    _connection = connection
    username = StringCol(length=100, alternateID=True)
    name = StringCol(length=100, alternateID=True)
    password = StringCol(length=100)
    admin = BoolCol(default=False)
    columnsInOrder = ['username', 'name', 'password', 'admin']

User.createTable(ifNotExists = True)
if User.select().count() == 0:
    u = User(username="admin", password="admin", name="Mr.  Administrator", admin=True)

The Webware page does something like this:

fields = Person.ffkFields()
fields.append(SubmitButton('submit', description='Save'))

def filterData(data):
    try: del data["submit"]
    except: pass
    return data

class users(WebwarePage, FormServlet):

...

    def content_template(self):
        try:
            u = User.byUsername(user)
        except:
            return self.sendRedirectAndEnd('/admin/users/')

        submitted, data = self.processForm()
        if str(data) == data:
            data = {}

        data = filterData(data)

        if submitted:
            u.set(**data)

        if user != u.username:
            return self.sendRedirectAndEnd('/admin/users/%s' % (u.username))

        defaults = {}
        for column in p._columns:
            name = column.kw['name']
            defaults[name] = getattr(p, name)

        df = self.renderableForm(defaults = defaults)
        dl = df.htFormTable(bgcolor = '#dddddd')

        mySearchList = {
            'person': p,
            'dl': dl,
        }
        return self.userTemplate(searchList=[mySearchList, self])

I imagine I can get it down to two classes - one that handles lists and edits, and one that handles creating anew. Then I can create new simple entity types without a problem, and focus on more fun things. Like actually finishing the project in time for the conference presentation I'm planning to give on it...