For the last two weeks my goal was to try to work through as much of the WordPress integration work as possible.
Unfortunately I didn’t really get anywhere with that. I spent about a day starting to create the skeleton of a WordPress featurelet, which mostly involved trying to refactor out a common base for all featurelets that talk to an external application: TaskTracker, WordPress, whatever else we may add on. I didn’t get nearly as far with that as I wanted; it’s good enough for WordPress, but I’d like to revisit it before we have a third case for it.
Then, though, I got pulled back into NUI, and spent far too long doing far too little. I got things done and learned a huge amount (about Zope testing layers, about Zope event subscribers, about portal_catalog) but, when I stepped back and thought about what I had done, I realized almost all of it was minute enough that I should have given up after half a day. On the bright side, Mouna assures me that the hardest and most trivial thing I accomplished, the mailing list thread count I added to portal_catalog, will be useful for the Listen NUI screens, so that’ll be nice if it’s true.
Between adding AJAX resorting by column to the project contents page, adding AJAX field validation for the sitewide join form, and trying to explain the formhandling system Nick and I had worked out, I also spent quite a lot of time this iteration looking at Octopus and FormLite, the two “major” (very relatively speaking) form handlers we have in opencore.nui.
The former I mentioned briefly in my last blog post; developed for the project contents page, but since used (and modified to be helpful for) a TaskTracker widget and the team management page, it basically just takes a request structured in a particular way, passes it on to a method on your view, and returns either the result of that operation (for an asynchronous request) or a redirect to HTTP_REFERER (for a synchronous request).
The latter is a lightweight little delegation system Whit wrote; have your View class extend opencore.nui.formhandlers.FormLite, decorate a couple of methods with @action(’unique_label’), and FormLite’s handle_request method will call the action decorated with whatever label it first finds in the request.
David tried to use Octopus during the iteration, and was annoyed to see that he had to format his request a particular way which meant he couldn’t just trigger the Ploneish object.validate() using the untouched request fields. So I wrote a @deoctopize function which takes an octopus-structured request and constructs a Ploneish request from it for each of the objects that is referred to in the request.
Meanwhile for the join form, once I added validation as well as creation submissions, I decided to use FormLite so I could have separate createMember() and validate() methods which were easily testable individually.
While I was working on that join form I added a couple of things to FormLite to make it more useful for me.
First, I was annoyed that, to use FormLite, I had to bind separate views for the join form’s rendering (bound to a template) and its delegated formhandling (bound to an attribute on the view class). This actually took me quite a while to figure out, since I didn’t really understand how Zope views work, but the short story was that if I called a method on a view which was bound to a template, my AJAX friendly response would be clobbered by a rerendering of that template.
So my solution was to add a concept of a default action to FormLite — that is, if you decorate a single method with @action(’unique_label’, default=True), that method will be called as a fallback if no other method labels are found in the request. With that done, I added to my JoinView class a default action that simply renders the form template, bound my sole view to the handle_request attribute (from FormLite, so it delegates) on the view class, and (to avoid infinite recursion) deleted the TAL call to view.handle_request() at the top of the page template. Though there are still a few hitches, I like this convention quite a lot; rather than having a form rendering method which explicitly calls a particular “work-doing” method on the view, it feels a lot more intuitive and natural to explicitly define delegatable actions to an implicit delegator method which is called every time a request is made to the form’s sole URL.
Next, I was annoyed by some tests. I wanted my validate() method to return a list of error messages, but I had to format them a particular way for the Javascript which was not ideal for running a test. I knew that FormLite left its delegatees untouched so that they were still directly callable, so I decided to add in a way to decorate a method *only* if it’s called by the FormLite delegator and *not* if it’s called directly — that way it would return the Javascript-friendly structure if it was ever triggered from a ‘real’ request, and it would return the Python doctest-friendly structure if it was just triggered directly in a test. So I added another option to the @action decorator, ‘apply’, which takes either one or a tuple of references to decorators to be applied to the delegatee. So for the user this might look like
@action('my_action', apply=(jsonify, post_only))
def my_action(self): return an_obj
for an action which should always return an_obj when it’s called directly in tests, but, when delegated to, will treat it as a call to
@post_only
@jsonify
def my_action(self): return an_obj
(As a side bonus, this syntax avoids having to worry about the FormLite quirk whereby a function’s outermost decorator must have a __name__ identical to the innermost function.)
This, incidentally — applying decorators to methods at runtime — was harder than I expected, because the innermost method was bound to the view class and the decorated methods were not, so the decorated methods had to be called as method(view) but the innermost decorator, internally, had to call bound_method(). I got around it by, instead of passing bound_method to the decorators, passing bound_method.im_func, which is an *unbound* reference to the function.
By this point, I realized, I was very close to having pushed FormLite and Octopus close enough to each other that it was time to merge them — FormLite could delegate to any action, Octopus could always delegate to a single location but had enough information to figure out what action to use, and both FormLite and Octopus could inspect or modify the internal method’s response to the client (through apply in the former case, checking for mode==async and either returning the response or a redirect in the latter case).
So here’s a first stab at combining Octopus and FormLite into OctoLite.
A view with a form is structured like the join form. That is, there is a single view for the form, bound to the view class’s handle_request attribute; it is not bound to a template. The view class has a render() method which calls self.form_template() where form_template is a ZopeTwoPageTemplateFile class attribute which contains the form ZPT. This could be the view class’s responsibility to define, or it could be defined in an OctoLite class which the view class extends and leave as the view class’s sole responsibility defining self.form_template.
The view class also contains one or more methods decorated with @action: form_submission and validation, for example; or delete_objects, update_objects, and validate_objects; etc.
OctoLite.handle_request combines the handle_request method of FormLite with the pre-call form processing in the octopus decorator. First it calls Octopus.handle_request, which processes values from the form to determine what action has been taken, on what objects, and with what parameters. It then triggers the FormLite delegation method on that action (first, perhaps, deoctopizing the request form, though there are some issues here). The delegated method returns, and Octopus kicks in again, either returning that value for an asynchronous request, or doing a redirect to some URL for a synchronous request. Instead of HTTP_REFERER, though, this redirect URL should be specified by the delegatee, since 1) HTTP_REFERER won’t always be there and 2) some delegatees might want to do unpredictable things, like redirect to a different page.
So, that’s the first pass through. It’s very straightforward, though I think it’s important that the pieces each continue to work on their own, at least for backwards compatibility until we move over all the forms. (I have a hunch that it’s worth keeping them discrete for other reasons, too, though.)
Some questions to address in later passes:
1) Does the redirect URL come back in the return value of the delegatee, or is it stored in an attribute on the delegatee method itself? I prefer the latter — it feels more explicit and less obtrusive to the programmer — but this might be difficult in cases where a single method needs to redirect to multiple places conditionally e.g. on success or failure — this concern is probably a deal-breaker for the attribute approach.
2) What happens to the concept of ‘apply’? After all the post-call thing that Octopus does is essentially a case identical to something like an @action(apply=conditionally_redirect). Is there something redundant here? Is the apply thing unuseful?
3) Even further separation of underlying test-friendly behavior and particular AJAX-friendly data structures. As we flesh out the Javascript half of this story, the data expected back from the server gets pushed into an increasingly convoluted structure which provides a decreasing ratio of information directly related to the core behavior versus information useful only for AJAX rendering purposes. It may be that the core delegatee should be responsible only for a very small amount of data expressed as naturally as possible, while the AJAX-friendly representation is defined only once and the data converted in a single decorator `apply’ed to all delegatees.
In this last I mentioned fleshing out the Javascript half of the story. Nick and I started thinking about this today, in response to a few new use cases discovered by us and Rob Miller. Currently, the system is very dumb: if you make a request with an action called “delete”, the server is expected to send back a JSON array of DOM elements to delete; if you make a request with an action called “update”, the server is expected to send back a JSON dictionary keyed on DOM elements to delete with values being HTML snippets to insert in place of the keys; if you make a request with any other action, the client doesn’t render any effects on success. Rob wanted to be able to define his own action names, though — which will also be necessary for any remotely powerful FormLite form; and Nick and I wanted to be able to specify which HTML snippets on a “update” callback should blink/highlight when they appear and which shouldn’t; and both Rob and I wanted to send back text messages to insert into particular DOM nodes. So I think I will specify that any server response is expected to have something like the following format, in JSON:
Return a dictionary whose KEYS are ids of existing DOM elements on the page, and whose VALUES are a JSON object (with all fields optional):
class ActionInfo {
string html; # a string which can be evaluated as HTML and rendered into DOM elements (quoted HTML or plain text)
string effect; # a comma-separated string of JavaScript effects to apply to the element referred to in the id key, like fadeOut, highlight, or blink (we need to define a fixed set of these or something)
string action; # a string indicating what action to take on the DOM element specified in the id key. This will probably end up being some large subset of the commands in the Deliverance specification, actually: certainly 'replace', 'copy', 'append', 'drop', and maybe 'prepend' too..
}
So a sample response might look like
{'el1': {'html': "Changes saved!", 'action': "copy", 'effect': "blink"}, 'el2': {'action': "delete"} }
I’m hoping to implement this tomorrow; you can probably see, though, why I’m concerned to move this response-generating code out into, if possible, a single unobtrusive function.