How to get AjaxSelectFieldWidget working on multi selection lists?

Hello everyone,

I'm running Plone 5.0.2 and making an addon which will have a custom DX which needs AjaxSelectFieldWidget. I tried using AutocompleteMultiFieldWidget but that is a whole other can of problems. I need a bunch of custom fields which will act like the plone Tags field. I've been at this all day now.

The schema definition looks like

multi_field_test = schema.Tuple(
    title=u'My Multi Field Test',
    description=u'Desc',
    required=False,
    value_type=schema.TextLine(),
    missing_value=(),
)

#directives.read_permission(multi_field_test='cmf.AddPortalContent') 
#directives.write_permission(multi_field_test='cmf.AddPortalContent') 

directives.widget(
    'multi_field_test',
    AjaxSelectFieldWidget,
    vocabulary='plone.app.vocabularies.Keywords'
)

However it is throwing:

Traceback (innermost last):
Module ZPublisher.Publish, line 138, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 48, in call_object
Module plone.app.content.browser.vocabulary, line 76, in __call__
Module plone.app.content.browser.vocabulary, line 207, in get_vocabulary
Module plone.app.dexterity.permissions, line 60, in validate
AttributeError: 'DXAddViewFieldPermissionChecker' object has no attribute '_request'

So...

  1. Where I'm going wrong?
  2. Can someone provide a good concrete example of this on github?
  3. Where does the Tags DX behavior and schema live in the plone namespace so I can reverse engineer this? (I've looked in plone.app.contenttypes and plone.behaviours)

Thank you,
David

UPDATE:
I've tried static SimpleVocabulary and it results in the same odd error. I'm wondering if the directive.widget is correct? I've seen some other people use form.widget but they didn't provide the import.

The subject field is defined here: plone.app.dexterity/plone/app/dexterity/behaviors/metadata.py at master · plone/plone.app.dexterity · GitHub

AutocompleteMultiSelectionWidget works fine in Plone 5, here an example to give you an idea how to use it.
It's a bit more complex but the tricky part is the dynamic vocabulary here, which is useful if you want to autocomplete long lists dynamicly.

from plone.autoform import directives
from plone.supermodel import model
from plone.formwidget.autocomplete.widget import AutocompleteMultiSelectionWidget
from plone.app.vocabularies.terms import safe_simpleterm_from_value
from z3c.formwidget.query.interfaces import IQuerySource
from zope import schema
from zope.interface import implementer
from zope.schema.interfaces import IContextSourceBinder
import z3c
from zope.schema.vocabulary import SimpleVocabulary
from binascii import a2b_qp


@implementer(IQuerySource)
class KeywordSource(object):
    """ A dynamic vocabulary source
    """

    def __init__(self, context):
        self.vocab = SimpleVocabulary([])
        self.context = context

    def __contains__(self, term):
        # return True, to actually ignoreMissing
        return True

    def __iter__(self):
        return self.vocab.__iter__()

    def __len__(self):
        return self.vocab.__len__()

    def getTerm(self, value):  # noqa
        # return fake term, to actually ignoreMissing
        return safe_simpleterm_from_value(value)

    def getTermByToken(self, token):  # noqa
        # return fake term, to actually ignoreMissing
        value = a2b_qp(token)
        return safe_simpleterm_from_value(value)

    def search(self, query_string=None):
        if not query_string:
            return self.vocab
        q = query_string

        # query external system for vocabulary entries,
        # here github API as an example
        q = q.lower()
        github_query = "https://api.github.com/search/repositories?q=%s+in:name&per_page=100" % q  # noqa
        r = requests.get(github_query)
        if(not r.ok):
            return []
        results = json.loads(r.text or r.content)
        repositories = sorted(set([i['full_name'] for i in results['items']]))
        self.vocab = safe_simplevocabulary_from_values(self.repositories)
        return results

@implementer(IContextSourceBinder)
class KeywordSourceBinder(object):

def __call__(self, context):
    return KeywordSource(context)


class IDynRepos(model.Schema):
    directives.widget('repos', AutocompleteMultiFieldWidget)
    repos = schema.List(
        title=u"Multiple repositories",
        value_type=schema.Choice(
            title=u"Multiple",
            source=KeywordSourceBinder()
        ),
        required=False
    )
1 Like

Thank you Maik. AutocompleteMultiFieldWidget is throwing javascript errors "formwidget_autocomplete_parser is not defined" in the Barcelona theme. It just a whole other set of problems.

Thank you all for the responses. I figured something out but is very weird.

Here is what is causing the DXAddViewFieldPermissionChecker error I've been having.

Weird Example 1:
If I create the dexterity object and leave the AjaxSelectFieldWidget empty, then go back and edit it. The field works perfectly.

Weird Example 2:
The error DXAddViewFieldPermissionChecker is coming from the ++add++ page. I can directly call the vocabularies and it has no problems when I remove the add context.

This call causes it:
http://localhost:8082/streaming/++add++polklibrary.content.viewer.models.record/@@getVocabulary?name=polklibrary.content.viewer.MyTags&field=mytags&query=&page_limit=10&page=1&_=1501602268430

This call doesn't:
http://localhost:8082/streaming/@@getVocabulary?name=polklibrary.content.viewer.MyTags&field=mytags&query=&page_limit=10&page=1&_=1501602268430

Any ideas?

The error AttributeError: 'DXAddViewFieldPermissionChecker' object has no attribute '_request' is really very strange: DXAddViewFieldPermissionChecker should be defined in plone.app.dexterity.permissions and it clearly has the attribute _request.

This said, you seem to be in a difficult to explain situation. In those cases, I always think of using debugging to get things more clear. In my development instances, I have Products.PDBDebugMode installed which activates the debugger in case of exceptions. I can then easily investigate the context of the exception. In your case, I suggest to verify whether the DXAddViewFieldPermissionChecker is really defined in the above module and whether it really lacks the _request attribute.

Thanks Dieter. I did pdb.set_trace(). Indeed _request is not defined. It is set to MockRequest(TestRequest) which implements the IWidgetsLayer. BTW, I'm running plone.app.dexterity-2.1.13. So it is TestRequest not being called.

I'm going to try to pin up the version plone.app.dexterity. I noticed some few changes to this file in github.

Thank you Dieter, Maik and Thomas.

I upgrade my dev env to 5.0.4 and the problem has been resolved. I'm fairly confident I can upgrade our prod env to 5.0.4 without issue.