Moved widget module to ZMI
authorThierry Florac <thierry.florac@onf.fr>
Wed, 11 Jul 2018 10:18:57 +0200
changeset 77 5eb2f0ff9ba3
parent 76 1d0d13ab5665
child 78 8bab1101f4ef
Moved widget module to ZMI
src/pyams_thesaurus/widget/__init__.py
src/pyams_thesaurus/widget/interfaces.py
src/pyams_thesaurus/widget/templates/term-display.pt
src/pyams_thesaurus/widget/templates/term-input.pt
src/pyams_thesaurus/widget/templates/terms-list-display.pt
src/pyams_thesaurus/widget/templates/terms-list-input.pt
src/pyams_thesaurus/widget/templates/terms-list-selector-input.pt
src/pyams_thesaurus/widget/templates/terms-tree-input.pt
src/pyams_thesaurus/zmi/widget/__init__.py
src/pyams_thesaurus/zmi/widget/interfaces.py
src/pyams_thesaurus/zmi/widget/templates/term-display.pt
src/pyams_thesaurus/zmi/widget/templates/term-input.pt
src/pyams_thesaurus/zmi/widget/templates/terms-list-display.pt
src/pyams_thesaurus/zmi/widget/templates/terms-list-input.pt
src/pyams_thesaurus/zmi/widget/templates/terms-list-selector-input.pt
src/pyams_thesaurus/zmi/widget/templates/terms-tree-input.pt
--- a/src/pyams_thesaurus/widget/__init__.py	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,212 +0,0 @@
-#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-import json
-import re
-
-# import interfaces
-from pyams_form.interfaces.form import IFormLayer
-from pyams_thesaurus.interfaces.term import IThesaurusTerm
-from pyams_thesaurus.interfaces.thesaurus import IThesaurus
-from pyams_thesaurus.schema import IThesaurusTermField, IThesaurusTermsListField
-from pyams_thesaurus.widget.interfaces import IThesaurusTermWidget, IThesaurusTermsListWidget
-from z3c.form.interfaces import IDataConverter, IFieldWidget
-
-# import packages
-from pyams_form.widget import widgettemplate_config
-from pyams_utils.adapter import adapter_config
-from pyams_utils.registry import get_utility, query_utility
-from pyams_utils.traversing import get_parent
-from z3c.form.browser.widget import HTMLInputWidget
-from z3c.form.converter import BaseDataConverter
-from z3c.form.widget import Widget, FieldWidget
-from zope.interface import implementer_only
-from zope.schema.fieldproperty import FieldProperty
-
-
-SYNONYM = re.compile('(.*)\ \[\ .*\ \]')
-
-
-#
-# Term widget
-#
-
-@adapter_config(context=(IThesaurusTermField, IThesaurusTermWidget), provides=IDataConverter)
-class ThesaurusTermDataConverter(BaseDataConverter):
-    """Thesaurus term data converter"""
-
-    def toWidgetValue(self, value):
-        # Widget expects term label or caption
-        if value is self.field.missing_value:
-            return ''
-        if IThesaurusTerm.providedBy(self.widget.context):
-            return value.label
-        else:
-            return value.title
-
-    def toFieldValue(self, value):
-        # Field expects thesaurus term instance
-        if not value:
-            return self.field.missing_value
-        match = SYNONYM.match(value)
-        if match:
-            value = match.groups()[0]
-        thesaurus_name = self.widget.thesaurus_name or self.field.thesaurus_name
-        if thesaurus_name:
-            thesaurus = get_utility(IThesaurus, name=thesaurus_name)
-        else:
-            thesaurus = get_parent(self.widget.context, IThesaurus)
-            if thesaurus is None:
-                thesaurus = query_utility(IThesaurus, name=thesaurus_name)
-        if thesaurus is None:
-            return None
-        else:
-            return thesaurus.terms.get(value)
-
-
-@widgettemplate_config(mode='input', template='templates/term-input.pt', layer=IFormLayer)
-@widgettemplate_config(mode='display', template='templates/term-display.pt', layer=IFormLayer)
-@implementer_only(IThesaurusTermWidget)
-class ThesaurusTermWidget(HTMLInputWidget, Widget):
-    """Thesaurus term widget"""
-
-    thesaurus_name = FieldProperty(IThesaurusTermWidget['thesaurus_name'])
-    extract_name = FieldProperty(IThesaurusTermWidget['extract_name'])
-
-    @property
-    def query_params(self):
-        return json.dumps({'thesaurus_name': self.thesaurus_name,
-                           'extract_name': self.extract_name})
-
-    @property
-    def values_map(self):
-        return json.dumps({self.value: self.value})
-
-
-@adapter_config(context=(IThesaurusTermField, IFormLayer), provides=IFieldWidget)
-def ThesaurusTermFieldWidget(field, request):
-    """Thesaurus term field widget factory"""
-    return FieldWidget(field, ThesaurusTermWidget(request))
-
-
-#
-# Terms list widget
-#
-
-@adapter_config(context=(IThesaurusTermsListField, IThesaurusTermsListWidget), provides=IDataConverter)
-class ThesaurusTermsListDataConverter(BaseDataConverter):
-    """Thesaurus terms list data converter"""
-
-    def __init__(self, field, widget):
-        super(ThesaurusTermsListDataConverter, self).__init__(field, widget)
-
-    def toWidgetValue(self, value):
-        # Widget expects a list of thesaurus terms labels or captions
-        if value is self.field.missing_value:
-            return []
-        if IThesaurusTerm.providedBy(self.widget.context):
-            return [term.label for term in value]
-        else:
-            return [term.title for term in value]
-
-    def toFieldValue(self, value):
-        # Field expects a list of thesaurus terms
-        if not value:
-            return self.field.missing_value
-        thesaurus_name = self.widget.thesaurus_name or self.field.thesaurus_name
-        if thesaurus_name:
-            thesaurus = get_utility(IThesaurus, name=thesaurus_name)
-        else:
-            thesaurus = get_parent(self.widget.context, IThesaurus)
-            if thesaurus is None:
-                thesaurus = query_utility(IThesaurus, name=thesaurus_name)
-        if thesaurus is None:
-            return None
-        if isinstance(value, str):
-            value = value.split('|')
-        for idx, val in enumerate(value):
-            match = SYNONYM.match(val)
-            if match:
-                value[idx] = match.groups()[0]
-        terms = thesaurus.terms
-        return [terms.get(term) for term in value]
-
-
-@widgettemplate_config(mode='input', template='templates/terms-list-input.pt', layer=IFormLayer)
-@widgettemplate_config(mode='display', template='templates/terms-list-display.pt', layer=IFormLayer)
-@implementer_only(IThesaurusTermsListWidget)
-class ThesaurusTermsListWidget(HTMLInputWidget, Widget):
-    """Thesaurus terms list widget"""
-
-    thesaurus_name = FieldProperty(IThesaurusTermsListWidget['thesaurus_name'])
-    extract_name = FieldProperty(IThesaurusTermsListWidget['extract_name'])
-
-    @property
-    def query_params(self):
-        return json.dumps({'thesaurus_name': self.thesaurus_name,
-                           'extract_name': self.extract_name})
-
-    @property
-    def values_map(self):
-        result = {}
-        [result.update({value: value}) for value in (self.value or ())]
-        return json.dumps(result)
-
-
-@adapter_config(context=(IThesaurusTermsListField, IFormLayer), provides=IFieldWidget)
-def ThesaurusTermsListFieldWidget(field, request):
-    """Thesaurus terms list field widget factory"""
-    return FieldWidget(field, ThesaurusTermsListWidget(request))
-
-
-@widgettemplate_config(mode='input', template='templates/terms-tree-input.pt', layer=IFormLayer)
-class ThesaurusTermsTreeWidget(ThesaurusTermsListWidget):
-    """Thesaurus terms tree widget"""
-
-    @property
-    def top_terms(self):
-        thesaurus = query_utility(IThesaurus, name=self.thesaurus_name)
-        if thesaurus is not None:
-            return sorted(thesaurus.get_top_terms(extract=self.extract_name),
-                          key=lambda x: x.label)
-        else:
-            return ()
-
-    def get_subterms(self, term):
-        for subterm in term.specifics:
-            if (not self.extract_name) or (self.extract_name in subterm.extracts):
-                yield subterm
-                for another in self.get_subterms(subterm):
-                    yield another
-
-
-def ThesaurusTermsTreeFieldWidget(field, request):
-    """Thesaurus terms tree field widget factory"""
-    return FieldWidget(field, ThesaurusTermsTreeWidget(request))
-
-
-#
-# Terms list widget with selector
-#
-
-@widgettemplate_config(mode='input', template='templates/terms-list-selector-input.pt', layer=IFormLayer)
-class ThesaurusTermsListSelectorWidget(ThesaurusTermsListWidget):
-    """Thesaurus terms list widget with selector"""
-
-
-def ThesaurusTermsListSelectorFieldWidget(field, request):
-    """Thesaurus terms list field widget with selector factory"""
-    return FieldWidget(field, ThesaurusTermsListSelectorWidget(request))
--- a/src/pyams_thesaurus/widget/interfaces.py	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-
-__docformat__ = 'restructuredtext'
-
-
-# import standard library
-
-# import interfaces
-from z3c.form.interfaces import IWidget
-
-# import packages
-from zope.schema import TextLine
-
-
-class IThesaurusTermWidget(IWidget):
-    """Single term widget"""
-
-    thesaurus_name = TextLine(title="Thesaurus name",
-                              required=False)
-
-    extract_name = TextLine(title="Extract name",
-                            required=False)
-
-
-class IThesaurusTermsListWidget(IWidget):
-    """Terms list widget"""
-
-    thesaurus_name = TextLine(title="Thesaurus name",
-                              required=False,
-                              default='')
-
-    extract_name = TextLine(title="Extract name",
-                            required=False,
-                            default='')
--- a/src/pyams_thesaurus/widget/templates/term-display.pt	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-<input type="hidden" autocomplete="off" readonly
-	tal:attributes="id view/id;
-					name view/name;
-					class string:select2 ${view/klass};
-					style view/style;
-					title view/title;
-					value view/value | nothing;
-					lang view/lang;
-					onclick view/onclick;
-					ondblclick view/ondblclick;
-					onmousedown view/onmousedown;
-					onmouseup view/onmouseup;
-					onmouseover view/onmouseover;
-					onmousemove view/onmousemove;
-					onmouseout view/onmouseout;
-					onkeypress view/onkeypress;
-					onkeydown view/onkeydown;
-					onkeyup view/onkeyup;
-					disabled view/disabled;
-					tabindex view/tabindex;
-					onfocus view/onfocus;
-					onblur view/onblur;
-					onchange view/onchange;
-					accesskey view/accesskey;
-					onselect view/onselect;" />
--- a/src/pyams_thesaurus/widget/templates/term-input.pt	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<label class="input bordered with-icon"
-	   tal:omit-tag="view/required" i18n:domain="pyams_thesaurus">
-	<i class="icon-append fa fa-trash-o text-primary hint opaque"
-		title="Clear selected value" i18n:attributes="title"
-		tal:omit-tag="view/required"
-		data-ams-hint-gravity="se"
-		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
-		tal:attributes="data-ams-select2-target view/name"></i>
-	<div class="select2-parent"
-		 tal:omit-tag="view/required">
-		<input type="hidden" autocomplete="off"
-			data-ams-select2-minimum-input-length="2"
-			data-ams-select2-allow-clear="true"
-			data-ams-select2-multiple="true"
-			data-ams-select2-maximum-selection-size="1"
-			data-ams-select2-query-method="findTerms"
-			data-ams-select2-method-target="/api/thesaurus/json"
-			tal:attributes="id view/id;
-							name view/name;
-							class string:select2 ${view/klass};
-							style view/style;
-							title view/title;
-							value view/value | nothing;
-							lang view/lang;
-							onclick view/onclick;
-							ondblclick view/ondblclick;
-							onmousedown view/onmousedown;
-							onmouseup view/onmouseup;
-							onmouseover view/onmouseover;
-							onmousemove view/onmousemove;
-							onmouseout view/onmouseout;
-							onkeypress view/onkeypress;
-							onkeydown view/onkeydown;
-							onkeyup view/onkeyup;
-							disabled view/disabled;
-							tabindex view/tabindex;
-							onfocus view/onfocus;
-							onblur view/onblur;
-							onchange view/onchange;
-							readonly view/readonly;
-							accesskey view/accesskey;
-							onselect view/onselect;
-							data-ams-select2-values view/values_map;
-							data-ams-select2-query-params view/query_params;" />
-	</div>
-</label>
--- a/src/pyams_thesaurus/widget/templates/terms-list-display.pt	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-<input type="hidden" autocomplete="off" readonly
-	data-ams-select2-multiple="true"
-	data-ams-select2-separator="|"
-	tal:attributes="id view/id;
-					name view/name;
-					class string:select2 ${view/klass};
-					style view/style;
-					title view/title;
-					value python:'|'.join(view.value);
-					lang view/lang;
-					onclick view/onclick;
-					ondblclick view/ondblclick;
-					onmousedown view/onmousedown;
-					onmouseup view/onmouseup;
-					onmouseover view/onmouseover;
-					onmousemove view/onmousemove;
-					onmouseout view/onmouseout;
-					onkeypress view/onkeypress;
-					onkeydown view/onkeydown;
-					onkeyup view/onkeyup;
-					disabled view/disabled;
-					tabindex view/tabindex;
-					onfocus view/onfocus;
-					onblur view/onblur;
-					onchange view/onchange;
-					accesskey view/accesskey;
-					onselect view/onselect;
-					data-ams-select2-values view/values_map;" />
--- a/src/pyams_thesaurus/widget/templates/terms-list-input.pt	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-<label class="input bordered with-icon"
-	   tal:omit-tag="view/required" i18n:domain="pyams_thesaurus">
-	<i class="icon-append fa fa-trash-o text-primary hint opaque"
-		title="Clear selected values" i18n:attributes="title"
-		tal:omit-tag="view/required"
-		data-ams-hint-gravity="se"
-		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
-		tal:attributes="data-ams-select2-target view/name"></i>
-	<div class="select2-parent"
-		 tal:omit-tag="view/required">
-		<input type="hidden" autocomplete="off"
-			data-ams-select2-minimum-input-length="2"
-			data-ams-select2-allow-clear="true"
-			data-ams-select2-multiple="true"
-			data-ams-select2-separator="|"
-			data-ams-select2-query-method="findTerms"
-			data-ams-select2-method-target="/api/thesaurus/json"
-			tal:attributes="id view/id;
-							name view/name;
-							class string:select2 ${view/klass};
-							style view/style;
-							title view/title;
-							value python:'|'.join(view.value or ());
-							lang view/lang;
-							onclick view/onclick;
-							ondblclick view/ondblclick;
-							onmousedown view/onmousedown;
-							onmouseup view/onmouseup;
-							onmouseover view/onmouseover;
-							onmousemove view/onmousemove;
-							onmouseout view/onmouseout;
-							onkeypress view/onkeypress;
-							onkeydown view/onkeydown;
-							onkeyup view/onkeyup;
-							disabled view/disabled;
-							tabindex view/tabindex;
-							onfocus view/onfocus;
-							onblur view/onblur;
-							onchange view/onchange;
-							readonly view/readonly;
-							accesskey view/accesskey;
-							onselect view/onselect;
-							data-ams-select2-values view/values_map;
-							data-ams-select2-query-params view/query_params;" />
-	</div>
-</label>
--- a/src/pyams_thesaurus/widget/templates/terms-list-selector-input.pt	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-<label class="input bordered with-icons" i18n:domain="pyams_thesaurus">
-	<i class="icon-append fa fa-trash-o text-primary hint opaque"
-		title="Clear selected values" i18n:attributes="title"
-		tal:omit-tag="view/required"
-		data-ams-hint-gravity="se"
-		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
-		tal:attributes="data-ams-select2-target view/name"></i>
-	<i class="icon-append icon-append-2 fa fa-th-list text-primary hint opaque"
-		title="Show terms selector" i18n:attributes="title"
-		data-ams-url="MyAMS.helpers.select2ClearSelection"
-		data-toggle="modal"
-		tal:attributes="data-ams-select2-target view/name"></i>
-	<div class="select2-parent">
-		<input type="hidden" autocomplete="off"
-			data-ams-select2-minimum-input-length="2"
-			data-ams-select2-allow-clear="true"
-			data-ams-select2-multiple="true"
-			data-ams-select2-separator="|"
-			data-ams-select2-query-method="findTerms"
-			data-ams-select2-method-target="/api/thesaurus/json"
-			tal:attributes="id view/id;
-							name view/name;
-							class string:select2 ${view/klass};
-							style view/style;
-							title view/title;
-							value python:'|'.join(view.value or ());
-							lang view/lang;
-							onclick view/onclick;
-							ondblclick view/ondblclick;
-							onmousedown view/onmousedown;
-							onmouseup view/onmouseup;
-							onmouseover view/onmouseover;
-							onmousemove view/onmousemove;
-							onmouseout view/onmouseout;
-							onkeypress view/onkeypress;
-							onkeydown view/onkeydown;
-							onkeyup view/onkeyup;
-							disabled view/disabled;
-							tabindex view/tabindex;
-							onfocus view/onfocus;
-							onblur view/onblur;
-							onchange view/onchange;
-							readonly view/readonly;
-							accesskey view/accesskey;
-							onselect view/onselect;
-							data-ams-select2-values view/values_map;
-							data-ams-select2-query-params view/query_params;" />
-	</div>
-</label>
--- a/src/pyams_thesaurus/widget/templates/terms-tree-input.pt	Wed Jul 11 10:18:34 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-<tal:loop repeat="term view/top_terms">
-	<div class="col col-xs-6 col-sm-4 col-md-4 col-lg-3">
-		<div class="ams-widget" data-ams-widget-toggle-button="false"
-			 tal:define="index python:repeat['term'].index()"
-			 tal:attributes="id string:them_${index}">
-			<header class="no-margin">
-				<h2 tal:content="term/label"></h2>
-			</header>
-			<div class="widget-body no-padding viewport-y viewport-200 viewport-x-none"
-				 style="height: 200px; width: calc(100% - 2px);">
-				<tal:loop repeat="subterm python:view.get_subterms(term)">
-					<div tal:define="padding python:(subterm.level - 1) * 20"
-						 tal:attributes="style string:padding-left: ${padding}px;; line-height: 1em;;">
-						<label class="checkbox"
-							   tal:define="published python:subterm.status == 'published'"
-							   tal:omit-tag="not:published">
-							<input type="checkbox" name="form.widgets.themes:list"
-								   tal:condition="published"
-								   tal:attributes="id string:term_${subterm/label};
-												   name string:${view/name}:list;
-												   value subterm/label;
-												   checked python:subterm.label in view.value" />
-							<i></i>
-							<div tal:attributes="class python:'' if published else 'bold margin-top-5'"
-								 tal:content="subterm/label"></div>
-						</label>
-					</div>
-				</tal:loop>
-			</div>
-		</div>
-	</div>
-</tal:loop>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/__init__.py	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,212 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import json
+import re
+
+# import interfaces
+from pyams_form.interfaces.form import IFormLayer
+from pyams_thesaurus.interfaces.term import IThesaurusTerm
+from pyams_thesaurus.interfaces.thesaurus import IThesaurus
+from pyams_thesaurus.schema import IThesaurusTermField, IThesaurusTermsListField
+from pyams_thesaurus.zmi.widget.interfaces import IThesaurusTermWidget, IThesaurusTermsListWidget
+from z3c.form.interfaces import IDataConverter, IFieldWidget
+
+# import packages
+from pyams_form.widget import widgettemplate_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import get_utility, query_utility
+from pyams_utils.traversing import get_parent
+from z3c.form.browser.widget import HTMLInputWidget
+from z3c.form.converter import BaseDataConverter
+from z3c.form.widget import Widget, FieldWidget
+from zope.interface import implementer_only
+from zope.schema.fieldproperty import FieldProperty
+
+
+SYNONYM = re.compile('(.*)\ \[\ .*\ \]')
+
+
+#
+# Term widget
+#
+
+@adapter_config(context=(IThesaurusTermField, IThesaurusTermWidget), provides=IDataConverter)
+class ThesaurusTermDataConverter(BaseDataConverter):
+    """Thesaurus term data converter"""
+
+    def toWidgetValue(self, value):
+        # Widget expects term label or caption
+        if value is self.field.missing_value:
+            return ''
+        if IThesaurusTerm.providedBy(self.widget.context):
+            return value.label
+        else:
+            return value.title
+
+    def toFieldValue(self, value):
+        # Field expects thesaurus term instance
+        if not value:
+            return self.field.missing_value
+        match = SYNONYM.match(value)
+        if match:
+            value = match.groups()[0]
+        thesaurus_name = self.widget.thesaurus_name or self.field.thesaurus_name
+        if thesaurus_name:
+            thesaurus = get_utility(IThesaurus, name=thesaurus_name)
+        else:
+            thesaurus = get_parent(self.widget.context, IThesaurus)
+            if thesaurus is None:
+                thesaurus = query_utility(IThesaurus, name=thesaurus_name)
+        if thesaurus is None:
+            return None
+        else:
+            return thesaurus.terms.get(value)
+
+
+@widgettemplate_config(mode='input', template='templates/term-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/term-display.pt', layer=IFormLayer)
+@implementer_only(IThesaurusTermWidget)
+class ThesaurusTermWidget(HTMLInputWidget, Widget):
+    """Thesaurus term widget"""
+
+    thesaurus_name = FieldProperty(IThesaurusTermWidget['thesaurus_name'])
+    extract_name = FieldProperty(IThesaurusTermWidget['extract_name'])
+
+    @property
+    def query_params(self):
+        return json.dumps({'thesaurus_name': self.thesaurus_name,
+                           'extract_name': self.extract_name})
+
+    @property
+    def values_map(self):
+        return json.dumps({self.value: self.value})
+
+
+@adapter_config(context=(IThesaurusTermField, IFormLayer), provides=IFieldWidget)
+def ThesaurusTermFieldWidget(field, request):
+    """Thesaurus term field widget factory"""
+    return FieldWidget(field, ThesaurusTermWidget(request))
+
+
+#
+# Terms list widget
+#
+
+@adapter_config(context=(IThesaurusTermsListField, IThesaurusTermsListWidget), provides=IDataConverter)
+class ThesaurusTermsListDataConverter(BaseDataConverter):
+    """Thesaurus terms list data converter"""
+
+    def __init__(self, field, widget):
+        super(ThesaurusTermsListDataConverter, self).__init__(field, widget)
+
+    def toWidgetValue(self, value):
+        # Widget expects a list of thesaurus terms labels or captions
+        if value is self.field.missing_value:
+            return []
+        if IThesaurusTerm.providedBy(self.widget.context):
+            return [term.label for term in value]
+        else:
+            return [term.title for term in value]
+
+    def toFieldValue(self, value):
+        # Field expects a list of thesaurus terms
+        if not value:
+            return self.field.missing_value
+        thesaurus_name = self.widget.thesaurus_name or self.field.thesaurus_name
+        if thesaurus_name:
+            thesaurus = get_utility(IThesaurus, name=thesaurus_name)
+        else:
+            thesaurus = get_parent(self.widget.context, IThesaurus)
+            if thesaurus is None:
+                thesaurus = query_utility(IThesaurus, name=thesaurus_name)
+        if thesaurus is None:
+            return None
+        if isinstance(value, str):
+            value = value.split('|')
+        for idx, val in enumerate(value):
+            match = SYNONYM.match(val)
+            if match:
+                value[idx] = match.groups()[0]
+        terms = thesaurus.terms
+        return [terms.get(term) for term in value]
+
+
+@widgettemplate_config(mode='input', template='templates/terms-list-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/terms-list-display.pt', layer=IFormLayer)
+@implementer_only(IThesaurusTermsListWidget)
+class ThesaurusTermsListWidget(HTMLInputWidget, Widget):
+    """Thesaurus terms list widget"""
+
+    thesaurus_name = FieldProperty(IThesaurusTermsListWidget['thesaurus_name'])
+    extract_name = FieldProperty(IThesaurusTermsListWidget['extract_name'])
+
+    @property
+    def query_params(self):
+        return json.dumps({'thesaurus_name': self.thesaurus_name,
+                           'extract_name': self.extract_name})
+
+    @property
+    def values_map(self):
+        result = {}
+        [result.update({value: value}) for value in (self.value or ())]
+        return json.dumps(result)
+
+
+@adapter_config(context=(IThesaurusTermsListField, IFormLayer), provides=IFieldWidget)
+def ThesaurusTermsListFieldWidget(field, request):
+    """Thesaurus terms list field widget factory"""
+    return FieldWidget(field, ThesaurusTermsListWidget(request))
+
+
+@widgettemplate_config(mode='input', template='templates/terms-tree-input.pt', layer=IFormLayer)
+class ThesaurusTermsTreeWidget(ThesaurusTermsListWidget):
+    """Thesaurus terms tree widget"""
+
+    @property
+    def top_terms(self):
+        thesaurus = query_utility(IThesaurus, name=self.thesaurus_name)
+        if thesaurus is not None:
+            return sorted(thesaurus.get_top_terms(extract=self.extract_name),
+                          key=lambda x: x.label)
+        else:
+            return ()
+
+    def get_subterms(self, term):
+        for subterm in term.specifics:
+            if (not self.extract_name) or (self.extract_name in subterm.extracts):
+                yield subterm
+                for another in self.get_subterms(subterm):
+                    yield another
+
+
+def ThesaurusTermsTreeFieldWidget(field, request):
+    """Thesaurus terms tree field widget factory"""
+    return FieldWidget(field, ThesaurusTermsTreeWidget(request))
+
+
+#
+# Terms list widget with selector
+#
+
+@widgettemplate_config(mode='input', template='templates/terms-list-selector-input.pt', layer=IFormLayer)
+class ThesaurusTermsListSelectorWidget(ThesaurusTermsListWidget):
+    """Thesaurus terms list widget with selector"""
+
+
+def ThesaurusTermsListSelectorFieldWidget(field, request):
+    """Thesaurus terms list field widget with selector factory"""
+    return FieldWidget(field, ThesaurusTermsListSelectorWidget(request))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/interfaces.py	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from z3c.form.interfaces import IWidget
+
+# import packages
+from zope.schema import TextLine
+
+
+class IThesaurusTermWidget(IWidget):
+    """Single term widget"""
+
+    thesaurus_name = TextLine(title="Thesaurus name",
+                              required=False)
+
+    extract_name = TextLine(title="Extract name",
+                            required=False)
+
+
+class IThesaurusTermsListWidget(IWidget):
+    """Terms list widget"""
+
+    thesaurus_name = TextLine(title="Thesaurus name",
+                              required=False,
+                              default='')
+
+    extract_name = TextLine(title="Extract name",
+                            required=False,
+                            default='')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/templates/term-display.pt	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,25 @@
+<input type="hidden" autocomplete="off" readonly
+	tal:attributes="id view/id;
+					name view/name;
+					class string:select2 ${view/klass};
+					style view/style;
+					title view/title;
+					value view/value | nothing;
+					lang view/lang;
+					onclick view/onclick;
+					ondblclick view/ondblclick;
+					onmousedown view/onmousedown;
+					onmouseup view/onmouseup;
+					onmouseover view/onmouseover;
+					onmousemove view/onmousemove;
+					onmouseout view/onmouseout;
+					onkeypress view/onkeypress;
+					onkeydown view/onkeydown;
+					onkeyup view/onkeyup;
+					disabled view/disabled;
+					tabindex view/tabindex;
+					onfocus view/onfocus;
+					onblur view/onblur;
+					onchange view/onchange;
+					accesskey view/accesskey;
+					onselect view/onselect;" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/templates/term-input.pt	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,46 @@
+<label class="input bordered with-icon"
+	   tal:omit-tag="view/required" i18n:domain="pyams_thesaurus">
+	<i class="icon-append fa fa-trash-o text-primary hint opaque"
+		title="Clear selected value" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		data-ams-hint-gravity="se"
+		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
+		tal:attributes="data-ams-select2-target view/name"></i>
+	<div class="select2-parent"
+		 tal:omit-tag="view/required">
+		<input type="hidden" autocomplete="off"
+			data-ams-select2-minimum-input-length="2"
+			data-ams-select2-allow-clear="true"
+			data-ams-select2-multiple="true"
+			data-ams-select2-maximum-selection-size="1"
+			data-ams-select2-query-method="findTerms"
+			data-ams-select2-method-target="/api/thesaurus/json"
+			tal:attributes="id view/id;
+							name view/name;
+							class string:select2 ${view/klass};
+							style view/style;
+							title view/title;
+							value view/value | nothing;
+							lang view/lang;
+							onclick view/onclick;
+							ondblclick view/ondblclick;
+							onmousedown view/onmousedown;
+							onmouseup view/onmouseup;
+							onmouseover view/onmouseover;
+							onmousemove view/onmousemove;
+							onmouseout view/onmouseout;
+							onkeypress view/onkeypress;
+							onkeydown view/onkeydown;
+							onkeyup view/onkeyup;
+							disabled view/disabled;
+							tabindex view/tabindex;
+							onfocus view/onfocus;
+							onblur view/onblur;
+							onchange view/onchange;
+							readonly view/readonly;
+							accesskey view/accesskey;
+							onselect view/onselect;
+							data-ams-select2-values view/values_map;
+							data-ams-select2-query-params view/query_params;" />
+	</div>
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/templates/terms-list-display.pt	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,28 @@
+<input type="hidden" autocomplete="off" readonly
+	data-ams-select2-multiple="true"
+	data-ams-select2-separator="|"
+	tal:attributes="id view/id;
+					name view/name;
+					class string:select2 ${view/klass};
+					style view/style;
+					title view/title;
+					value python:'|'.join(view.value);
+					lang view/lang;
+					onclick view/onclick;
+					ondblclick view/ondblclick;
+					onmousedown view/onmousedown;
+					onmouseup view/onmouseup;
+					onmouseover view/onmouseover;
+					onmousemove view/onmousemove;
+					onmouseout view/onmouseout;
+					onkeypress view/onkeypress;
+					onkeydown view/onkeydown;
+					onkeyup view/onkeyup;
+					disabled view/disabled;
+					tabindex view/tabindex;
+					onfocus view/onfocus;
+					onblur view/onblur;
+					onchange view/onchange;
+					accesskey view/accesskey;
+					onselect view/onselect;
+					data-ams-select2-values view/values_map;" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/templates/terms-list-input.pt	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,46 @@
+<label class="input bordered with-icon"
+	   tal:omit-tag="view/required" i18n:domain="pyams_thesaurus">
+	<i class="icon-append fa fa-trash-o text-primary hint opaque"
+		title="Clear selected values" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		data-ams-hint-gravity="se"
+		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
+		tal:attributes="data-ams-select2-target view/name"></i>
+	<div class="select2-parent"
+		 tal:omit-tag="view/required">
+		<input type="hidden" autocomplete="off"
+			data-ams-select2-minimum-input-length="2"
+			data-ams-select2-allow-clear="true"
+			data-ams-select2-multiple="true"
+			data-ams-select2-separator="|"
+			data-ams-select2-query-method="findTerms"
+			data-ams-select2-method-target="/api/thesaurus/json"
+			tal:attributes="id view/id;
+							name view/name;
+							class string:select2 ${view/klass};
+							style view/style;
+							title view/title;
+							value python:'|'.join(view.value or ());
+							lang view/lang;
+							onclick view/onclick;
+							ondblclick view/ondblclick;
+							onmousedown view/onmousedown;
+							onmouseup view/onmouseup;
+							onmouseover view/onmouseover;
+							onmousemove view/onmousemove;
+							onmouseout view/onmouseout;
+							onkeypress view/onkeypress;
+							onkeydown view/onkeydown;
+							onkeyup view/onkeyup;
+							disabled view/disabled;
+							tabindex view/tabindex;
+							onfocus view/onfocus;
+							onblur view/onblur;
+							onchange view/onchange;
+							readonly view/readonly;
+							accesskey view/accesskey;
+							onselect view/onselect;
+							data-ams-select2-values view/values_map;
+							data-ams-select2-query-params view/query_params;" />
+	</div>
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/templates/terms-list-selector-input.pt	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,49 @@
+<label class="input bordered with-icons" i18n:domain="pyams_thesaurus">
+	<i class="icon-append fa fa-trash-o text-primary hint opaque"
+		title="Clear selected values" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		data-ams-hint-gravity="se"
+		data-ams-click-handler="MyAMS.helpers.select2ClearSelection"
+		tal:attributes="data-ams-select2-target view/name"></i>
+	<i class="icon-append icon-append-2 fa fa-th-list text-primary hint opaque"
+		title="Show terms selector" i18n:attributes="title"
+		data-ams-url="MyAMS.helpers.select2ClearSelection"
+		data-toggle="modal"
+		tal:attributes="data-ams-select2-target view/name"></i>
+	<div class="select2-parent">
+		<input type="hidden" autocomplete="off"
+			data-ams-select2-minimum-input-length="2"
+			data-ams-select2-allow-clear="true"
+			data-ams-select2-multiple="true"
+			data-ams-select2-separator="|"
+			data-ams-select2-query-method="findTerms"
+			data-ams-select2-method-target="/api/thesaurus/json"
+			tal:attributes="id view/id;
+							name view/name;
+							class string:select2 ${view/klass};
+							style view/style;
+							title view/title;
+							value python:'|'.join(view.value or ());
+							lang view/lang;
+							onclick view/onclick;
+							ondblclick view/ondblclick;
+							onmousedown view/onmousedown;
+							onmouseup view/onmouseup;
+							onmouseover view/onmouseover;
+							onmousemove view/onmousemove;
+							onmouseout view/onmouseout;
+							onkeypress view/onkeypress;
+							onkeydown view/onkeydown;
+							onkeyup view/onkeyup;
+							disabled view/disabled;
+							tabindex view/tabindex;
+							onfocus view/onfocus;
+							onblur view/onblur;
+							onchange view/onchange;
+							readonly view/readonly;
+							accesskey view/accesskey;
+							onselect view/onselect;
+							data-ams-select2-values view/values_map;
+							data-ams-select2-query-params view/query_params;" />
+	</div>
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/widget/templates/terms-tree-input.pt	Wed Jul 11 10:18:57 2018 +0200
@@ -0,0 +1,32 @@
+<tal:loop repeat="term view/top_terms">
+	<div class="col col-xs-6 col-sm-4 col-md-3 col-lg-3">
+		<div class="ams-widget" data-ams-widget-toggle-button="false"
+			 tal:define="index python:repeat['term'].index()"
+			 tal:attributes="id string:them_${index}">
+			<header class="no-margin">
+				<h2 tal:content="term/label"></h2>
+			</header>
+			<div class="widget-body no-padding viewport-y viewport-200 viewport-x-none"
+				 style="height: 200px; width: calc(100% - 2px);">
+				<tal:loop repeat="subterm python:view.get_subterms(term)">
+					<div tal:define="padding python:(subterm.level - 1) * 20"
+						 tal:attributes="style string:padding-left: ${padding}px;; line-height: 1em;;">
+						<label class="checkbox"
+							   tal:define="published python:subterm.status == 'published'"
+							   tal:omit-tag="not:published">
+							<input type="checkbox" name="form.widgets.themes:list"
+								   tal:condition="published"
+								   tal:attributes="id string:term_${subterm/label};
+												   name string:${view/name}:list;
+												   value subterm/label;
+												   checked python:subterm.label in view.value" />
+							<i></i>
+							<div tal:attributes="class python:'' if published else 'bold margin-top-5'"
+								 tal:content="subterm/label"></div>
+						</label>
+					</div>
+				</tal:loop>
+			</div>
+		</div>
+	</div>
+</tal:loop>