Added permissions widgets
authorThierry Florac <thierry.florac@onf.fr>
Mon, 20 Apr 2015 09:51:14 +0200
changeset 33 c422e7f61f19
parent 32 5b6650d040c2
child 34 b84b491ea8bd
Added permissions widgets
src/pyams_security/widget/__init__.py
src/pyams_security/widget/interfaces.py
src/pyams_security/widget/templates/permission-display.pt
src/pyams_security/widget/templates/permission-input.pt
src/pyams_security/widget/templates/permissions-set-display.pt
src/pyams_security/widget/templates/permissions-set-input.pt
--- a/src/pyams_security/widget/__init__.py	Mon Apr 20 09:50:51 2015 +0200
+++ b/src/pyams_security/widget/__init__.py	Mon Apr 20 09:51:14 2015 +0200
@@ -18,15 +18,18 @@
 
 # import interfaces
 from pyams_form.interfaces.form import IFormLayer
-from pyams_security.interfaces import ISecurityManager
-from pyams_security.schema import IPrincipal, IPrincipalsSet
-from pyams_security.widget.interfaces import IPrincipalWidget, IPrincipalsSetWidget
-from z3c.form.interfaces import IDataConverter, IFieldWidget
+from pyams_security.interfaces import ISecurityManager, IPermission
+from pyams_security.schema import IPrincipal, IPrincipalsSet, IPermissionField, IPermissionsSetField
+from pyams_security.widget.interfaces import IPrincipalWidget, IPrincipalsSetWidget, IPermissionWidget, \
+    IPermissionsSetWidget
+from z3c.form.interfaces import IDataConverter, IFieldWidget, NO_VALUE
+from zope.schema.interfaces import ITitledTokenizedTerm
 
 # import packages
 from pyams_form.widget import widgettemplate_config
 from pyams_utils.adapter import adapter_config
-from pyams_utils.registry import query_utility
+from pyams_utils.registry import query_utility, get_all_utilities_registered_for, get_utilities_for
+from z3c.form.browser.select import SelectWidget
 from z3c.form.browser.widget import HTMLInputWidget
 from z3c.form.converter import BaseDataConverter
 from z3c.form.widget import Widget, FieldWidget
@@ -34,6 +37,148 @@
 
 
 #
+# Permission widget
+#
+
+@adapter_config(context=(IPermissionField, IPermissionWidget), provides=IDataConverter)
+class PermissionDataConverter(BaseDataConverter):
+    """Permission data converter"""
+
+    def toWidgetValue(self, value):
+        # Permission widget is waiting for a Permission object
+        if not value:
+            return None
+        return query_utility(IPermission, name=value)
+
+    def toFieldValue(self, value):
+        # Permission widget only returns selected permission ID
+        return value
+
+
+@widgettemplate_config(mode='input', template='templates/permission-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/permission-display.pt', layer=IFormLayer)
+@implementer_only(IPermissionWidget)
+class PermissionWidget(SelectWidget):
+    """Principal widget"""
+
+    def isSelected(self, term):
+        return term.token == self.value.id
+
+    def items(self):
+        if self.terms is None:  # update() has not been called yet
+            return ()
+        items = []
+        if (not self.required or self.prompt) and self.multiple is None:
+            message = self.noValueMessage
+            if self.prompt:
+                message = self.promptMessage
+            items.append({'id': self.id + '-novalue',
+                          'value': self.noValueToken,
+                          'content': message,
+                          'selected': self.value == []})
+
+        ignored = {self.value.id} if self.value else set()
+
+        def addItem(idx, term, prefix=''):
+            selected = self.isSelected(term)
+            if selected and term.token in ignored:
+                ignored.remove(term.token)
+            id = '%s-%s%i' % (self.id, prefix, idx)
+            content = term.token
+            if ITitledTokenizedTerm.providedBy(term):
+                content = self.request.localizer.translate(term.title)
+            items.append({'id': id, 'value': term.token, 'content': content, 'selected': selected})
+
+        for idx, term in enumerate(self.terms):
+            addItem(idx, term)
+
+        if ignored:
+            # some values are not displayed, probably they went away from the vocabulary
+            for idx, token in enumerate(sorted(ignored)):
+                try:
+                    term = self.terms.getTermByToken(token)
+                except LookupError:
+                    # just in case the term really went away
+                    continue
+
+                addItem(idx, term, prefix='missing-')
+        return items
+
+    @property
+    def value_map(self):
+        if not self.value:
+            return ''
+        translate = self.request.localizer.translate
+        return json.dumps({self.value.id: translate(self.value.title)})
+
+    def extract(self, default=NO_VALUE):
+        result = super(PermissionWidget, self).extract(default)
+        if result != default:
+            result = result[0]
+        return result
+
+
+@adapter_config(context=(IPermissionField, IFormLayer), provides=IFieldWidget)
+def PermissionFieldWidget(field, request):
+    """Permission field widget factory"""
+    return FieldWidget(field, PermissionWidget(request))
+
+
+#
+# Permissions set widget
+#
+
+@adapter_config(context=(IPermissionsSetField, IPermissionsSetWidget), provides=IDataConverter)
+class PermissionsSetDataConverter(BaseDataConverter):
+    """Permissions set data converter"""
+
+    def toWidgetValue(self, value):
+        # Permissions set widget is waiting for a set of Permission object
+        if not value:
+            return ()
+        return sorted((query_utility(IPermission, name=permission_id) for permission_id in value),
+                      key=lambda x: x.title)
+
+    def toFieldValue(self, value):
+        # Permissions set widget only returns selected permissions IDs in a single input
+        if not value:
+            return set()
+        return set(value.split('|'))
+
+
+@widgettemplate_config(mode='input', template='templates/permissions-set-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/permissions-set-display.pt', layer=IFormLayer)
+@implementer_only(IPermissionsSetWidget)
+class PermissionsSetWidget(HTMLInputWidget, Widget):
+    """Permissions set widget"""
+
+    @property
+    def values_data(self):
+        translate = self.request.localizer.translate
+        registry = self.request.registry
+        return json.dumps(sorted([{'id': permission.id, 'text': translate(permission.title)}
+                                  for name, permission in registry.getUtilitiesFor(IPermission)],
+                                 key=lambda x: x['text']))
+
+    @property
+    def values_str(self):
+        return '|'.join((permission.id for permission in (self.value or ())))
+
+    @property
+    def values_map(self):
+        result = {}
+        translate = self.request.localizer.translate
+        [result.update({value.id: translate(value.title)}) for value in (self.value or ())]
+        return json.dumps(result)
+
+
+@adapter_config(context=(IPermissionsSetField, IFormLayer), provides=IFieldWidget)
+def PermissionsSetFieldWidget(field, request):
+    """Permissions set field widget factory"""
+    return FieldWidget(field, PermissionsSetWidget(request))
+
+
+#
 # Principal widget
 #
 
