plone.app.vocabularies/principals_Plone51: Add principals vocabularies which combines users and groups

Philip Bauer <jenkins-z4DKO/[email protected]>
Newsgroups gmane.comp.web.zope.plone.cvs
Message-ID <[email protected]>
Repository: plone.app.vocabularies
Branch: refs/heads/principals_Plone51
Date: 2017-07-14T16:00:46+02:00
Author: vangheem (vangheem) <[email protected]>
Commit: https://github.com/plone/plone.app.vocabularies/commit/17e5e156032ad6eb1e157172e25653970a2507ca

Add principals vocabularies which combines users and groups into one vocabulary

Files changed:
A plone/app/vocabularies/principals.py
M CHANGES.rst
M plone/app/vocabularies/configure.zcml
M plone/app/vocabularies/groups.py
M plone/app/vocabularies/security.py
M plone/app/vocabularies/tests/test_vocabularies.py

diff --git a/CHANGES.rst b/CHANGES.rst
index d867725..f98fc16 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -10,7 +10,8 @@ Breaking changes:
 
 New features:
 
-- *add item here*
+- Add principals vocabularies which combines users and groups into one vocabulary
+  [vangheem]
 
 Bug fixes:
 
diff --git a/plone/app/vocabularies/configure.zcml b/plone/app/vocabularies/configure.zcml
index acc8d86..4cf0a0c 100644
--- a/plone/app/vocabularies/configure.zcml
+++ b/plone/app/vocabularies/configure.zcml
@@ -158,6 +158,11 @@
     name="plone.app.vocabularies.ImagesScales"
     />
 
+  <utility
+     factory=".principals.PrincipalsFactory"
+     name="plone.app.vocabularies.Principals"
+     />
+
 
   <!-- All source implementations will be deprecated -->
   <adapter
diff --git a/plone/app/vocabularies/groups.py b/plone/app/vocabularies/groups.py
index bac86f6..a7c3222 100644
--- a/plone/app/vocabularies/groups.py
+++ b/plone/app/vocabularies/groups.py
@@ -72,6 +72,7 @@ def search(self, query):
         # title, only name, and seems to match other random groups if
         # it's unicode
 
+        query = query or ''
         try:
             name = query.encode('ascii')
         except UnicodeEncodeError:
