How's that title for a clever Django reference? (I initially was going to go with "some gypsy in France", but I imagine there might actually be a crowd of people out there who haven't had the opportunity to enjoy this fine film.
In regards to my Python off the Rails post, I've gotten a couple of pieces of feedback. The first is that Django is a harvested framework that has been running production sites, including some particularly impressive ones, for a couple of years now. Django is not a Python-come-lately framework like I initially read it to be. Thank you to both Simon Willison and Adrian Holovaty for pointing that out.
Another piece of feedback I got, from Adrian Holovaty, covers the larger part of the model that I touched on but didn't show in full. Referring again to the comments example,
ordering = (('submit_date', 'DESC'),) admin = meta.Admin( fields = ( (None, {'fields': ('content_type_id', 'object_id', 'site_id')}), ('Content', {'fields': ('user_id', 'headline', 'comment')}), ('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}), ('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}), ), list_display = ('user_id', 'submit_date', 'content_type_id', 'get_content_object'), list_filter = ('submit_date',), date_hierarchy = 'submit_date', search_fields = ('comment', 'user__username'), )These are used to help build UIs and such for these items, including administrative interfaces. According to Adrian, that's part of the design decision.
To me, this still smells like the extremely overweight Zope 2 objects, or at least of many Zope 2 based systems I've developed over the years because a path like this was the easiest path. These projects work. But they're not the greatest to maintain, to look at, or to have to go back and work on. Granted - it's better than doing a lot of this type of UI work manually. And I love having a decent coupling and reliability between validated data coming in from a form and what I have to store in a database.
In new projects, some built on our own in-house framework (a data management framework), some built in Zope 3, I'm preferring to off load as much of this stuff as possible from the core model object / model definition, and relating them all via a loose coupling.
It's the loose coupling that's not so easy, at least not on its own. Zope 3's component architecture is one way to solve this, usually by declaring a view or adapter as "provides(IFoo) for (IBar)" (not the actual code, but the intent). This is also where ZCML (Zope Configuration Markup Language) comes in. I used to wince at ZCML, and many people wince at XML based configuration. But it's nice to have the mechanism for loading up and registering components outside of the core language - or at least outside of the language's default code loading and execution procedures.
So in a Zope 3 application I'm working on, I have the following code. This is the model code for an article in a knowledge base. It needs no real logic - it's purely data. It subclasses from OrderedContainer because an article can contain comments. interfaces.IArticle
describes the meaning of each of the properties shown here. There's no object-relational mapping going on. The ZODB provides pure persistence. Although, without too much effort, I could replace this implementation with a SQLObject based one via sqlos - a SQLObject support package for Zope 3.
class Article(OrderedContainer): implements(interfaces.IArticle) title = u"" description = u"" content_type = u"" content = u"" tags = ()I've already shown what a Zope 3 schema can look like, so I'm not going to cover that here. The IArticle interface and schema means that anyone can write their own implementation based on my schema, and things like what I'm about to show will still work. Zope 3 does just about everything based on interfaces, because - in theory - if all you care about is a duck's bill - does it matter if the animal is a duck or a platypus?
So, to create a simple edit form based on the schema for IArticle, the following ZCML is used. This is from a real application, with only the package names altered. I'll go over it line by line in a moment.
<browser:editform name="edit.html" for="foo.interfaces.IArticle" schema="foo.interfaces.IArticle" permission="zope.ManageContent" label="Edit Article" menu="zmi_views" title="Edit" > <widget field="description" height="4"/> <widget field="content" height="18"/> <widget field="tags" class=".widgets.TagWidget" displayWidth="60"/> </browser:editform>This declares an editform with the name 'edit.html' for implementations of the
foo.interfaces.IArticle
interface. An editform is a browser view. The 'schema' line tells the edit view what schema to use to render it, and the 'permission' line restricts access to site members with the zope.ManageContent
permission. The 'label' gives the form its displayed title on the web page, while 'menu' and 'title' allow a view to be listed in a particular menu. 'zmi_views' render as tabs in the default Zope 3 user interface. Some things that could be done in this declaration which I did not do include providing a limited subset of schema fields to render, or providing a Python class that provides extended behavior for the editform view.
For many cases, that's all one might need to do with an editform. Maybe even less. Menu assignment is certainly nice in this situation, since it gives me a tab in the ZMI to edit an article from. But I wanted to customize some widget behavior without having to get too down and dirty with HTML or Python. That's what the widget
directives offer. The first two let me have a shorter description widget and a tall content widget. Zope 3 will find the best view for these fields (which will turn out to be a textarea) and render them. The last widget for 'tags' is a custom widget that I implemented for doing tag style fields. It parses its input and turns it into a Python tuple of lowercase strings with extraneous whitespace and punctuation removed, as well as any duplicate 'tags'.
But that's it! The edit form collaborates with the named schema and its field definitions to validate and store the data into an instance of that very simple Article
class I showcased above. It also fires off an ObjectModifiedEvent, which I can have subscribers listen for. This is used, for instance, to reindex a search catalog, and could be used to clear out caches.
The main point is that this admin is only loosely connected to that article object. As a developer, I could take the knowledge base article and its interface (if they were written by somebody else) and work them into my own CMS which might provide a very different UI and configuration than just a basic bare Zope 3 setup. Or I could adapt something that I had that was very similar to a knowledge base article and, through adaptation, provide an IArticle interface and have these views get bound to it.
Ultimately, a very nice thing about Zope 3's setup is that it's very well documented - all of the interfaces, the ZCML directives. When I write my IArticle interface, it gets indexed into Zope 3's increasingly useful api documentation tool, and if I wrote my own ZCML directives, they would also get scooped up. It's quite nice. In previous systems I've used and written myself, ones with a basic model implementation like Django's, there often ends up being a lot that the developer has to remember. This is a lot of what plagues Zope 2 developers. There are voodoo names one has to watch out for (there was a time when adding a DTML method named 'target' into a ZODB instance would cause the Zope Management Interface to do spectacularly funny things). The Zope 3 component architecture comes out of years of experience in these things, and I applaud the Zope 3 team for coming up with such a clean architecture (Zope 3.1 is cleaner than Zope 3.0!) that avoids getting bound to magic directory names, magic attribute names, and so on, as much as possible. In a Zope 2 properties screen, you're warned that "deleting the 'title' property is potentially dangerous". There's no such danger in Zope 3.