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...