In the ongoing evolution of the workbench I’ve solved some interesting problems and found some hard ones I haven’t solved yet. I also made some poor choices and later found more elegant solutions. All this is a learning process and I’d like to share some of those findings in this series. I’ll cover templates, web framework choices and making your own widgets.
Templates:
Originally I searched for the most elegant and expressive templating language for what I needed to do and found Mako. It’s fast, good for simple interpolation and some basic control structures. However, after trying to write even one of our basic workflows in it, I was less enthused. I needed a range of slightly dodgy hacks to get what I wanted out.
For instance, I wanted to have a mako function put a piece of text (javascript) in a buffer and output it later (in the head section). I found a way of doing this:
<%!
def string_buffer(fn, target_buffer='html_buf'):
def decorate(context, *args, **kw):
context['attributes'][target_buffer] = runtime.capture(context, fn, *args, **kw)
return ''
return decorate
%>
<%def name="doc_ready_js()" decorator="partial(string_buffer, target_buffer='doc_ready_buf')">
${caller.body()}
<%def name="main_js()" decorator="partial(string_buffer, target_buffer='main_js_buf')">
${caller.body()}
Needing to use decorators, partial function application and weird context/global variable magic for quite simple features freaked me out enough and convinced me to hunt for a better solution. I wanted the simple to be easy and the hard to be possible. The template was making the the simple hard. I looked at a pile of templating languages and I still liked Mako the most out of them, but decided after reading this wonderful rant by by Tavis Rudd that the solution is obvious: Do simple stuff with templates if you need to, but mostly avoid them.
The clearest way to demonstrate the advantages of a pure python solution is with code, both sections of code generate the same simplified workflow:
The Mako:
<%namespace name="zui" file="zui.mako"/>
<%namespace name="importer" file="importer.mako"/>
<%namespace name="widgets" file="widgets.mako"/>
<%inherit file="workflow_wrapper.mako"/>
<%def name="workflow()">
<%
current_workflow_id = attributes['get_new_id']()
attributes['workflow_steps'][current_workflow_id] = [[],[],[]]
grid = attributes['get_new_id']()
map = attributes['get_new_id']()
%>
<%call expr="importer.importer({'name':'text', 'address':'text'}, ['name', 'address'], r'''
Here you need to select a CSV file with address and a name for the locations
you want to geocode. Names have to be unique.''', table_name, 'import_done()')">
## events are run on the client side so are javascript
<%call expr="widgets.run_engine_step('Geocode',
{'onclick':'''calculate_button('geocode_addresses',
geocode_results);'''},
workflow_icon='geocode')">
<%call expr="zui.workflow_step('output', 'View Results')">
${widgets.grid_holder(table_name, grid)}
${widgets.map_holder(map)}
<%zui:main_js>
function import_done(){
console.log("run when import is finished");
}
function geocode_results(){
${widgets.grid_init(grid, table_name, '')}
}
The Python:
import zui, widgets, importer, layout
class Geocoder(zui.Workflow):
def __init__(self, the_zui, table):
zui.Workflow.__init__(self, the_zui, table)
importer.Importer(self, {'name':'text', 'address':'text'}, ['name', 'address'], r'''
Here you need to select a CSV file with address and a name for the locations
you want to geocode. Names have to be unique.''', self.table_name,
'Geocoder.import_done()')
## events are run on the client side so are javascript
widgets.RunEngineStep(self, 'Geocode',
{'onclick':'''calculate_button('geocode_addresses',
Geocoder.geocode_results);'''}, workflow_icon='geocode')
grid = widgets.Grid(self.table_name)
cm_map = widgets.Map()
self.workflow_step('output', 'View Results', container_size = "container-results1", body =
layout.multi_column('', grid, cm_map))
self.main_js(
'''
function import_done(){
console.log("run when import is finished");
}
function geocode_results(){
'''+grid.load()+'''
}
''')
It is so much easier to be expressive and elegant when you minimise the usage of templates. You can clean things up by using some of the expressive power of a multi-paradigm language and not have to do anything too strange. I still see the use for templates when you need to interpolate a few variables into a long piece of HTML. The built in python template function seems fine for this. In my next post I’ll over web frameworks and what I think about widgets. You should be able to see from today’s bit of code that I think they are important and hard to do with templates.
Loki
p.s.
If you feel like relaxing after reading this, check out my band’s YouTube channel. 🙂