Register different dataconverters for fields in the same schema

I created a dataconverter for my custom field (schema.Dict) and registered it as an adapter in configure.zcml - However, it now converts all of my schema.Dict fields.

How do I make it only convert this particular field?

    <adapter
        factory=".widgets.OrderLineDetailDataConverter" />
interface.py

@provider(IFormFieldProvider)
class IInvoiceItem(model.Schema):
    """ Form interface for Invoice line item objects
    """
    article_description = schema.Dict(
        title = _(
            'article_description_title',
            default = u'Description',
        ),
        key_type=schema.TextLine(title='key'),
        value_type=schema.TextLine(title='value'),
        required = True,
    )
    directives.widget('article_description', OrderLineDetailWidget)

widgets.py

class IOrderLineDetailWidget(IRichTextWidget):
    """ """
    pass


@implementer(IOrderLineDetailWidget, IFieldWidget)        
class OrderLineDetailWidget(RichTextWidget):
    """ """    
    pass


@adapter(IRichText, IFormLayer)
@implementer(IFieldWidget)
def OrderLineDetailFieldWidget(field, request): 
    return widget.FieldWidget(field, OrderLineDetailWidget(request))


@adapter(IDict, IFieldWidget)
class OrderLineDetailDataConverter(BaseDataConverter):
    """ converts a dict of values to RichText, depending on various factors
    """
    
    def toWidgetValue(self, value):
        """ we can not modify the dict here!
            it results in AttributeError: 'dict' object has no attribute 'output_relative_to'
        """
        html_value = u''
        printable_fields = [
            'description_article_name',
            'description_article_extra',
            'article_customs_number',
            'description_article_free_text',
        ]
        
        """ do some cool stuff """

        return RichTextValue(html_value, 'text/html', 'text/html')

If you change your data converter adapter decorator to

@adapter(IDict, IOrderLineDetailWidget)
class OrderLineDetailDataConverter(BaseDataConverter):
    ...

it should only convert your custom widget data.

Thanks @petschki that helped me to get the correct conversion for another Dict field in the schema. i.e. both Dict fields render properly if I keep the OrderLineDetailDataConverter as is, and use your suggestion on the second widget.

The first field is part of a DataGrid, the second is not.

When I use

@adapter(IDict, IOrderLineDetailWidget)
class OrderLineDetailDataConverter(BaseDataConverter):

and on the second field

@adapter(IDict, IPaymentTermsWidget)
class PaymentTermsDataConverter(BaseDataConverter):

I get the following long traceback:

Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 181, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 390, in publish_module
  Module ZPublisher.WSGIPublisher, line 285, in publish
  Module ZPublisher.mapply, line 85, in mapply
  Module Products.PDBDebugMode.wsgi_runcall, line 60, in pdb_runcall
  Module pnz.erpediem.core.browser.invoice, line 46, in __call__
  Module plone.autoform.view, line 41, in __call__
  Module plone.autoform.view, line 32, 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 134, in pt_render
  Module Products.PageTemplates.engine, line 365, in __call__
  Module z3c.pt.pagetemplate, line 174, in render
  Module chameleon.zpt.template, line 298, in render
  Module chameleon.template, line 190, in render
  Module 5504a40b068af0b58c7be7947799b788, line 544, in render
  Module 624e8ccdef9be00c7af08fce878049f1, line 921, in render_master
  Module 624e8ccdef9be00c7af08fce878049f1, line 1544, in render_content
  Module 5504a40b068af0b58c7be7947799b788, line 259, in __fill_content_core
  Module b2280e377000e3ed1752e3740849793c, line 1268, in render_invoice_pdf_view
  Module zope.tales.expressions, line 250, in __call__
  Module Products.PageTemplates.Expressions, line 221, in _eval
  Module Products.PageTemplates.Expressions, line 152, in render
  Module z3c.form.widget, line 178, in __call__
  Module zope.browserpage.viewpagetemplatefile, line 47, in __call__
  Module zope.pagetemplate.pagetemplate, line 134, in pt_render
  Module Products.PageTemplates.engine, line 365, in __call__
  Module z3c.pt.pagetemplate, line 174, in render
  Module chameleon.zpt.template, line 298, in render
  Module chameleon.template, line 190, in render
  Module 6e5b3e4076936e4d5f51fb13b0660731, line 247, in render
  Module zope.tales.expressions, line 250, in __call__
  Module Products.PageTemplates.Expressions, line 221, in _eval
  Module Products.PageTemplates.Expressions, line 152, in render
  Module z3c.form.widget, line 157, in render
  Module zope.browserpage.viewpagetemplatefile, line 47, in __call__
  Module zope.pagetemplate.pagetemplate, line 134, in pt_render
  Module Products.PageTemplates.engine, line 365, in __call__
  Module z3c.pt.pagetemplate, line 174, in render
  Module chameleon.zpt.template, line 298, in render
  Module chameleon.template, line 190, in render
  Module f5bed5718973b426c92f1b910f74fc46, line 478, in render
  Module f5bed5718973b426c92f1b910f74fc46, line 121, in render_widget_row
  Module zope.tales.expressions, line 250, in __call__
  Module Products.PageTemplates.Expressions, line 221, in _eval
  Module Products.PageTemplates.Expressions, line 152, in render
  Module collective.z3cform.datagridfield.datagridfield, line 291, in render
  Module z3c.form.object, line 375, in render
  Module z3c.form.widget, line 157, in render
  Module zope.browserpage.viewpagetemplatefile, line 47, in __call__
  Module zope.pagetemplate.pagetemplate, line 134, in pt_render
  Module Products.PageTemplates.engine, line 365, in __call__
  Module z3c.pt.pagetemplate, line 174, in render
  Module chameleon.zpt.template, line 298, in render
  Module chameleon.template, line 190, in render
  Module a460a787e86364197f01d94c494a75d4, line 197, in render
  Module zope.tales.expressions, line 250, in __call__
  Module Products.PageTemplates.Expressions, line 221, in _eval
  Module Products.PageTemplates.Expressions, line 152, in render
  Module pnz.erpediem.client.browser.widgets, line 130, in render
  Module z3c.form.widget, line 157, in render
  Module zope.browserpage.viewpagetemplatefile, line 47, in __call__
  Module zope.pagetemplate.pagetemplate, line 134, in pt_render
  Module Products.PageTemplates.engine, line 365, in __call__
  Module z3c.pt.pagetemplate, line 174, in render
  Module chameleon.zpt.template, line 298, in render
  Module chameleon.template, line 214, in render
  Module chameleon.utils, line 20, in raise_with_traceback
  Module chameleon.template, line 190, in render
  Module 904f714fdefef273fcc188ffbf3800f4, line 365, in render
  Module zope.tales.pythonexpr, line 73, in __call__
   - __traceback_info__: (value.output_relative_to(view.context))
  Module <string>, line 1, in <module>
AttributeError: 'list' object has no attribute 'output_relative_to'

 - Expression: "python:value.output_relative_to(view.context)"
 - Filename:   ... n3.8/site-packages/plone/app/textfield/widget_display.pt
 - Location:   (line 24: col 32)
 - Source:     ... "structure python:value.output_relative_to(view.context)"
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Expression: "widget/render"
 - Filename:   ... m/datagridfield/templates/datagridfieldobject_display.pt
 - Location:   (line 8: col 32)
 - Source:     ... div tal:replace="structure widget/render"></div>
                                              ^^^^^^^^^^^^^
 - Expression: "widget/render"
 - Filename:   ... z3cform/datagridfield/templates/datagridfield_display.pt
 - Location:   (line 47: col 40)
 - Source:     ... div tal:replace="structure widget/render"></div>
                                              ^^^^^^^^^^^^^
 - Expression: "view/render"
 - Filename:   ... ython3.8/site-packages/z3c/form/browser/widget_layout.pt
 - Location:   (line 15: col 45)
 - Source:     ... get" tal:content="structure view/render">
                                               ^^^^^^^^^^^
 - Expression: "view/w/invoice_items"
 - Filename:   ... /pnz/erpediem/core/browser/templates/invoice_pdf_view.pt
 - Location:   (line 182: col 56)
 - Source:     ... ol" tal:define="widget view/w/invoice_items"
                                          ^^^^^^^^^^^^^^^^^^^^
 - Expression: "provider:plone.abovecontentbody"
 - Filename:   ... ges/Products/CMFPlone/browser/templates/main_template.pt
 - Location:   (line 107: col 74)
 - Source:     ... ontent="structure provider:plone.abovecontentbody" />
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Expression: "context/@@main_template/macros/master"
 - Filename:   ... /src/pnz/erpediem/core/browser/templates/traject_view.pt
 - Location:   (line 6: col 21)
 - Source:     ... tal:use-macro="context/@@main_template/macros/master"
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Arguments:  template: <zope.browserpage.viewpagetemplatefile.ViewPageTemplateFile object at 0x7f3db5a2e1c0>
               options: {}
               args: ()
               nothing: None
               modules: <zope.pagetemplate.engine.TraversableModuleImporter object at 0x7f3dbba0ecd0>
               request: <WSGIRequest, URL=http://localhost:8888/Plone/invoices/307942/@@view>
               view: <OrderLineDetailWidget 'form.widgets.invoice_items.0.widgets.article_description'>
               context: None
               views: <zope.browserpage.viewpagetemplatefile.ViewMapper object at 0x7f3db932ce20>
               default: <DEFAULT>
               repeat: <Products.PageTemplates.engine.RepeatDictWrapper object at 0x7f3db2f2c240>
               loop: {}
               target_language: None
               translate: <function BaseTemplate.render.<locals>.translate at 0x7f3db932ba60>
               attrs: {}
               value: [('description_article_name', 'Thüring Holzentgrauer'), ('description_article_extra', 'Kd.-Art.-Nr.47981'), ('article_customs_number', '34025090'), ('description_article_free_text', ''), ('article_number_buyer', ''), ('shipping_address_country', 'CH')]

