I miss Quixote's object-publisher feel in Webware. I'm beginning to dislike the amount of work I need to do to discover the path that was used to get to my page/servlet. Anyway, since I found Rails' entity URL interface better than mine, I decided I'd steal it.
First problem - understanding what part of the URL was used to get to you. This way, you have a base URL you can use in links for the other functions, as well as being able to have a grip later on about which bits of the URL haven't been consumed yet. Forgive me if I missed something in Webware that does this.
So, I've changed the _respond method...:
def _respond(self, transaction):
eup = self.request().extraURLPath()
eupa = [p for p in posixpath.normpath(eup).split('/') if p and p != '.']
mypath = self.request().urlPath()
if eupa:
if mypath.rfind(eup) != -1:
mypath = mypath[:mypath.rfind(eup)]
if eupa[0]:
if self.handleExtraPathInfo == False:
raise HTTPNotFound
if self.handleExtraPathInfoMethod:
func = getattr(self, "child_" + eupa[0], None)
if not func:
raise HTTPNotFound
transaction.content_template = func
eup = eupa[1:]
transaction.myPath = mypath
transaction.unusedPath = eup
return Page._respond(self, transaction)
I've added a handleExtraPathInfoMethod attribute to my page class, which says whether I expect the extra path info to correspond to a function. If so, I check the function exists (using child_ as a prefix, a la Twisted to avoid unintentional publishing), and attach the function that will be used later to the transation object.
I also save the bit of the URL that corresponds to the page itself.
I'm not really happy - I want to be able to replace just the contents of the page (not the external layer of template), but I'm sure I'd want to replace/change the external layer if I wanted to. But I don't want to create a new class and object, as I like how available shared functions are. So, for now, the function just overrides the contents.
Ok, so now I can make child_list, child_show, child_edit, and child_new in EntityPage for the URL interface.
class EntityPage(WebwarePage, FormServlet):
listTemplate = ListContentTemplate
editTemplate = EditContentTemplate
showTemplate = ShowContentTemplate
handleExtraPathInfo = True
handleExtraPathInfoMethod = True
def child_list(self):
path = '%s/edit' % (self.myPath())
mySearchList = {
'admin': self.getAdmin(),
'path': path,
}
return self.listTemplate(searchList=[mySearchList, self])
def child_edit(self):
path = '%s/edit' % (self.myPath())
if not self.transaction().unusedPath:
self.sendRedirectAndEnd('%s/list' % (self.myPath()))
pathName = self.transaction().unusedPath.pop(0)
try:
item = self._samoosa_class._samoosa_pathnameLookup(pathName)
except:
return self.sendRedirectAndEnd('%s/list' % (self.myPath()))
submitted, data = self.processForm()
if str(data) == data:
data = {}
data = self.filterData(data)
if submitted:
item.set(**data)
newPath = item._samoosa_getPathName()
if pathName != newPath:
return self.sendRedirectAndEnd('%s/%s' % (path, newPath))
defaults = {}
for column in item._columns:
name = column.kw['name']
value = getattr(item, name)
if value is not None and isinstance(value, SQLObject):
value = value.id
defaults[name] = value
df = self.renderableForm(defaults = defaults)
dl = df.htFormTable()
mySearchList = {
'item': item,
'dl': dl,
'admin': self.getAdmin(),
'path': path,
}
return self.editTemplate(searchList=[mySearchList, self])
def child_new(self):
submitted, data = self.processForm()
if str(data) == data:
data = {}
data = self.filterData(data)
if submitted:
item = self._samoosa_class(**data)
return self.sendRedirectAndEnd('%s/edit/%s' % (self.myPath(), item._samoosa_getPathName()))
defaults = {
}
df = self.renderableForm(defaults = defaults)
dl = df.htFormTable()
mySearchList = {
'item': None,
'dl': dl,
}
return self.editTemplate(searchList=[mySearchList, self])
def child_show(self):
path = '%s/edit' % (self.myPath())
if not self.transaction().unusedPath:
self.sendRedirectAndEnd('%s/list' % (self.myPath()))
pathName = self.transaction().unusedPath.pop(0)
try:
item = self._samoosa_class._samoosa_pathnameLookup(pathName)
except:
return self.sendRedirectAndEnd('%s/list' % (self.myPath()))
mySearchList = {
'item': item,
}
return self.showTemplate(searchList=[mySearchList, self])
def content_template(self):
return self.child_list()
Nice and simple - the default entrypoint will just use the list, otherwise we use the named functions.
I needed a 'show' version, so I used much the same method as I used to build a FunFormKit form for 'edit':
class SiteSQLObject(SQLObject):
def fieldValues(self):
ret = []
columnDict = {}
for col in self._columns:
columnDict[col.kw['name']] = col
_capitalRE = re.compile(r'[A-Z]')
for colName in self._samoosa_columnsInOrder:
words = []
template = colName
while 1:
match = _capitalRE.search(template)
if match:
words.append(template[:match.start()])
template = template[match.start()].lower() + template[match.start()+1:]
else:
words.append(template)
break
description = " ".join(words).capitalize()
col = columnDict[colName]
display = registry.queryAdapter(col, IColumnDisplay, '')
ret.append((description, display.displayValue(getattr(self, colName))))
return ret
class ICol(Interface): pass
class IColumnDisplay(Interface): pass
classImplements(Col, ICol)
class ColumnDisplay:
implements(IColumnDisplay)
def __init__(self, context):
self.context = context
class DefaultColumnDisplay(ColumnDisplay):
def displayValue(self, value):
return value
registry.register([ICol], IColumnDisplay, '', DefaultColumnDisplay)
I had to take the code from FunFormKit to convert SQLObject-style column names to English. I was totally amazed I never realised FunFormKit did that for me. And now it "just works" for show too.
I have an interesting idea for a minor tweak, placing the SQLObject update statement in a separate function. Then, I can have certain people edit the object and have it update into the database, and others will appear to be editing the object and create an "update request", which someone else can accept/reject into the database. And I can easily put in audit trails too. All that's going to be a win for my secret project...