Customizing p.a.z3cform layout.pt

I want to customize the wrapper template (layout.pt from p.a.z3cform) for my dexterity content types but not the default Plone items. I am already able to do this by creating a browser view for 'edit' registered for my ICustomItems. This was just one zcml directive.

For the 'add' views, I am using adapters. However, I have many custom types and I had to register an 'add' adapter for each one like below:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    i18n_domain="my.theme">

  <adapter
    name="Some Folder"
    for="Products.CMFCore.interfaces.IFolderish
         zope.publisher.interfaces.browser.IDefaultBrowserLayer
         plone.dexterity.interfaces.IDexterityFTI"
    provides="zope.publisher.interfaces.browser.IBrowserPage"
    factory=".my_content.myAddView" />

  <adapter
    name="Test Folder"
    for="Products.CMFCore.interfaces.IFolderish
         zope.publisher.interfaces.browser.IDefaultBrowserLayer
         plone.dexterity.interfaces.IDexterityFTI"
    provides="zope.publisher.interfaces.browser.IBrowserPage"
    factory=".my_content.myAddView" />

I do not need to customize any other feature except their wrapper template.

For most templates, they can be overridden via z3c.jbot but this does not work for p.a.z3cform wrapper templates (layout.pt, form.pt, etc.). The package p.a.z3cform overrode layout.pt from p.z3cform via an adapter zcml directive:

  <adapter factory=".views.layout_factory" />

What other attributes and values do I need to use in my adapter zcml configuration?

I will be trying things out but I would appreciate assistance if somebody has done the same already.

I am using Plone 6.0.13 ClassicUI with Python 3.12.5

Thanks!

I think that's not so easy and the effort is the same like you define your own add forms.

layout_factory = plone.z3cform.templates.ZopeTwoFormTemplateFactory(
    path("layout.pt"),
    form=plone.z3cform.interfaces.IFormWrapper,
    request=plone.app.z3cform.interfaces.IPloneFormLayer,
)

You need a more specific layer for the layout_factory. But plone.app.z3cform.interfaces.IPloneFormLayer is very specific in the view/form context. You can add custom browserlayer interface in the add view to the form via alsoProvides, but then you need the custom add form definition from above.

But i'm not sure... it's zope magic :wink:

2 Likes

Thank you for the information. I am now able to change the default wrapper form (p.a.z3cform.layout.pt) for the add and edit views for ALL content types. The code and configuration below made it work:

browser/layout.py

import os.path
import plone.app.z3cform.interfaces
import plone.z3cform.interfaces
import plone.z3cform.templates
import my.theme

def path(filepart):
    return os.path.join(
        os.path.dirname(my.theme.__file__),
        "browser",
        filepart,
    )

layout_factory = plone.z3cform.templates.ZopeTwoFormTemplateFactory(
    path("my_content.pt"),
    form=plone.z3cform.interfaces.IFormWrapper,
    request=my.theme.interfaces.IMyThemeLayer,
)

browser/configure.zcml

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:plone="http://namespaces.plone.org/plone"
    i18n_domain="my.theme">

  <adapter
    factory=".layout.layout_factory" />

interfaces.py

from plone.app.z3cform.interfaces import IPloneFormLayer
from zope.publisher.interfaces.browser import IDefaultBrowserLayer


class IMyThemeLayer(IDefaultBrowserLayer, IPloneFormLayer):
    """Marker interface that defines a browser layer."""

What remains is how to make the custom wrapper only work for some content types.

1 Like

I looked at some of my code, and noticed that I had written:

<!-- note: Name must be same as content type -->
<adapter
    factory=".views.MeetingAddFormView"
    provides="zope.publisher.interfaces.browser.IBrowserPage"
    for="*
         DocentIMS.ActionItems.interfaces.IMyAddonLayer
         plone.dexterity.interfaces.IDexterityFTI"
         name="Meeting"
    />

While that is not the case for the edit form:

<browser:page
    name="edit"
    for="plone.dexterity.content.Item"
    class=".views.MeetingEditFormView"
    permission="cmf.ModifyPortalContent"
    layer="My.Addon.interfaces.IMyAddonLayer"
    />

Which would basically (with that code) 'work' for all 'Items', and you could (probably) do something like this:

    def updateWidgets(self):
        super(MeetingEditForm, self).updateWidgets()
     
        if self.portal_type == "Meeting":
            self.widgets['IEventBasic.some_field'].mode = interfaces.HIDDEN_MODE

        if self.portal_type == "Another":
            self.widgets['IEventBasic.another_field'].mode = interfaces.HIDDEN_MODE

Thanks, Espen.

Finally, I made my custom wrapper template apply only to my custom types using the following:

/browser/configure.zcml

  <!-- custom edit view -->
  <browser:page
      for="my.app.interfaces.IMyContent"
      name="edit"
      class=".my_content.MyEditView"
      template="my_content.pt"
      permission="cmf.ModifyPortalContent"
      />

  <!-- custom add view -->
  <adapter
    for="my.app.interfaces.IMyContainer
         zope.publisher.interfaces.browser.IDefaultBrowserLayer
         plone.dexterity.interfaces.IDexterityFTI"
    provides="zope.publisher.interfaces.browser.IBrowserPage"
    factory=".my_content.MyAddView" />

my/theme/interfaces.py

from plone.app.z3cform.interfaces import IPloneFormLayer
from zope.publisher.interfaces.browser import IDefaultBrowserLayer


class IMyThemeLayer(IDefaultBrowserLayer, IPloneFormLayer):
    """Marker interface that defines a browser layer."""

browser/my_content.py

from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.dexterity.browser.add import DefaultAddForm
from plone.dexterity.browser.add import DefaultAddView
from plone.dexterity.browser.edit import DefaultEditForm
from plone.dexterity.browser.edit import DefaultEditView


class MyEditForm(DefaultEditForm):
    """ MyTheme edit form """


class MyEditView(DefaultEditView):
    """ MyTheme edit form view """
    form = MyEditForm


class MyAddForm(DefaultAddForm):
    """ MyTheme add form """
    # portal_type = "My Type"

class MyAddView(DefaultAddView):
    """ MyTheme add view """
    form = MyAddForm
    index = ViewPageTemplateFile("my_content.pt")

browser/layout.py
(same as my earlier post)

Notes:

  • My custom types have marker interfaces similar to dexterity (content, container, and item)
  • I did not use different FTI id and title in my custom types so I can get away with a single adapter registration for my add view. Otherwise, I will need a separate adapter registration for that particular type and I would need to add the portal_type attribute in its add form class.