Field widget for TreeVocabulary [SOLVED]

Hi all,

I am unable to render a field for TreeVocabulary.fromDict(my_dict) in Plone 5.0.7 using these examples


or

using plone.app.z3cform.widget.SelectWidget - Traceback at bottom...

also, the AjaxSelectFieldWidget only displays the top level terms, as received from @@getVocabulary?name=mdb_theme.TestDynamicTree&field=test_tree_vocab

As a possible alternative, I looked at collective.dynatree (as suggested in collective.vdexvocabulary) but this appears to be a Plone4 project and relying on a custom js framework. There is a mockup pattern which looks nice, is there a fieldwidget implementation of http://plone.github.io/mockup/dev/#pattern/tree ?

Any other options?

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.z3cform.layout, line 66, in __call__
  Module plone.z3cform.layout, line 60, in update
  Module z3c.form.form, line 162, in render
  Module zope.browserpage.viewpagetemplatefile, line 51, in __call__
  Module zope.pagetemplate.pagetemplate, line 132, in pt_render
  Module five.pt.engine, line 98, in __call__
  Module z3c.pt.pagetemplate, line 163, in render
  Module chameleon.zpt.template, line 261, in render
  Module chameleon.template, line 171, in render
  Module a3583e4527d394bdb7e9da558e9a86a2.py, line 91, in render
  Module e0a1136da86f253326fdefc41a4628b2.py, line 1809, in render_titlelessform
  Module e0a1136da86f253326fdefc41a4628b2.py, line 811, in render_fields
  Module e0a1136da86f253326fdefc41a4628b2.py, line 126, in render_widget_rendering
  Module e0a1136da86f253326fdefc41a4628b2.py, line 1070, in render_field
  Module five.pt.expressions, line 161, in __call__
  Module Products.Five.browser.metaconfigure, line 485, in __call__
  Module zope.browserpage.viewpagetemplatefile, line 83, in __call__
  Module zope.browserpage.viewpagetemplatefile, line 51, in __call__
  Module zope.pagetemplate.pagetemplate, line 132, in pt_render
  Module five.pt.engine, line 98, in __call__
  Module z3c.pt.pagetemplate, line 163, in render
  Module chameleon.zpt.template, line 261, in render
  Module chameleon.template, line 191, in render
  Module chameleon.template, line 171, in render
  Module ff6d9d0b890fa3488040cd291a773153.py, line 610, in render
  Module ff6d9d0b890fa3488040cd291a773153.py, line 481, in render_widget_wrapper
  Module five.pt.expressions, line 161, in __call__
  Module plone.app.z3cform.widget, line 120, in render
  Module plone.app.widgets.base, line 178, in __init__
  Module plone.app.widgets.base, line 202, in _set_items
ValueError: too many values to unpack

 - Expression: "widget/@@ploneform-render-widget"
 - Filename:   ... emplates/overrides/plone.app.z3cform.templates.macros.pt
 - Location:   (line 99: col 81)
 - Source:     ... place="structure widget/@@ploneform-render-widget"/>
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Expression: "widget/render"
 - Filename:   ... rm-3.0.9-py2.7.egg/plone/app/z3cform/templates/widget.pt
 - Location:   (line 39: col 46)
 - Source:     ... xt" tal:replace="structure widget/render"
                                              ^^^^^^^^^^^^^
 - Arguments:  repeat: {...} (0)
               context: <SelectWidget test_tree_vocab at 0x7f897750cc10>
               views: <ViewMapper - at 0x7f8977463a90>
               modules: <TraversableModuleImporter - at 0x7f89897d1350>
               args: <tuple - at 0x7f899228d050>
               nothing: <NoneType - at 0x91a870>
               target_language: <NoneType - at 0x91a870>
               default: <object - at 0x7f89921a9530>
               request: <instance - at 0x7f897e8a3440>
               wrapped_repeat: {...} (0)
               loop: {...} (0)
               template: <ViewPageTemplateFile - at 0x7f89771b67d0>
               translate: <function translate at 0x7f8977452a28>
               options: {...} (0)
               view: <RenderWidget ploneform-render-widget at 0x7f89770a6490>

Might want to look at collective.taxonomy? Though the widget could need some Love...

Thanks @petschki - I actually tried collective taxonomy before going with the homebrew TreeVocabulary approach. There are many things I like about it - including the TTW editing ability.