diff --git a/plone/app/vocabularies/principals.py b/plone/app/vocabularies/principals.py
new file mode 100644
index 0000000..90e7ef4
--- /dev/null
+++ b/plone/app/vocabularies/principals.py
@@ -0,0 +1,171 @@
+from plone.app.vocabularies import SlicableVocabulary
+from plone.app.vocabularies.groups import GroupsSource
+from plone.app.vocabularies.security import GroupsVocabularyFactory
+from plone.app.vocabularies.users import _createUserTerm
+from plone.app.vocabularies.users import UsersFactory
+from plone.app.vocabularies.users import UsersSource
+from Products.CMFCore.utils import getToolByName
+from zope.component.hooks import getSite
+from zope.interface import implementer
+from zope.interface import provider
+from zope.schema.interfaces import IContextSourceBinder
+from zope.schema.interfaces import ISource
+from zope.schema.interfaces import IVocabularyFactory
+from zope.schema.vocabulary import SimpleTerm
+
+
+@implementer(ISource)
+@provider(IContextSourceBinder)
+class PrincipalsSource(object):
+    """
+      >>> from plone.app.vocabularies.tests.base import create_context
+      >>> from plone.app.vocabularies.tests.base import DummyTool
+
+      >>> context = create_context()
+
+      >>> tool = DummyTool('acl_users')
+      >>> users = ('user1', 'user2')
+      >>> def getUserById(value, default):
+      ...     return value in users and value or default
+      >>> tool.getUserById = getUserById
+      >>> def searchUsers(fullname=None):
+      ...     return [dict(userid=u) for u in users]
+      >>> tool.searchUsers = searchUsers
+      >>> groups = ('group1', 'group2')
+      >>> def getGroupById(value, default):
+      ...     return value in groups and value or default
+      >>> tool.getGroupById = getGroupById
+      >>> def searchGroups(name=None):
+      ...     return [dict(groupid=u) for u in groups]
+      >>> tool.searchGroups = searchGroups
+      >>> context.acl_users = tool
+
+      >>> source = PrincipalsSource(context)
+      >>> source
+      <plone.app.vocabularies.principals.PrincipalsSource object at ...>
+
+      >>> len(source.search(None))
+      4
+
+      >>> 'user:user1' in source, 'user:noone' in source
+      (True, False)
+
+      >>> 'group:group1' in source, 'group:noone' in source
+      (True, False)
+
+      >>> source.get('user:user1'), source.get('user:noone')
+      ('user1', None)
+
+     >>> source.get('group:group1'), source.get('group:noone')
+     ('group1', None)
+    """
+
+    def __init__(self, context):
+        self.context = context
+        self.users_source = UsersSource(context)
+        self.groups_source = GroupsSource(context)
+
+    def __contains__(self, value):
+        """Return whether the value is available in this source
+        """
+        if self.get(value) is None:
+            return False
+        return True
+
+    def search(self, query):
+        results = self.users_source.search(query)
+        results.extend(self.groups_source.search(query))
+        return results
+
+    def get(self, value):
+        if not value:
+            return
+        if value.startswith('user:'):
+            value = value.replace('user:', '')
+            return self.users_source.get(value)
+        else:
+            value = value.replace('group:', '')
+            return self.groups_source.get(value)
+
+
+def _createGroupTerm(groupid, context=None, acl_users=None):
+    if acl_users is None:
+        acl_users = getToolByName(context, 'acl_users')
+    group = acl_users.getGroupById(groupid, None)
+    groupname = groupid
+    if group:
+        groupname = group.getProperty('name', None) or groupname
+    token = groupid.encode('unicode_escape') if isinstance(
+        groupid, unicode) else groupid
+    return SimpleTerm(groupid, token, groupname)
+
+
+class PrincipalsVocabulary(SlicableVocabulary):
+
+    def __init__(self, terms, context, *interfaces):
+        super(PrincipalsVocabulary, self).__init__(terms, *interfaces)
+        self._users = getToolByName(getSite(), 'acl_users')
+
+    def __contains__(self, value):
+        if value.startswith('user:'):
+            if self._users.getUserById(value.replace('user:', ''), None):
+                return True
+        else:
+            if self._users.getGroupById(value.replace('group:', ''), None):
+                return True
+        return False
+
+    def prefix_term(self, term, prefix):
+        term.token = prefix + term.token
+        term.value = prefix + term.value
+        return term
+
+    def getTerm(self, _id):
+        if _id.startswith('user:'):
+            return self.prefix_term(
+                _createUserTerm(_id.replace('user:', ''), acl_users=self._users),
+                'user:')
+        else:
+            return self.prefix_term(
+                _createGroupTerm(_id.replace('group:', ''), acl_users=self._users),
+                'group:')
+    getTermByToken = getTerm
+
+    def __iter__(self):
+        return self._terms
+
+
+@implementer(IVocabularyFactory)
+class PrincipalsFactory(object):
+    """Factory creating a UsersVocabulary
+    """
+
+    def prefix_values(self, terms, prefix):
+        result = []
+        for term in terms:
+            term.token = prefix + term.token
+            term.value = prefix + term.value
+            result.append(term)
+        return result
+
+    def filter(self, term):
+        """
+        ability to override to provide custom filtering
+        """
+        return True
+
+    def lazy_filtered_list(self, *lists):
+        for ll in lists:
+            for item in ll:
+                if self.filter(item):
+                    yield item
+
+    def __call__(self, context, query=''):
+        """
+        we're simply combining the user and groups results
+        """
+        users = UsersFactory()(context, query)
+        groups = GroupsVocabularyFactory(context, query)
+        return PrincipalsVocabulary(self.lazy_filtered_list(
+            self.prefix_values(users._terms, 'user:') +
+            self.prefix_values(groups._terms, 'group:')), context)
diff --git a/plone/app/vocabularies/security.py b/plone/app/vocabularies/security.py
index 345b28f..90464d5 100644
--- a/plone/app/vocabularies/security.py
+++ b/plone/app/vocabularies/security.py
@@ -118,12 +118,17 @@ class GroupsVocabulary(object):
       ('Editors', 'editors', 'editors')
     """
 
-    def __call__(self, context):
+    def __call__(self, context, query=''):
         items = []
         site = getSite()
         gtool = getToolByName(site, 'portal_groups', None)
         if gtool is not None:
-            groups = gtool.listGroups()
+            if query:
+                groups = []
+                for group_info in gtool.searchGroups(name=query):
+                    groups.append(gtool.getGroupById(group_info['groupid']))
+            else:
+                groups = gtool.listGroups()
             items = [(g.getGroupId(), g.getGroupTitleOrName()) for g in groups]
             items.sort()
             items = [SimpleTerm(i[0], i[0], i[1]) for i in items]
diff --git a/plone/app/vocabularies/tests/test_vocabularies.py b/plone/app/vocabularies/tests/test_vocabularies.py
index 782eefd..332f83c 100644
--- a/plone/app/vocabularies/tests/test_vocabularies.py
+++ b/plone/app/vocabularies/tests/test_vocabularies.py
@@ -85,5 +85,11 @@ def test_suite():
                 tearDown=vocabTearDown,
                 optionflags=optionflags
             ),
+            DocTestSuite(
+                'plone.app.vocabularies.principals',
+                setUp=vocabSetUp,
+                tearDown=vocabTearDown,
+                optionflags=optionflags
+            ),
         )
     )



------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.