--- a/src/pyams_security/widget/interfaces.py	Mon Apr 20 09:50:51 2015 +0200
+++ b/src/pyams_security/widget/interfaces.py	Mon Apr 20 09:51:14 2015 +0200
@@ -21,6 +21,14 @@
 # import packages
 
 
+class IPermissionWidget(IWidget):
+    """Permission widget"""
+
+
+class IPermissionsSetWidget(IWidget):
+    """Permissions set widget"""
+
+
 class IPrincipalWidget(IWidget):
     """Principal widget"""
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/permission-display.pt	Mon Apr 20 09:51:14 2015 +0200
@@ -0,0 +1,24 @@
+<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/id | 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;
+					data-ams-select2-values view/value_map;" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/permission-input.pt	Mon Apr 20 09:51:14 2015 +0200
@@ -0,0 +1,40 @@
+<label class="input bordered with-icon"
+	   tal:omit-tag="view/required" i18n:domain="pyams_security">
+	<i class="icon-append fa fa-trash-o hint opaque"
+		title="Clear selected values" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		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">
+		<select class="select2"
+				tal:attributes="id view/id;
+								name string:${view/name};
+								class string:select2 ${view/klass};
+								style view/style;
+								title view/title;
+								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;
+								multiple view/multiple;
+								size view/size;">
+			<option tal:repeat="entry view/items"
+					tal:attributes="value entry/value;
+									selected entry/selected;"
+					tal:content="entry/content"></option>
+		</select>
+	</div>
+</label>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/permissions-set-display.pt	Mon Apr 20 09:51:14 2015 +0200
@@ -0,0 +1,26 @@
+<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((value.id for value in 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;
+					data-ams-select2-values view/values_map;" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_security/widget/templates/permissions-set-input.pt	Mon Apr 20 09:51:14 2015 +0200
@@ -0,0 +1,44 @@
+<label class="input bordered with-icon"
+	   tal:omit-tag="view/required" i18n:domain="pyams_security">
+	<i class="icon-append fa fa-trash-o hint opaque"
+		title="Clear selected values" i18n:attributes="title"
+		tal:omit-tag="view/required"
+		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" class="select2"
+				data-ams-select2-placeholder="Select permissions..."
+				data-ams-select2-allow-clear="true"
+				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 view/values_str;
+								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-data view/values_data;
+								data-ams-select2-values view/values_map;"
+				i18n:attributes="data-ams-select2-placeholder" />
+	</div>
+</label>