[Zope-CMF] [dev] 'add' actions and views - a proposal

Martin Aspeli optilude at gmx.net
Sun Sep 21 09:57:54 EDT 2008

Hi Yuppie,

>>> 1.) CMF add views adapt not only container and request, but also the 
>>> type info object. This way the views can't be accessed directly and have 
>>> self.fti available.
>> This is quite interesting, and possibly necessary. However, it means 
>> that CMF add views are not just "views" and will need to be registered 
>> differently to other views (i.e. you can't just use <browser:page /> 
>> which also means that you won't get the Five security treatment etc).
> Yes. This causes more problems than it solves. I think I found a much 
> better solution:
> CMF add views are registered for a special layer called IAddViewLayer. 
> Like any other layer, IAddViewLayer extends IBrowserRequest. And it 
> defines an additional 'ti' attribute for the request.
> Like before views can't be accessed directly and have self.ti available. 
> (I now use 'ti' instead of 'fti' because we have other type info 
> implementations than FactoryTypeInformation.)

I'm not sure I like this much more. It involves adding a marker 
interface to the request conditionally during traversal. You'll possibly 
run into funny sequence dependent conditions if you want to customise 
the add view for a particular "theme" browser layer.

My preference would be:

  - Define an interface IFTIAwareView that has an 'fti' property
  - Define a traversal view (@@add) that does this kind of thing on 

  addview = queryMultiAdapter((self.context, request), name=name)
  if IFTIAwareView.providedBy(addview):
      addview.fti = fti
  return addview.__of__(self.context)

or something to that effect.

This keeps the view that the developer writes close to a "real" view 
that can be customised in the normal ways. Of course, it can only be 
traversed to via the traversal adapter, but I think this'll be the case 
no matter what.

>>> 3.) A traversal hook looks up the type info based on the view name. And 
>>> then returns the corresponding add form:
>>> queryMultiAdapter((container, request, fti), name=fti.factory)
>> Why do we need to *both* adapt the FTI and use the factory name as the 
>> view name?
> Because we want to use different views for different content factories 
> *and* pass the type info to the view. This way we don't depend on an 
> environment that sets the ti value later. And all view methods work from 
> the beginning without fallback code for a missing ti.

The addview definitely needs to know which FTI it's being used for. 
However, in the case above, you could just have it be an unnamed 
adapter, since self.fti.factory will always equal self.__name__ in the 
view, no?

> As mentioned above, I now propose to pass in the type info as part of 
> the request object.

Right. I'm not sure this is naturally something that belongs in the 
request, though.

>>> 6.) An IPublishTraverse adapter is used for IFolderish objects. It tries 
>>> to resolve names starting with '+' and falls back to 
>>> DefaultPublishTraverse behavior if no add view is found. This way URLs 
>>> like http://www.example.org/folder/+PortalType are supported.
>> -1
>> Or maybe -10 now that I think about it a bit more...
> I also started with -1, but thinking a bit more about it my vote is +1.

Why? Why is the +PortalType convention so important? It's not used 
elsewhere, and it means dictating a convention that developers must know 
about and follow.

Moreover, this convention currently means something else in Plone. :)

In plone.app.contentrules, for example, we have a "+condition" view 
that's very similar to the "+" view; it provides IConditionAdding that's 
analogous to IAdding (provided by "+"). I'm pretty sure your proposed 
traverser would conflict and screw up the +condition (and +action and 
+portlet) views we have there already, necessitating complex workarounds 
that would need to be aware of the semantics of multiple packages.

>> This is pretty invasive. It'd make it impossible to have another 
>> IPublishTraverse adapter for any IFolderish without either subclassing 
>> from the CMFDefault one or breaking all add forms.
> Shrug. That is how the IPublishTraverse adapter works. There are many 
> use cases for the IPublishTraverse hook and only one available slot.

So you want CMF to "steal" that slot for *all* folders?

> You 
> never can be sure that this hook is free. If you write your own 
> IPublishTraverse adapter, you always have to look at the code base and 
> subclass those adapters you want to use.

Well, you can avoid writing the one adapter for a very generic 
interface. In the @@add case, CMF provides *one* view (i.e. we "reserve" 
the name @@add for any IFolderish) and traversal continues from that view.

Put differently, traversal from a folder is complex, but also well 
established. You travese into attributes/contained objects or views. 
Traversal from a new @@add view does not have any pre-existing 
semantics. We can choose exactly what we do with the traversal sub-path. 
We don't need to derive from the default IPublishTraverse at all.

>> Rather than invent a "pseudo" namespace with a naming convention, why 
>> not use a proper namespace, i.e. 
>> http://www.example.org/folder/@@add/PortalType ?
> What's the difference between a 'pseudo' namespace and a proper namespace?

+PortalType relies on a naming convention. Developers must know about it 
and know that it has magic semantics.

