Correct way to render widget and where are my behaviors?

Plone 5.2.2, Python 3.8.5

Hi, I am trying to write a custom Pagetemplate to edit some fields of a content_type. Therefore I created a subclass from DefaultView:

from plone.dexterity.browser.view import DefaultView

class UksNew(DefaultView):
    def __call__(self):
        # Fills the dictionary self.w
        self.update()
        # Now set the mode of each widget to "input"
        for name, widget in self.w.items():
            widget.mode = 'input'

        return super().__call__()

And in my pt file I then can write something like this:

<div tal:replace="structure view/w/myCustomField/@@ploneform-render-widget" />

I am not sure if @@ploneform-render-widget is the right thing here but it works better than the render method because it also renders the label and validation errors if there are some.

However my main issue is that I am unable to render the title or any other field that comes from a behavior except IShortName.id. This works:

<div tal:replace="structure view/w/IShortName.id/ploneform-render-widget" />

But this throws an exception:

<div tal:replace="structure view/w/IBasic.title/@@ploneform-render-widget" />

In the exception you can see that only the IShortName.id widget is available and no other widget from the behaviors I defined for that type:

Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 162, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 359, in publish_module
  Module ZPublisher.WSGIPublisher, line 254, in publish
  Module ZPublisher.mapply, line 85, in mapply
  Module ZPublisher.WSGIPublisher, line 63, in call_object
  Module ldkiid.gui.browser.uksgui, line 248, in __call__
  Module plone.autoform.view, line 42, in __call__
  Module plone.autoform.view, line 33, in render
  Module Products.Five.browser.pagetemplatefile, line 126, in __call__
  Module Products.Five.browser.pagetemplatefile, line 58, in __call__
  Module zope.pagetemplate.pagetemplate, line 133, in pt_render
  Module Products.PageTemplates.engine, line 367, in __call__
  Module z3c.pt.pagetemplate, line 176, in render
  Module chameleon.zpt.template, line 307, in render
  Module chameleon.template, line 214, in render
  Module chameleon.utils, line 75, in raise_with_traceback
  Module chameleon.template, line 192, in render
  Module fa54e4b1003d1061caf3c68edad839f7, line 756, in render
  Module 4029b48a9028ca2f58238d1588b37fc8, line 860, in render_master
  Module 4029b48a9028ca2f58238d1588b37fc8, line 1526, in render_content
  Module fa54e4b1003d1061caf3c68edad839f7, line 241, in __fill_main
  Module zope.tales.expressions, line 250, in __call__
  Module Products.PageTemplates.Expressions, line 188, in _eval
  Module zope.tales.expressions, line 153, in _eval
  Module Products.PageTemplates.Expressions, line 104, in trustedBoboAwareZopeTraverse
  Module zope.traversing.adapters, line 156, in traversePathElement
   - __traceback_info__: ({'myCustomField': <DateWidget 'form.widgets.myCustomField'>, 'IShortName.id': <TextWidget 'form.widgets.IShortName.id'>}, 'IBasic.title')
  Module zope.traversing.adapters, line 61, in traverse
   - __traceback_info__: ({'myCustomField': <DateWidget 'form.widgets.myCustomField'>, 'IShortName.id': <TextWidget 'form.widgets.IShortName.id'>}, 'IBasic.title', ['@@ploneform-render-widget'])
zope.location.interfaces.LocationError: zope.location.interfaces.LocationError: ({'myCustomField': <DateWidget 'form.widgets.myCustomField'>, 'IShortName.id': <TextWidget 'form.widgets.IShortName.id'>}, 'IBasic.title')

These are the behaviors I enabled for that type:

    <element value="plone.namefromtitle"/>
    <element value="plone.excludefromnavigation"/>
    <element value="plone.shortname"/>
    <element value="plone.ownership"/>
    <element value="plone.basic"/>
    <element value="plone.locking"/>

What is it that I am missing? How can I populate the view.w dictionary with all widgets of all behaviors?

Thanks!

For now I found out that the function plone.autoform.utils.processFields puts all fields except IShortName.id into the do_no_process list. And because of that the fields are not available as widgets too.
This seems to depend on the omitted list. Here you can see an overview of the values for prefix, omitted and do_not_process after there are completely evaluated:

prefix = 'IExcludeFromNavigation'
omitted = [('exclude_from_nav', 'true')]
do_not_process = ['myCustomField', 'IExcludeFromNavigation.exclude_from_nav']

prefix = 'IShortName'
omitted = []
do_not_process = ['myCustomField']

prefix = 'IOwnership'
omitted = [('rights', 'true'), ('contributors', 'true'), ('creators', 'true')]
do_not_process = ['myCustomField', 'IOwnership.rights', 'IOwnership.contributors', 'IOwnership.creators']

prefix = 'IBasic'
omitted = [('description', 'true'), ('title', 'true')]
do_not_process = ['myCustomField', 'IBasic.description', 'IBasic.title']

Now I have to find the difference between the behaviors. Why is IShortName not omitted but all the the fields are.

Sometimes it's just that easy:

from zope.interface import implementer
from z3c.form.interfaces import IEditForm

@implementer(IEditForm)
class UksNew(DefaultView):
    ...

However I am still unsure if this is the correct fix.
How can I define my own Interface like IEditForm and then specifiy which widgets should show up on my form?

That's what i do, if i full customize add and edit forms:

# configure.zcml
<!-- Register AddForm for Collection -->
<adapter for="Products.CMFCore.interfaces.IFolderish
    zope.publisher.interfaces.browser.IDefaultBrowserLayer
    plone.dexterity.interfaces.IDexterityFTI"
  name="Collection"
  provides="zope.publisher.interfaces.browser.IBrowserPage"
  factory=".views.CollectionAddFormView"/>

<!-- Override EditForm for Collection -->
<browser:page for="plone.app.contenttypes.interfaces.ICollection"
  name="edit"
  class=".views.CollectionEditFormView"
  layer="my.addon.interfaces.IMyAddonLayer"
  permission="cmf.ModifyPortalContent" />
# interfaces.py
class IDXBaseForm(Interface):
    """ Marker Interface"""
    pass

class IMyAddonCollectionAddForm(Interface):
    """ Marker Interface"""
    pass


class IMyAddonCollectionEditForm(Interface):
    """ Marker Interface"""
    pass
# views.py
class CollectionAddFormView(DefaultAddView):
    """ Collection Add Form View """

    form = CollectionAddForm


class CollectionEditFormView(DefaultEditView):
    """ Collection Edit Form View """

    form = CollectionEditForm
# forms.py
from plone.dexterity.browser.add import DefaultAddForm
from plone.dexterity.browser.edit import DefaultEditForm

@implementer(IDXBaseForm)
class DXBaseForm(Form):

    # hold all widget from all fieldsets
    allWidgets = {}
    autoGroups = False
    enable_form_tabbing = False

    def updateFields(self):
        super(DXBaseForm, self).updateFields()
        pass

    def update(self):

        super(DXBaseForm, self).update()

        self.collectWidgets()

    def collectWidgets(self):
        # all widgets from the schema
        # the default fieldset
        for name, widgetitem in self.widgets.items():
            self.allWidgets.update({name: widgetitem})

        # all widgets from the behavior schemas
        # additional fieldset
        for group in self.groups:
            for name, widgetitem in group.widgets.items():
                self.allWidgets.update({name: widgetitem})

@implementer(IMyAddonCollectionAddForm)
class CollectionAddForm(DXBaseForm, DefaultAddForm):
    """ Collection Add Form"""

    portal_type = "Collection"

    index = ViewPageTemplateFile("templates/collection_add_edit_form.pt")


@implementer(IMyAddonCollectionEditForm)
class CollectionEditForm(DXBaseForm, DefaultEditForm):
    """ Collection Edit Form"""

    portal_type = "Collection"

    index = ViewPageTemplateFile("templates/collection_add_edit_form.pt")
