Vocabulary-related errors with a custom widget
Question
I have two Dexterity content types - Participant & Criterion - the latter of which is used to determine participant inclusion in a project. Criteria are stored on the Participant as a RelationList, however I'd like to replace the default picker widget with a checkbox based one. I've created a custom widget that I'm assigning to the criteria field which displays the correct criteria as checkboxes, but gives me the following error when it validates:
2011-11-04 00:27:26 ERROR Zope.SiteErrorLog 1320380846.610.720672558798 http://192.168.2.128:8080/ctcc/Trials/trial1/sites/site1/++add++ctcc.Participant
Traceback (innermost last):
Module ZPublisher.Publish, line 126, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 46, in call_object
Module plone.z3cform.layout, line 70, in __call__
Module plone.z3cform.layout, line 54, in update
Module plone.z3cform.fieldsets.extensible, line 59, in update
Module plone.z3cform.patch, line 30, in GroupForm_update
Module z3c.form.group, line 125, in update
Module z3c.form.form, line 134, in updateWidgets
Module z3c.form.field, line 275, in update
Module z3c.form.browser.checkbox, line 44, in update
Module z3c.form.browser.widget, line 70, in update
Module z3c.form.widget, line 200, in update
Module z3c.form.widget, line 84, in update
Module z3c.form.widget, line 216, in extract
AttributeError: 'list' object has no attribute 'getTermByToken'
I have defined the widget like so:
class ICriteriaListingWidget(Interface):
"""Marker interface for the criteria listing widget"""
class CriteriaSelectionWidget(CheckBoxWidget):
implements(ICriteriaListingWidget)
klass = u'criteria-listing-widget'
input_template = ViewPageTemplateFile('criteria_listing_input.pt')
display_template = ViewPageTemplateFile('criteria_listing_display.pt')
@property
def terms(self):
catalog = getToolByName(self.context, 'portal_catalog')
content = catalog(
portal_type='ctcc.Criterion',
)
return [SimpleTerm(x.id, x, title=x.Title) for x in content]
@adapter(IRelationList, IFormLayer)
@implementer(IFieldWidget)
def CriteriaListingWidget(field, request):
return FieldWidget(field, CriteriaSelectionWidget(request))
Then on the Participant Dexterity type, the field is:
form.widget(criteria=CriteriaListingWidget)
criteria = RelationList(
title = _(u'Inclusion Criteria'),
description = _(u'The participant criteria evaluated against for inclusion'),
value_type = RelationChoice(
source = ObjPathSourceBinder(
object_provides = ICriterion.__identifier__,
),
),
default = [],
required = False,
)
Given the nature of the error, I replaced the ObjPathSourceBinder source with a custom Vocabulary for criteria hoping it would return objects with the correct interface, but I'm seeing the exact same errors using that solution.
UPDATE: I've wrapped the terms list in a SimpleVocabulary as suggested but it just shifts the problem along. Note that the error is now occurring in kss_z3cform_inline_validation.
2011-11-06 19:24:36 ERROR Zope.SiteErrorLog 1320625476.430.209960132592 http://192.168.2.128:8080/ctcc/Trials/trial1/sites/site1/kss_z3cform_inline_validation
Traceback (innermost last):
Module ZPublisher.Publish, line 126, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 46, in call_object
Module <wrapper>, line 5, in wrapper
Module kss.core.actionwrapper, line 236, in apply
Module plone.app.z3cform.kss.validation, line 51, in validate_input
Module z3c.form.group, line 92, in extractData
Module z3c.form.form, line 145, in extractData
Module z3c.form.field, line 301, in extract
Module z3c.form.converter, line 311, in toFieldValue
AttributeError: 'SimpleVocabulary' object has no attribute 'getValue'
2011-11-06 19:24:36 ERROR plone.transformchain Unexpected error whilst trying to apply transform chain
Solution 2
I'm not sure if this counts as an answer per se but I ended up avoiding the need for a custom widget by creating custom Source & SourceBinder objects.
class CTCCSource(object):
implements(IVocabularyTokenized)
container = None
content_type = None
def __init__(self, context):
self.context = context
container_path = self._container_path(context)
self.catalog = getToolByName(context, 'portal_catalog')
self.intid_utility = getUtility(IIntIds)
self.content = [i.getObject() for i in self.catalog(portal_type=self.content_type, path={'query': container_path})]
def _container_path(self, context):
physical_path = list(context.getPhysicalPath())
trial_path = physical_path[:physical_path.index('sites')]
criteria_path = trial_path + [self.container]
return '/'.join(criteria_path)
def __contains__(self, term):
return term in self.content
def __iter__(self):
for crit in self.content:
yield SimpleTerm(crit, self.intid_utility.getId(crit), crit.Title)
def __len__(self):
return len(self.content)
def getTerm(self, obj):
return SimpleVocabulary.createTerm(
obj, self.intid_utility.getId(obj), obj.Title()
)
def getTermByToken(self, value):
return self.getTerm(self.intid_utility.getObject(int(value)))
class CTCCSourceBinder(object):
implements(IContextSourceBinder)
source = None
def __init__(self, **kw):
pass
def __call__(self, context):
return self.source(context)
This allowed me to produce form checkbox fields from content contained within the current context, adding references to the chosen options into a RelatedList.
OTHER TIPS
You will need to return a vocabulary object, not a list:
from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
# ...
class CriteriaSelectionWidget(CheckBoxWidget):
# ...
@property
def terms(self):
catalog = getToolByName(self.context, 'portal_catalog')
content = catalog(
portal_type='ctcc.Criterion',
)
return SimpleVocabulary([SimpleTerm(x.id, x, title=x.Title) for x in content])
The SimpleVocabulary
class implements the correct methods for this case.