@@add/PortalType relies on looking up a single, well-defined view that 
can be traced in the component architecture. When you find the 
implementation of that view, it's clear that it implements 
IPublishTraverse and is thus capable of providing special semantics for 
a traversal sub-path (PortalType).

> The 'add' view you propose is a 'pseudo' view. It just exists to be 
> bypassed, not to be returned.

Yes, that's true. I'm not sure that's bad, though.:)

>> This is short, simple and allows alternative implementations if people 
>> want to use a different name (e.g. the @@add-deterity-content traverser 
>> in Dexterity), and does not require a clunky traverser for all 
>> IFolderish. Instead, you register the @@add view for IFolderish and have 
>> it implement IPublishTraverse as well.
> We have a trade-off here: clunky traverser vs. clunky URL.

We have a trade-off of restricting customisability and flexibility for 
developers vs. an aesthetic preference for a particular URL convention. 
FWIW, I don't think +PortalType is any prettier than /@@add/PortalType.

You can drop the @@ if you'd like, though. 
http://example.org/folder/add/Document. That's almost RESTful. :)

> I don't want to have names like '@@add-dexterity-content', 
> '@@add-cmf-content' or '@@resize-plone-image' in my URLs. '@@add' looks 
> a bit more generic, but it isn't.

Well, @@add would be the CMF default semantics. In this way, 
@@add-dexterity-content (which should be shorter, I guess) allows an 
override with different semantics not by overriding or even being aware 
of components from other packages, but by following a well-defined pattern.

> '+PortalType' works with any add view implementation.

I don't think it'd work work Dexterity, because in the default case, all 
Dexterity content uses a single, generic add view that constructs a form 
based on knowing the schema (which it looks up in the FTI). Thus, 
Dexterity needs different traversal semantics, to fall back on a default 
add view implementation when no factory-specific view exists.

> If you hardcode 
> the portal type, you can register the add view directly for the default 
> skin. If you want to use the CMF traversal or dexterity traversal, you 
> can still support the same URL.

I'm not sure that'd be possible.

Of course, the actions work you've already done means that Dexterity can 
continue to use @@add-dexterity-content and its own traversal namespace. 
We'd just have to be damned sure not call that +dexterity-content. :)

> Following your proposal, we have '@@addPortalType' for simple add views, 
> '@@add/PortalType' for CMF traversal and 
> '@@add-dexterity-content/PortalType' for dexterity traversal. I don't 
> think that's what IPublishTraverse is made for.

It's not? I don't think PublishTraverse is "made for" any of this, 
really, it's just a way to react to a traversal subpath (what comes next 
in the URL). My proposal is that we traverse to a view in the normal 
way, which then makes the decision by implementing IPublishTraverse. 
This level of indirection allows different semantics for different URLs.

> Are the IBaseObject traversers used by Archetypes and plone.app.imaging 
> also 'bad'? Or what's the big difference to the adapter I propose?

I'm not sure they're good, in any case. They are needed to support 
conventions that are already in the wild (/obj/image_thumb, say, to get 
a thumbnail). If we could do it again, I'd certainly argue for something 
more flexible.

>>> Proposed CMFCore TypesTool changes
>>> ----------------------------------
>>> 7.) listActions of the types tool returns add actions for *all* portal 
>>> types.
>> +1 - this change is already done, right?
> Yes. Checked in yesterday.
>>> 8.) If no add view expression is defined in the type info, a default add 
>>> view URL is returned.
>> -0
>> Would it not be better to push this logic higher up, i.e. in the view 
>> code that's actually using the add views?
> Can't follow you here. How should that work?
>> The default for CMFCore is 
>> probably not going to be a suitable fallback for Plone, for example, 
>> which means we either need to customise/override or ensure that all 
>> types always have such an add action.
> With a default value CMFDefault needs no migration code and copying type 
> infos is easier. But I agree that it might be useful to make it 
> customizable. Maybe we should use a types tool property for that setting?

That'd be nice, I think; my thought was mostly just that if you have a 
view that's rendering the add menu, it could have the logic "if this URL 
expression is empty, use this default", rather than having that in the 
listActions() method, since in this case it'd be hard to know whether a 
type really had a view defined or you just happened to get the default.

>>> If there are no objections, I'll make the proposed changes on the trunk.
>> Apart from the traverser stuff, I think this sounds great. :)
> The traverser stuff can easily be changed later, so I can't see a need 
> to put my work on hold. And working on a branch isn't very useful 
> because you get much less feedback.

Right, I certainly don't want to hold you up! I do think that 
/add/PortalType is much preferable to /+PortalType, though, for reasons 
outlined above. I guess we should see what Tres and Jens and others think.


Author of `Professional Plone Development`, a book for developers who
want to work with Plone. See http://martinaspeli.net/plone-book

More information about the Zope-CMF mailing list