[Zope-CMF] Re: Add forms and menus

Martin Aspeli optilude at gmx.net
Wed Jul 16 04:44:56 EDT 2008


Hi,

Having thought about this a bit more ...

>> I don't really see why you need a traverser *unless* you're trying to 
>> have a single add form implementation that covers multiple types.
> i.e. if you have one content type, i.e. a folder, but you want to use
> exactly this type with different workflows, names, icons, then this
> makes indeed sence.
> 
> here you might simply add another fti, and its done. adding such a type
> is then either invoked like @@add/Folder or @@add/AnotherFtiForFolder,
> but both return the same form.

Right, I get the need now.

>>  You 
>> may of course have that, and maybe it's helpful to let people write 
>> that, but I think most people would prefer to write "plain" add views 
>> that use the standard z3c.form patterns.
> its possible anyway, isn't it? the advantage is that there's one way how
> adding works in general. and the discussion is still about implementing
> a generic adding mechanism in CMF. as i pointed in a previous post,
> there should be the possibility to do customization. so as convention it
> might be done this way.
> 
> * lookup fti for ``portal_type``
> 
> * have a look if theres a custom view set.
> 
> * if so, do lookup with this name
> 
> * if not, try lookup with ``portal_type`` as name
> 
> * finally do general lookup if others failed.
> 
> thats also why i tried to introduce IFormFactory, because the traverser
> might not need to know too much. but thats maybe a bit too far...?

That feels like a lot of hoops, but maybe not.

I wouldn't necessarily try to create a generic add form that is a 
fallback for all types. Even if CMFDefault chooses to generalise its 
forms, that's not likely to work for other implementations. Rather, 
then, each CMFDefault FTI may choose to use the same form registration.

>> For something like Dexterity, where we explicitly want to support 
>> "generic" content with a schema that varies according to runtime 
>> configuration, this is more of an issue. But even there, the intention 
>> is that whilst the framework has a few hooks like this so that it works 
>> with content that's more malleable, it doesn't force you to use 
>> unconventional patterns if you do something yourself on the filesystem.
> the goal should be the various IFormFactory hooks, so you might not need
> to change the way you write addforms in general, but to provide a
> specific IFormFactory implementation for a specific framework.
> (dexterity, devilstick, archetypes, whatever).
> 
> as an alternative the magic could be done in the traverser directly, but
> then there must be different traversers for each framework and different
> 'add' browserpages where those traversers could be bound to. this would
> then look like this for invoking:
> 
> @@+cmf/``portal_type``
> @@+ds/``portal_type``
> @@+dx/``portal_type``
> @@+at/``portal_type``
> ...
> 
> which of them to call in the add dropdown must be stored then i the fti.

I think we should at least make this *possible*, in that I don't think 
we should hardcode the dependency on an ../@@add/<FTI> name. That should 
be the result of a TALES expression that may by convention look like:

  string:${folder/absolute_url}/@@add/${portal_type}

or whatever (here I'm assuming we add a convenience variable for 
portal_type to the TALES expression context so that people don't have to 
remember to change the portal type if they copy and paste the expression 
between types).

>> In the case above, you end up having to register your form as a 
>> particular adapter rather than a browser view. That's fairly unnatural, 
>> and also doesn't necessarily deal with things like security settings. It 
>> makes the add view quite different to write than the edit view, too.
> all the forms can be registered as browserpages anyway (and should!).
> there's no difference if you call it via the browser, or get it inside
> the traverser with getMultiAdapter and return this one. even the
> security settings are considered then imo.

So, let me try to summarise what I think we're saying here:

  - My type has a form like:

class MyAddForm(CMFBaseAddForm):
     fields = form.Fields(IMyType)
     portal_type = 'My type'

  - The base form knows to look at self.factory_name to look up the 
factory when it does the create() call.

  - The base add form implements ICMFAddForm

  - I register the form as a normal <browser:page />, with the 
convention that the name is the same as the factory name

  - The FTI has an 'addview' property, which by convention is set to 
string:${folder/absolute_url}/@@add/${portal_type}

  - The @@add view looks like

class AddView(BrowserView):
     implements(IPublishTraverse)

     def publishTraverse(self, request, name):
         portal_types = getToolByName(self.context, 'portal_types')
         fti = getattr(portal_types, name)
         factory = fti.factory
         addview = getMultiAdapter((self.context, request), ICMFAddForm,
                                       name=factory)
         addview.portal_type = name
         return addview

A few things to note about this:

  - The traverser doesn't call the view, it just returns it (the 
publisher will call it when it needs to)

  - We don't look up a default, unnamed add form view. This doesn't make 
any sense unless we really can generalise all forms; frameworks like 
Dexterity may have a way to do this and thus may be able to have their 
own versions of @@add, but I don't think this something we should do at 
the CMF level.

  - This doesn't require any more registrations than the simple add form 
browser view.

  - If I don't want to use this idiom, I could change that TALES 
expression to something like string:${folder/absolute_url}/@@add-my-stuff

I quite like this approach now. ;-)

Martin



More information about the Zope-CMF mailing list