DefaultView widgets have no values, unless

Split this separate question from Plone.autoform - object has no attribute __iro__

I am experimenting with making an object that is generated on the fly by SQLA behave along the lines of a regular Dexterity content type. My goal is to modernize example.trajectory.

In my view, I intend to use @@ploneform-render-widget to render all fields and their values. However, unless I define each field as a property in the view wrapper, the widgets will have no value.

Since I plan on creating several new content types along these lines, How do I avoid having to define dozens of properties for each of the new content types?

Given that I subclass the Dexterity object from a SQLA base class, do I still require this wrapper?

Here are my various parts...

A schema:

@provider(IFormFieldProvider)
class IInvoicIn(model.Schema):
    """ Form interface for InvoicIn content type
    """

    document_id = schema.TextLine(
        title = _(u'Invoice Number'),
        description = u'',
        required=True
    )

    document_date = schema.TextLine (
        title = _(u'Date'),
        description = u'',
        required=False,
    )

    document_type = schema.TextLine (
        title = _(u'Type'),
        description = u'',
        required=False,
    )

    originator_reference_id = schema.TextLine (
        title = _(u'Originator'),
        description = u'',
        required=False,
    )

A SQLA base class:

class InvoicInBase(Base):

    __tablename__ = 'invoic_in'

    document_id               = Column(types.Unicode(20), primary_key=True)
    document_date             = Column(types.Date(),      default=null())

    document_type             = Column(types.Unicode(3),  default=null())
    originator_reference_id   = Column(types.Unicode(20), default=null())

The Dexterity subclass:

@implementer(IInvoicIn)
@adapter(IDexterityContent, Interface)
class InvoicIn(InvoicInBase, object):
    """ My InvoicIn Content
    """
    def __init__(self, context):
        self.context = context            

My view:

@implementer(IDexterityContent)
class InvoicInView(DefaultView):
    """ Default view for InvoicIn content
    """

    def __init__(self, context, request):
        self.context = context
        self.request = request

    def __call__(self, content_type=None, *args, **kw):        
        context = self.context
        request = self.request
        
        return super(InvoicInView, self).__call__()

The view wrapper:

@implementer(IInvoicIn)    
class InvoicInWrapper(Model):
    """A simple object representing the SQLAlchemy content within the Plone
    context.
    """

    portal_type = 'InvoicIn'

    document_id  = None

    def __init__(self, document_id):
        self.document_id  = document_id
        self.invoic_in = get_invoic_in_by_id(document_id)

    def Title(self):
        return self.invoic_in.document_id

    def Description(self):
        return u''

    @property        
    def document_date(self):
        return self.invoic_in.document_date

    @property
    def document_type(self):
        return self.invoic_in.document_type

    @property
    def originator_reference_id(self):
        return self.invoic_in.originator_reference_id

    @property
    def originator_reference_date(self):
        return self.invoic_in.originator_reference_date

InitializeClass(InvoicInWrapper)

def get_invoic_in_by_id(invoic_in_id):
    """Given an invoic_in_id, return a SQLAlchemy InvoicIn object with that document_id."""
    session = session_client(dbconnection)
    result = session.query(InvoicIn).filter(InvoicIn.document_id == invoic_in_id).scalar()
    return result

My zcml:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:i18n="http://namespaces.zope.org/i18n"
    i18n_domain="pnz.erpediem.core">

    <!-- Set up trajectory adapters, allowing us to view InvoicIns at
    /invoic_ins/{id}
    -->
    <include package="collective.trajectory" />
    <adapter factory="collective.trajectory.components.Traverser"
             for="pnz.erpediem.core.interfaces.IInvoicInContainer
                  zope.publisher.interfaces.IRequest"/>

    <!-- Make 'view', provide as a default view for a InvoicIn object -->
    <browser:defaultView
        name="view"
        for=".model.IInvoicIn"
        layer="...interfaces.IPnzErpediemCoreLayer"
        />

    <browser:page
        name="view"
        for=".model.IInvoicIn"
        class=".browser.InvoicInView"
        permission="zope2.View"
        layer="...interfaces.IPnzErpediemCoreLayer"
        template="view.pt"
        />

</configure>

And the view:

<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="plone">
<body>

<metal:content-core fill-slot="content-core">
<metal:content-core define-macro="content-core">
  <form id="form" class="enableFormTabbing pat-autotoc autotabs"
                  data-pat-autotoc="levels: legend; section: fieldset; className: autotabs">
    <fieldset id="form-groups-main-content">
      <legend i18n:translate="" >
        <span tal:omit-tag i18n:translate="">Seite</span> 1
      </legend>
      <tal:block repeat="widget view/widgets/values" >
        <tal:widget tal:replace="structure widget/@@ploneform-render-widget"/>
      </tal:block>
    </form>
</metal:content-core>
</metal:content-core>
</body>
</html>

Resulting in:

Norbert via Plone Community wrote at 2022-6-17 14:34 +0000:

...
My goal is to have a view that uses @@ploneform-render-widget to render all fields and their values. However, unless I define each field as a property in the view wrapper, the widgets will have no value.

You could define __getattr__ to delegate all lookups
(maybe restricted to names defined by a specific model)
to an underlying object.
This would avoid the definition of individual properties.

Yes! Again, thank you so much @dieter - that was the missing link

I glanced at example.trajectory/src/example/trajectory/product/browser.py at master · esteele/example.trajectory · GitHub and noticed that the edit view defines getContent()

All I needed to do was to update the view to get the widgets, and then run getContent to get all the values.

My updated view:

@implementer(IDexterityContent)
class InvoicInView(DefaultView):
    """ Default view for InvoicIn content
    """

    def __init__(self, context, request):
        self.context = context
        self.request = request

    def __call__(self, content_type=None, *args, **kw):
             
        context = self.context
        request = self.request

        self.update()
        self.getContent()
        
        return super(InvoicInView, self).__call__()

    def getContent(self):
        invoic_in = self.context.invoic_in
        ret = {'document_id': invoic_in.document_id}
        for field_id in schema.getFields(IInvoicIn):
            ret[field_id] = getattr(invoic_in, field_id)
        return ret

Just a follow up, and again thank you @dieter!

This is why I love Zope/Plone!