I do not understand completely. The error says, that the plone.app.textfield widget gets a list value which in fact can not work ... so a bit more details on the schema configuration would be helpful.

I have a schema IInvoiceItem that provides the columns of the DataGridField above.

among the other fields is

    article_description = schema.Dict(
        title = _(
            'article_description_title',
            default = u'Description',
        ),
        key_type=schema.TextLine(title='key'),
        value_type=schema.TextLine(title='value'),
        required = True,
    )
    directives.widget('article_description', OrderLineDetailWidget)
    

IInvoiceItem is the schema used in my invoice_items field, in in IInvoice

    invoice_items = schema.List(
        title=_(
            'invoice_items_title',
            default = u'Invoice Items',
        ),
        value_type=DictRow(
            title=_(
                'value_type_title',
                default = u'Table',
            ),
            schema=IInvoiceItem,
        ),
        default=[],
        required=False,
    )
    directives.widget('invoice_items', DataGridFieldWidgetFactory)

in the same IInvoice schema, I also have a field payment_terms

payment_terms = schema.Dict(
    description = u'',
    title = _(
        'payment_terms',
        default = u'Payment Terms',
    ),
    key_type=schema.TextLine(title='key'),
    value_type=schema.TextLine(title='value'),
    required = False,
)
directives.widget('payment_terms', PaymentTermsWidget)

Using

@adapter(IDict, IOrderLineDetailWidget)
class OrderLineDetailDataConverter(BaseDataConverter):

results in the traceback above.

Thanks. And the OrderLineDetailWidget is based on which widget? According to the traceback, this widget has the plone/app/textfield/widget_display.pt template registered and there's the problem. So it might be a wrong super class there...

Both the OrderLineDetailWidget and PaymentTermsWidget are based on RichTextWidget from plone.app.textfield - see also my first post upthread.

Sorry, overlooked that. So the question is, why your custom dataconverter isn't looked up inside the DGF schema if I'm reading correctly. I had this problem too and thought I've fixed that here fix dataconverters for DictRow by petschki · Pull Request #150 · collective/collective.z3cform.datagridfield · GitHub but obviously there might be something more missing.

You could add a breakpoint at this line https://github.com/collective/collective.z3cform.datagridfield/blob/master/src/collective/z3cform/datagridfield/row.py#L92 and look if (and why) there's an exception when looking up the column data converter. Because if there's an exception, the raw value is returned, which in your case cannot be rendered in the widget.

Yes, looks like you're reading it correctly :slight_smile:

    def toWidgetValue(self, value):
        _converted = {}
        for name, fld in self.field.schema.namesAndDescriptions():
            converter = self._getConverter(fld)
            log.info('%s - %s' % (name, converter))

With @adapter(IDict, IFieldWidget)

article_description - <OrderLineDetailDataConverter converts from Dict to MultiWidget>

and with @adapter(IDict, IOrderLineDetailWidget)

article_description - <DictMultiConverter converts from Dict to MultiWidget>