Hi Harald, > I need some advise how to use dynamically generated interfaces, content > and forms:
I'm working on a package called Dexterity for CMF/Plone that can work with content types created through-the-web (among other things). For TTW content to have a unique interface, you need a dynamic one. See http://martinaspeli.net/articles/dexterity for some more information. This is slightly out of date, but not too much. If you just want an interface at runtime, with no persistence to the ZODB, then it's pretty easy. You can generate an interface (it helped me a lot to understand that an interface, whilst defined with the 'class' keyword, is really just an object of type InterfaceClass) and use it however you want. It gets a bit more tricky if you want to have content objects that are both persistent to the ZODB and provide the interface you generate forevermore. However, it's not too difficult. > 1. step: > generate interfaces from xsd > I've used zope.interface.interface.InterfaceClass to achieve this You may be interested to look at http://svn.plone.org/svn/plone/plone.supermodel which does the same thing from an XML representation of zope.schema fields. plone.supermodel will work in "plone Zope 3", so you should be able to re-use it. I'd be really interested to see your XSD parsing code, by the way! We thought about using XSD for Dexterity, although I find XSD syntax a bit verbose. Is it feasible to actually write the interface in Python? plone.supermodel has a grokker (you don't need all of Grok, just the re-usable grok.component) that lets you write something like this: >>> class IMyType(plone.supermodel.Schema): ... plone.supermodel.model('mytype.xml') plone.supermodel.Schema is just a marker interface. At ZCML processing time, this will read 'mytype.xml' and scribble zope.schema type fields onto IMyType. This interface is then every bit as real as any other interface you have, and is quite easy to work with. For inspiration: http://dev.plone.org/collective/browser/example.dexterity/trunk/example/dexterity/page.py If it's not feasible to write a per-file schema interface like this, then you need to generate it all on the fly. That's easy too - you just construct an InterfaceClass object at runtime and return it (plone.supermodel has methods that do this, for example). The challenge, as you've seen, is what you do with that once you have it ... > 2. step: > generate content object implementing the generated interface from step 1 > zope.interface.directlyProvides or classImplements? > FieldProperty? If you really want to make sure that ISomeGeneratedSchema.providedBy(some_content) is True, then here's how Dexterity does it: http://dev.plone.org/plone/browser/plone.dexterity/trunk/plone/dexterity/factory.py This is a custom IFactory utility (we register local utilities for each TTW-defined content type). Your code doesn't need to be in such a utility, of course. The relevant bits are: 1. Find a class to use. We store this in a string property that's TTW editable and resolve it. 2. Create an instance of the class. 3. Look up the (dynamic) schema marker interface. 4. Check if the instance already provides the interface. 5. If not, do an alsoProvide() to mark the instance. At this point, if you have a generic class, you've probably just told a lie. Your generic class won't have the fields that the interface promises. You can either go through and set properties on the instance, or use a __getattr__() that can return field defaults. We opted for the latter, since it makes it easier to deal with interfaces that change over time. This is done here: http://dev.plone.org/plone/browser/plone.dexterity/trunk/plone/dexterity/content.py Here, we look up the type's schema again. Note that we need some kind of registry to know which schema the instance is supposed to have. Dexterity has all that in a utility, but you could just as easily have done something like queryType(self). We *did* look at other ways, such as generating a class for each interface, or doing a dynamic __provides__ property. However, that got very complicated, or slow, or both. The pattern above is pretty straightforward. However... :) I assume you want to persist things in the ZODB as well? That gets a little tricky, because if you use alsoProvides() to set it on an instance, you need to ensure the interface has a real, stable module path so that the ZODB can load it again. The way we've solved it in Dexterity is this: - We use a small package called plone.alterego (http://svn.plone.org/svn/plone/plone.alterego/trunk/) that has some hooks to let you create a dynamic module. You can re-use this in plain Zope 3. - The dynamic module has a __getattr__ hook which delegates to a utility that you write. Your utility creates and returns the module's object for that name. - You need to be careful that your utility always returns the exact same object, with the same python id(), otherwise you'll get stuck. The utility in this case is SchemaModuleFactory in this file: http://dev.plone.org/plone/browser/plone.dexterity/trunk/plone/dexterity/schema.py The dynamic module 'generated' is kept there as well. When anything tries to get any attribute from this, the SchemaModuleFactory utility will be called. This uses the interface name to determine what schema to load. You need some kind of naming convention like that, since you won't know anything else than the name that was accessed at this point. Presuming you can look up the schema from the name, you just load it and return it. Once you're sure it's not going to change, you can setattr() it on the module so that the utility doesn't get called again. However, you have to be able to deal with the case where the ZODB is trying to load the object before you've got enough context to find the interface's fields. In this case, we create a placeholder interface which is then updated. Note that you can't just return a blank interface and then return a different one later. plone.supermodel has a helper method sync_schema() that can make one interface get the fields, bases and tagged values of another. We use this to update the placeholder interface with the real fields from the 'contextless' interface that plone.supermodel returns when we load it. - To actually get hold of this interface in the first place, you use something like: >>> from plone.dexterity.schema import generated >>> my_schema = getattr(generated, 'my_schema', None) This will either return the interface quickly if it has been fully loaded and setattr'd onto the dynamic module, or it will invoke the utility. > 3. step: > generate z3c.forms with subforms(depending on interface complexity) > There are 3 different types of forms in my use chase: > a) simple interface, with simple attributes > b) interface with zope.schema.Object to display > c) interface with zope.schema.Dict and zope.schema.Object as value_type > d) interface with zope.schema.List and zope.schema.Object as value_type > > but how? > I've looked at the addressbook example in z3c.form but ... > I'm still missing infos/knowledge about how to connect the three steps This part is now quite easy. You need to make sure your views are registered properly. One way is to have a generic view registered for the common class. If you have a concrete interface, then it's easy to register views for it. And if you have a dynamic interface, you can register local components for this dynamic interface. Once you have the view, it's all just business as usual with z3c.form. Dexterity's forms are a bit complex because they also allow people to specify UI hints in the schema. plone.supermodel parses this (via some plugin utilities for application-specific logic) and stores them in tagged values on the interface object. These are then used to set up the form. There's also support for doing groups with objects rather than classes. I suspect you won't need all of that to begin with though. The forms are here: http://dev.plone.org/plone/browser/plone.dexterity/trunk/plone/dexterity/browser/base.py http://dev.plone.org/plone/browser/plone.dexterity/trunk/plone/dexterity/browser/edit.py Hope this helps! Martin -- Author of `Professional Plone Development`, a book for developers who want to work with Plone. See http://martinaspeli.net/plone-book _______________________________________________ Zope3-users mailing list Zope3-users@zope.org http://mail.zope.org/mailman/listinfo/zope3-users