<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="my.addon">

  <metal:main fill-slot="main">
    <metal:main-macro define-macro="main">

      <h1 class="documentFirstHeading"
        tal:content="view/form_instance/label | nothing" />

      <tal:status define="status view/form_instance/status"
        condition="status">
        <dl class="portalMessage error"
          tal:condition="view/form_instance/widgets/errors">
          <dt i18n:domain="plone"
            i18n:translate="">
                Error
          </dt>
          <dd tal:content="status" />
        </dl>
        <dl class="portalMessage info"
          tal:condition="not: view/form_instance/widgets/errors">
          <dt i18n:domain="plone"
            i18n:translate="">
                Info
          </dt>
          <dd tal:content="status" />
        </dl>
      </tal:status>

      <div id="content-core"
        tal:define="
            form nocall:view/form_instance;
            widgets nocall:view/form_instance/allWidgets">

        <!-- here thats my debug snippet for the names -->
        <tal:block tal:repeat="widget nocall:view/form_instance/allWidgets">
          <p tal:content="widget"></p>
        </tal:block>

        <metal:description-slot define-slot="description">
          <p class="discreet"
            tal:define="description view/description | nothing"
            tal:condition="description"
            tal:content="structure description">
                        Description
          </p>
        </metal:description-slot>

        <form tal:attributes="
                    id form/id;
                    action form/action;
                    method form/method;"
          class="enableUnloadProtection"
          enctype="multipart/form-data">

          <tal:field tal:replace="structure widgets/IBasic.title/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IBasic.description/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/ILeadImageBehavior.image/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/ILeadImageBehavior.image_caption/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IRichTextBehavior.text/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IVersionable.changeNote/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IPublication.effective/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IPublication.expires/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/ICategorization.subjects/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/ICategorization.language/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IRelatedItems.relatedItems/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IAllowDiscussion.allow_discussion/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IExcludeFromNavigation.exclude_from_nav/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IShortName.id/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IVersionable.versioning_enabled/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/ITableOfContents.table_of_contents/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IOwnership.creators/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IOwnership.contributors/@@ploneform-render-widget" />

          <tal:field tal:replace="structure widgets/IOwnership.rights/@@ploneform-render-widget" />
          
          <tal:block tal:condition="form/enableCSRFProtection|nothing"
            tal:replace="structure context/@@authenticator/authenticator" />

          <tal:block tal:condition="form/actions/values|nothing"
            repeat="action form/actions/values">

            <input type="submit"
              tal:replace="structure action/render" />

          </tal:block>

        </form>

      </div>

    </metal:main-macro>
  </metal:main>
</html>
2 Likes

Okay, the EditForm works now but only if I define the template in the configure.zcml directly. The index property of CollectionEditForm seems not to be evaluated.

But I don't get how CollectionAddForm can be called. If I go to the toolbar and choose Add... --> Collection the normal add will open, not the one I defined. I also tried renaming the property CollectionAddForm.index to CollectionAddForm.template but seems not to make any difference. Do you have any hint for me?

When I remember right, index it the template attribute name for BrowserView, for forms it is template.

Okay, it's more weird than before now.
If I set the template for the EditForm in configure.zcml it works flawlessly.

	<!-- Override EditForm for Stichtag -->
	<browser:page
		for="my.addon.content.stichtag.IStichtag"
		name="edit"
		class=".stichtag.StichtagEditFormView"
		layer="ldkiid.base.interfaces.ILdkiidBaseLayer"
		template="templates/content/stichtag_add_form.pt"
		permission="cmf.ModifyPortalContent"
	/>

But if I remove it here and set in inside the Form like so...

@implementer(IStichtagEditForm)
class StichtagEditForm(DXBaseForm, DefaultEditForm):
    """ Stichtag Edit Form"""

    portal_type = "stichtag"

    template = ViewPageTemplateFile("templates/content/stichtag_add_form.pt")

...it results in an error:

zope.location.interfaces.LocationError: zope.location.interfaces.LocationError: (<my.addon.browser.stichtag.StichtagEditForm object at 0x7efdb086c100>, 'form_instance')

So it tries to use the same template but something is different now. It seems not to know about form_instance.

However. While writing this I just found out I had to add index to StichtagEditFormView. This works fine and now looks like this:

class StichtagEditFormView(DefaultEditView):
    """ Stichtag Edit Form View """

    form = StichtagEditForm

    index = ViewPageTemplateFile("templates/content/stichtag_add_form.pt")

    def update(self):
        super().update()
        self.request.set('disable_plone.leftcolumn', 1)

I came to that conclusion because the standard FormWrapper mentions this right at the beginning of its class definition as you can see here: https://github.com/zopefoundation/plone.z3cform/blob/8b69a7607bd62c80f32c3e46e4eff0e66b7e7fae/src/plone/z3cform/layout.py#L24

Plone Foundation Code of Conduct