A blocker for me is the way the translation of terms is stored. We manage all our other translated terms (headings, field content) with Pootle and sync those back to Plone .po files. Then the TAL templates take care of displaying content in the requested language.

Edit to add, plus I need the term tokens to be predictable, as they are used in other parts of my application.

Meanwhile, I was able to pin these:

# p.a.w 2.4.1 provides support for optgroup in select forms
plone.app.widgets = 2.4.1
# p.a.c. 3.5.4. is the last one before refactoring getObjSize (human_readable_size)
plone.app.content = 3.5.4
# try bleeding edge plone.app.z3cform
plone.app.z3cform = 3.0.9

and reach a potentially adequate solution: using a SelectWidget, I can display/edit all options in an optgroup (e.g. second-level)

    <field name="test_tree_vocab" type="zope.schema.Set" lingua:independent="true">
      <form:widget type="plone.app.z3cform.widget.SelectWidget" />
      <value_type type="zope.schema.Choice">
        <vocabulary>mdb_theme.TestDynamicTree</vocabulary>
      </value_type>
      <multiple>True</multiple>
      <required>False</required>
      <description>Tree selector</description>
      <title>Tree</title>
    </field>

One caveat, the new pins brought back to life an issue with fields which use RelationList or RelationChoice objects, probably the querysource rooted wrong. Will attempt to tackle that after the weekend...

Also, collective.taxonomy does not uninstall properly. Found myself with a dangling persistent utility
AttributeError: type object 'ITaxonomy' has no attribute '__iro__' even after removing my test taxonomy from the registry before uninstalling...

For big projects, I suggest to use an external vocabulary server. For example:

http://iqvoc.net/

http://skosmos.org/

This can also help you to reuse vocabularies and ontologies, and be ready for semantic web, If you need it.

Update, I finally gave up on using a single widget. My use case can also be resolved with collective.z3cform.datagridfield - using a master and slave field. The vocabulary in the slave field gets filtered by the master value whenever the master field changes.

At some point in the future, I might take a better look at collective.dynatree to see if it could be ported to Plone 5.

Update 2
Icouldn't let this go, and ended up updating Mikko Ohtamaa's dgftreeselect widget for Plone5, this was quite easy to do. I also needed the json used in the example widget to be dynamic, and ended up subclassing VocabularyView from plone.app.content

so with this as an interface,

class IPackagingRowSchema(Interface):
    packaging_unit_key = schema.TextLine( title   = _(u'Mengeneinheit')
                                        , required=True
                                        )
    packaging_unit_value = schema.TextLine( title = _(u'Gebindegröße')
                                          , required=True
                                          )
    directives.widget('packaging_unit_key',   DGFTreeSelectFieldWidget)
    directives.widget('packaging_unit_value', DGFTreeSelectFieldWidget)
IPackagingRowSchema.setTaggedValue("vocabulary", 'my.product.CustomTreeVocabulary')

and this view

# -*- coding: utf-8 -*-
from plone.app.content.browser.vocabulary import BaseVocabularyView
from plone.app.content.browser.vocabulary import VocabularyView
from zope.schema.interfaces import ITreeVocabulary
from collections import OrderedDict

import json

class TreeVocabularyView(BaseVocabularyView):

    def __call__(self):
        """
        Accepts GET parameters of:
        name: Name of the vocabulary
        field: Name of the field the vocabulary is being retrieved for
        """

        context = self.context
        request = self.request

        request.response.setHeader(
            'Content-Type', 'application/json; charset=utf-8'
        )

        vocabulary = VocabularyView(self, request).get_vocabulary()

        if ITreeVocabulary.providedBy(vocabulary):
            level = 0
            items = get_terms(vocabulary, level, [])

        else:
            items = [{ 'id'    : term.token
                     , 'label' : term.title
                     } for term in vocabulary]

        return json.dumps(items)


def get_terms(vocabulary, level, terms):
    for term, child_terms in vocabulary.items():
        item = OrderedDict()

        item['id'] = term.token
        item['label'] = term.title

        if child_terms and level < 10:
            level += 1
            item['children'] = get_terms(child_terms, level, [])

        terms.append(item)

    return terms

the dgftreewidget can use

http://localhost:8080/plone/@@getTreeVocabulary?name=my.product.CustomTreeVocabulary&field=my_custom_field

and get its data...

Since there is not much documentation and examples out there, i posted an example in my blog.

2 Likes

Thanks :+1: