David Glick – Plone developer
z3c.baseregistry provides a safer way to register Zope components that should only be available within sites that have a particular add-on installed.
(Updated 10/17/2010 to reflect some minor corrections from Martin.)
At Groundwire it is very common for us to run a number of Plone sites within the same Zope instance. This introduces some unique requirements for add-on products to follow when they are registering Zope components:
- If the add-on registers some new component, it should do it in such a way that it is only available within sites that have the add-on installed.
- If the add-on overrides some default component from Zope or Plone, it should do it in such a way that it can be further overridden for one particular site.
There are several common approaches that can help with these requirements, but each has downsides. Overriding components using overrides.zcml is global (i.e., affects all Plone sites in an instance) and prevents further customization. Registering components for a browser layer only works for components that adapt the request (such as browser views). Registering persistent local utilities or adapters in a site's local component registry keeps things isolated, but can be a headache when it's time to uninstall the add-on or remove the implementation of a component.
There is a lesser-known fourth option: using z3c.baseregistry to create a registry specific to one add-on.
Component registries in Zope 2
In Zope 2 we typically deal with 2 types of component registries (also called site managers historically):
- The global registry, which is populated with components at startup by processing ZCML.
- A local registry associated with each Plone site (implemented in five.localsitemanager). These store components persistently in the ZODB, and can be populated via the
zope.component.interfaces.IComponentRegistrationAPI in Python, or via GenericSetup (componentregistry.xml)
When Zope traverses over a Plone site, its local registry is set as the active registry (via
zope.app.component.hooks.setSite in older Zopes—which sets a thread local). After that, this registry is the one that can be obtained by
zope.component.getSiteManager, and the one that will be implicitly used by the functions that do component lookups. If a component is looked up but not found in the local registry, it will fall back to checking in that registry's base registries. By default in Plone, there is just one base registry, which is the global registry.
However, there's no requirement that the global registry be the only base registry. z3c.baseregistry makes it possible to define additional, named registries which can be installed as additional base registries for a particular site. Then when a component lookup occurs, it will be looked for first in the local registry, then in the custom base registry, and then in the global registry.
The cool thing is that while the installation of a z3c.baseregistry is persistent, the components one contains are not. Instead, the components are populated at Zope startup via ZCML, very much like the global registry. The
registerIn grouping directive lets us specify which registry components should be registered in:
<registerIn registry=".packageComponents"> <!-- component directives here --> </registerIn>
This means that when you uninstall an add-on that has its own base registry, you just need to remove the registry from the site manager's bases, rather than figuring out how to unregister each individual component as would be necessary for persistent components in the local registry. It also means that you can safely remove a component's class when you remove its registration without worrying about breaking legacy persistent registrations of that component.
Step by step
I've used this approach in a few projects lately. Here's what it looks like:
- Add z3c.baseregistry to the add-on's install_requires in setup.py, and re-run buildout to make sure it is installed.
Create a new registry instance.
In __init__.py (or could be elsewhere):
from zope.component import getGlobalSiteManager from z3c.baseregistry.baseregistry import BaseComponents packageComponents = BaseComponents(getGlobalSiteManager(), 'foo.bar')
Here, we made sure that the new registry has the global registry as its base, and is named after our add-on package (foo.bar).
Register a local utility for looking up the new registry by name (this is used by z3c.baseregistry internally).
<!-- registry for package-specific components --> <utility component=".packageComponents" provides="zope.component.interfaces.IComponents" name="foo.bar" />
Install the new registry in the bases for a particular site. z3c.baseregistry includes a form for doing this through the web, but it doesn't seem to work in Zope 2. Oh well, we can do it with a GenericSetup import handler instead.
from zope.component import getSiteManager from zope.component.interfaces import IComponents def install_base_registry(site): sm = getSiteManager(context=site) reg = sm.getUtility(IComponents, name=u'foo.bar') sm.__bases__ = tuple([reg] + [r for r in sm.__bases__ if r is not reg])
You would then call this from the add-on's custom "import various" GenericSetup handler.
Now components can be registered for the new add-on specific registry, using the registerIn grouping directive.
<!-- make sure we can use registerIn --> <include package="z3c.baseregistry" file="meta.zcml"/> <registerIn registry=".packageComponents"> <browser:page for="*" name="foobar" template="foobar.pt" permission="zope.Public" /> </registerIn>
These components will be found within sites that have the product installed, but not within sites that don't!
- It should be obvious, but this only localizes the effects of ZCML directives whose effect is to register something in the component registry (e.g. utility, adapter, subscriber, browser:page). Directives that mutate other things, such as
<class>which directly modifies a class, will still have a global effect.
- Don't forget to make sure that the base registry gets removed from the local registry's bases when the add-on is uninstalled. Otherwise removing the product will break the site when it tries to unpickle the base registry.
Plone 4 made a cameo appearance on NBC Nightly News coverage of Google's new instant search feature.
In case you missed it, last night Plone 4 made an appearance on NBC Nightly News! Its inclusion was happenstance—it came during a demonstration of Google's new instant search feature, with a search for "how much faster is it." The first result was Hanno's blog entry discussing performance improvements in Plone 4!
(Thanks to Darci for the screen capture.)
You can still see the results yourself, although Plone is now second after a blog post about Google's instant search.
The just-released Plone 4 includes rather a lot of big enhancements. Here are some of my favorite little ones.
As you've probably heard by now, Plone 4.0 final was released this week! (The cynical among you are probably muttering that we couldn't even beat Perl 6, but hey, it's here now :) ). Working on Plone 4 has been a huge part of my life for the past year, but I did far from all of the work. Thanks to every one of you who contributed; I couldn't be prouder of what we've created together!
See the link above for info on major features in the release, but there are a lot more little things included too. Without further ado, here are ten of my favorite Plone 4 improvements that nobody's talking about...
General / site admin features
1. New editor features. Check the control panel for TinyMCE (our new WYSIWYG editor) to find some useful buttons that aren't enabled by default:
- Paste as Plain Text (preserves newlines)
- Paste from Word (strips junk markup)
- Insert/edit Media
2. Better date/time handling. The display of event times in listings is now smarter. If the event begins and ends on different days, the time is not shown. If the start and end times are the same, it is not shown twice.
In addition, dates and times are now stored with a timezone, with proper consideration of daylight savings time.
3. Inline error tracebacks. If you're logged in as a Manager and encounter an error, the traceback is shown immediately, rather than requiring you to click through to the error_log in the ZMI.
4. Permission auditing. Thanks to a new feature in Zope 2.12, it's now easy to double-check what permissions a particular user will have in a particular context, via this button on the Security tab in the ZMI:
5. Better control over portlet columns. In Plone 3, it was possible to make a template prevent rendering of the portlet columns by filling METAL slots. In Plone 4, this can be done by setting the disable_plone.leftcolumn and disable_plone.rightcolumn flags on the request before calling main_template. For example:
<tal:block tal:define="foo python:request.set('disable_plone.leftcolumn', 1)"/> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" metal:use-macro="context/main_template/macros/master" i18n:domain="plone"> (snip) </html>
(This might also be done in Python in the __init__ method of a browser view.) Handily, it's actually a 3-way flag: set it to True to force hiding the colum, False to force displaying it even if there are no portlets assigned, and None to use the default logic based on whether there are portlets assigned.
6. "All content" listing view and content-core macro. There is a new folder listing display option called "All content", which renders the full content of each item. (See it in action on the homepage of this website.)
This listing takes advantage of the new content-core slot which is defined in main_template following the title and description and between the various above/below viewlet managers, and filled by each content type's default view. This gives each content type control over how it renders in the "all content" listing. (The "all content" listing also takes advantage of some new slots/macros in the folder_listing template to reuse its logic for batching and fetching item properties; this may be a useful model for other custom folder listings.)
It also makes it simpler to write a view template for a content type, since one no longer needs to include the title, description, and common viewlet managers as boilerplate. The simplest view template is now (this is a copy of Plone 4's document_view):
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" metal:use-macro="context/main_template/macros/master" i18n:domain="plone"> <body> <metal:content-core fill-slot="content-core"> <metal:content-core define-macro="content-core"> <metal:field use-macro="python:context.widget('text', mode='view')"> Body text </metal:field> </metal:content-core> </metal:content-core> </body> </html>
For backwards compatibility, templates may still continue to fill the main slot instead of content-core, and provide their own title, description, and viewlet managers.
7. Flexible image scaling. There is now an Image Handling control panel which allows configuring the standard image scale sizes used by Plone.
In addition, since image scales are now generated on demand, it's quite easy for a template to request an image scale of a non-standard size, using this idiom:
<img tal:define="scale context/@@images" tal:replace="structure python: scale.scale('image', width=42, height=42).tag()" />
Including a standard image scale is simpler too:
<img tal:replace="structure context/@@images/image/mini" />
See plone.app.imaging for additional options.
8. Automatic registering of Zope 2 permissions. In Zope 2.10, registering a new permission took two steps (using the <permission/> ZCML directive to register a Zope 3 permission utility, and registering it as a Zope 2 permission, usually by calling Products.CMFCore.permissions.setDefaultRoles during product initialization).
In Zope 2.12, only the first step is required. The equivalent of setDefaultRoles is automatically called after processing the permission directive, and sets the default roles to ('Manager',).
If you want different default roles, you must still call setDefaultRoles yourself, prior to loading of ZCML (e.g., at import time). This will improve in Plone 4.1 / Zope 2.13, where the permission directive gets an optional role subdirective to specify the desired default roles in ZCML.
9. Fixed ordering of CMF/Archetypes interaction during object creation. In Plone 3, when you created a new content item, CMF called the factory method of an Archetypes content type, then called its ObjectCreated event, then updated the item's portal_type attribute. This had the unfortunate effect that if you were trying to create a schema extender that used the portal_type to determine whether the extender should be applied (perhaps based on some configurable property), and your site used content types that shared the class of other content types, you were out of luck.
This is now fixed so that ObjectCreated is not fired until the portal_type is set. I think we also managed to avoid calling some event twice during object creation that was causing duplicate catalog indexing, but I could be misremembering that.
10. Folder ordering adapters. The order of items in a folder is now looked up via a named adapter of the folder to plone.folder.interfaces.IOrdering—and it is looked up dynamically when ordering catalog results by getObjPositionInParent, rather than consulting an index. This means that it should now be simpler to customize how a particular folder is ordered (e.g. to automatically sort alphabetically or by date), by writing a new adapter that provides IOrdering and setting a folder to use it. To set the name of the IOrdering adapter that is used for a particular folder, use its setOrdering method.
Out of the box, most folders use the DefaultOrdering adapter, which allows user-configurable order. This should be appropriate in many cases. Folders that were migrated from Plone 3's Large Plone Folders use the "unordered" ordering (but this shouldn't be needed for new folders that will store many items, as the default ordering is reasonably performant). plone.folder also has a "partial" ordering which allows specifying the order for some but not all subitems, but this is not directly used or supported in the Plone UI at this time.
On to 4.1 and beyond!