Initial implementation. Works and tested, with some evil unicode hacks
git-svn-id: http://svn.plone.org/svn/plone/plone.principalsource/trunk@26102 3b4cdb85-528f-c531-b63d-5919d5b1aa08
This commit is contained in:
parent
fab43bc509
commit
509511eac9
|
@ -0,0 +1,21 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
This package provides a queriable sources (vocabularies) that return PAS
|
||||
users, groups or principals (both users and groups).
|
||||
|
||||
They are registered as named vocabularies, so you can do::
|
||||
|
||||
class IMyInterface(Interface):
|
||||
users = schema.Choice(title=u"Users",
|
||||
vocabulary="plone.principalsource.Users")
|
||||
|
||||
groups = schema.Choice(title=u"Groups",
|
||||
vocabulary="plone.principalsource.Groups")
|
||||
|
||||
principals = schema.Choice(title=u"Principals",
|
||||
vocabulary="plone.principalsource.Principals")
|
||||
|
||||
The underlying source (see source.py) implements the IQuerySource interface
|
||||
from z3c.formwidget.query. This means that it can be used for a query-select
|
||||
widget, including the one in plone.formwidget.autocomplete.
|
|
@ -0,0 +1,8 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
1.0b1 - Unreleased
|
||||
----------------
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
plone.principalsource Installation
|
||||
----------------------------------
|
||||
|
||||
To install plone.principalsource into the global Python environment (or a workingenv),
|
||||
using a traditional Zope 2 instance, you can do this:
|
||||
|
||||
* When you're reading this you have probably already run
|
||||
``easy_install plone.principalsource``. Find out how to install setuptools
|
||||
(and EasyInstall) here:
|
||||
http://peak.telecommunity.com/DevCenter/EasyInstall
|
||||
|
||||
* Create a file called ``plone.principalsource-configure.zcml`` in the
|
||||
``/path/to/instance/etc/package-includes`` directory. The file
|
||||
should only contain this::
|
||||
|
||||
<include package="plone.principalsource" />
|
||||
|
||||
|
||||
Alternatively, if you are using zc.buildout and the plone.recipe.zope2instance
|
||||
recipe to manage your project, you can do this:
|
||||
|
||||
* Add ``plone.principalsource`` to the list of eggs to install, e.g.:
|
||||
|
||||
[buildout]
|
||||
...
|
||||
eggs =
|
||||
...
|
||||
plone.principalsource
|
||||
|
||||
* Tell the plone.recipe.zope2instance recipe to install a ZCML slug:
|
||||
|
||||
[instance]
|
||||
recipe = plone.recipe.zope2instance
|
||||
...
|
||||
zcml =
|
||||
plone.principalsource
|
||||
|
||||
* Re-run buildout, e.g. with:
|
||||
|
||||
$ ./bin/buildout
|
||||
|
||||
You can skip the ZCML slug if you are going to explicitly include the package
|
||||
from another package's configure.zcml file.
|
|
@ -0,0 +1,222 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
|
@ -0,0 +1,16 @@
|
|||
plone.principalsource is copyright Martin Aspeli
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||||
MA 02111-1307 USA.
|
|
@ -0,0 +1,6 @@
|
|||
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
|
||||
try:
|
||||
__import__('pkg_resources').declare_namespace(__name__)
|
||||
except ImportError:
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
|
@ -0,0 +1,25 @@
|
|||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:five="http://namespaces.zope.org/five"
|
||||
i18n_domain="plone.principalsource">
|
||||
|
||||
<utility
|
||||
provides="zope.schema.interfaces.IVocabularyFactory"
|
||||
component=".source.PrincipalVocabularyFactory"
|
||||
name="plone.principalsource.Principals"
|
||||
/>
|
||||
|
||||
|
||||
<utility
|
||||
provides="zope.schema.interfaces.IVocabularyFactory"
|
||||
component=".source.UsersVocabularyFactory"
|
||||
name="plone.principalsource.Users"
|
||||
/>
|
||||
|
||||
<utility
|
||||
provides="zope.schema.interfaces.IVocabularyFactory"
|
||||
component=".source.GroupsVocabularyFactory"
|
||||
name="plone.principalsource.Groups"
|
||||
/>
|
||||
|
||||
</configure>
|
|
@ -0,0 +1,8 @@
|
|||
from zope.schema.interfaces import ITokenizedTerm
|
||||
from zope import schema
|
||||
|
||||
class IPrincipalTerm(ITokenizedTerm):
|
||||
"""A tokenised term that knows whether it refers to a user or a group.
|
||||
"""
|
||||
|
||||
type = schema.Choice(title=u"Type", values=('user', 'group',))
|
|
@ -0,0 +1,208 @@
|
|||
from zope.interface import implements
|
||||
from z3c.formwidget.query.interfaces import IQuerySource
|
||||
from zope.schema.interfaces import IContextSourceBinder
|
||||
from plone.principalsource.term import PrincipalTerm
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
|
||||
class PrincipalSource(object):
|
||||
"""A queriable source for users, groups or principals (users and/or
|
||||
groups).
|
||||
|
||||
User ids are tokens. Usernames are values. The user's fullname is the
|
||||
title, with a fallback on the name.
|
||||
|
||||
If user ids are not ASCII strings, they'll be encoded to utf-8.
|
||||
|
||||
If search_name is True, both user login names and full names will be
|
||||
searched. If False, only full names are searched.
|
||||
"""
|
||||
implements(IQuerySource)
|
||||
|
||||
def __init__(self, context, users=True, groups=True, search_name=True):
|
||||
self.context = context
|
||||
self.users = users
|
||||
self.groups = groups
|
||||
self.search_name = search_name
|
||||
|
||||
if not self.users and not self.groups:
|
||||
raise ValueError(u"You must enable either users or groups")
|
||||
|
||||
self.acl_users = getToolByName(context, 'acl_users')
|
||||
|
||||
def __contains__(self, value):
|
||||
try:
|
||||
self.getTerm(value)
|
||||
except LookupError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def __iter__(self):
|
||||
"""Return all users and/or groups as terms. Calling this on a site
|
||||
with many users is a very bad idea.
|
||||
"""
|
||||
|
||||
seen = set()
|
||||
for result in self._search():
|
||||
if result['id'] not in seen:
|
||||
seen.add(result['id'])
|
||||
|
||||
# XXX: We can sometimes get bogus group results from Plone's
|
||||
# mutable properties plugin, which doesn't distinguish between
|
||||
# users and groups
|
||||
if self.users and not self.groups:
|
||||
if self.acl_users.getUserById(result['id']) is None:
|
||||
continue
|
||||
|
||||
yield self._term_for_result(result)
|
||||
|
||||
def __len__(self):
|
||||
"""Return all users and/or groups as terms. Calling this on a site
|
||||
with many users is a very bad idea.
|
||||
"""
|
||||
count = 0
|
||||
for item in self:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def getTerm(self, value):
|
||||
|
||||
results = []
|
||||
|
||||
# There is no common attribute for user login and group id
|
||||
if self.users:
|
||||
results = self.acl_users.searchUsers(login=value, exact_match=True)
|
||||
if not results and self.groups:
|
||||
# XXX: stupidly, groups only support str ids. attempt to encode to utf-8
|
||||
# for lack of a better policy
|
||||
value = value.encode('utf-8')
|
||||
results = self.acl_users.searchGroups(id=value, exact_match=True)
|
||||
|
||||
if not results:
|
||||
raise LookupError(value)
|
||||
|
||||
# There may be more than one result, and we may have gotten a subset
|
||||
# match or similar even though we asked for an exact match. Let's
|
||||
# be thorough.
|
||||
for item in results:
|
||||
term = self._term_for_result(item)
|
||||
if term.value == value:
|
||||
return term
|
||||
|
||||
raise LookupError(value)
|
||||
|
||||
def getTermByToken(self, token):
|
||||
# XXX: This imples the method was called with the wrong value. Tokens
|
||||
# should alwas be strings. Be lenient anyway.
|
||||
if isinstance(token, unicode):
|
||||
token = token.encode('utf-8')
|
||||
results = self._search(id=token, exact_match=True)
|
||||
if not results:
|
||||
raise LookupError(token)
|
||||
return self._term_for_result(results[0])
|
||||
|
||||
def search(self, query_string):
|
||||
|
||||
seen = set()
|
||||
|
||||
if self.users:
|
||||
|
||||
# Search by user fullname
|
||||
for result in self.acl_users.searchUsers(fullname=query_string):
|
||||
seen.add(result['id'])
|
||||
yield self._term_for_result(result)
|
||||
|
||||
# Search by user name
|
||||
for result in self.acl_users.searchUsers(login=query_string):
|
||||
if result['id'] not in seen:
|
||||
seen.add(result['id'])
|
||||
yield self._term_for_result(result)
|
||||
|
||||
if self.groups:
|
||||
|
||||
# XXX: Stupidly, the groups plugin requires titles and ids to be
|
||||
# strings. Encode to utf-8 for lack of a better policy
|
||||
|
||||
group_query_string = query_string.encode('utf-8')
|
||||
|
||||
# Search by group title
|
||||
for result in self.acl_users.searchGroups(title=group_query_string):
|
||||
if result['id'] not in seen:
|
||||
seen.add(result['id'])
|
||||
yield self._term_for_result(result)
|
||||
|
||||
# Search by group name
|
||||
for result in self.acl_users.searchGroups(id=group_query_string):
|
||||
if result['id'] not in seen:
|
||||
seen.add(result['id'])
|
||||
yield self._term_for_result(result)
|
||||
|
||||
# Helper methods
|
||||
|
||||
def _term_for_result(self, result_dict):
|
||||
id = result_dict['id']
|
||||
|
||||
token = id
|
||||
if isinstance(token, unicode):
|
||||
token = token.encode('utf-8')
|
||||
|
||||
type = result_dict.get('principal_type', 'user')
|
||||
value = result_dict.get('login', result_dict.get('groupid')) or id
|
||||
title = result_dict.get('title') or value
|
||||
|
||||
# Attempt to get a title from the fullname if not set. Unfortunately,
|
||||
# source_users doesn't have fullname, and mutable_properties doesn't
|
||||
# match on login name or id when searching.
|
||||
|
||||
if title == value:
|
||||
if type == 'user':
|
||||
user = self.acl_users.getUserById(id)
|
||||
if user is not None:
|
||||
try:
|
||||
# XXX: user.getProperty() is PlonePAS specfic
|
||||
title = user.getProperty('fullname') or value
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
#
|
||||
# Seems the groups source is a bit more intelligent, so we don't
|
||||
# need this.
|
||||
#
|
||||
# elif type == 'group':
|
||||
# try:
|
||||
# group = self.acl_users.getGroupById(id)
|
||||
# if group is not None:
|
||||
# title = group.getProperty('title') or value
|
||||
# except AttributeError:
|
||||
# pass
|
||||
|
||||
return PrincipalTerm(value=value, type=type, token=token, title=title)
|
||||
|
||||
@property
|
||||
def _search(self):
|
||||
if self.users and self.groups:
|
||||
return self.acl_users.searchPrincipals
|
||||
elif self.users:
|
||||
return self.acl_users.searchUsers
|
||||
elif self.groups:
|
||||
return self.acl_users.searchGroups
|
||||
|
||||
# Source binders
|
||||
|
||||
class PrincipalSourceBinder(object):
|
||||
"""Bind the principal source with either users or groups
|
||||
"""
|
||||
implements(IContextSourceBinder)
|
||||
|
||||
def __init__(self, users=True, groups=True):
|
||||
self.users = users
|
||||
self.groups = groups
|
||||
|
||||
def __call__(self, context):
|
||||
return PrincipalSource(context, self.users, self.groups)
|
||||
|
||||
# Vocabulary factories (for named vocabularies)
|
||||
|
||||
PrincipalVocabularyFactory = PrincipalSourceBinder(users=True, groups=True)
|
||||
UsersVocabularyFactory = PrincipalSourceBinder(users=True, groups=False)
|
||||
GroupsVocabularyFactory = PrincipalSourceBinder(users=False, groups=True)
|
|
@ -0,0 +1,21 @@
|
|||
from zope.interface import implements, alsoProvides
|
||||
from zope.schema.interfaces import ITitledTokenizedTerm
|
||||
from plone.principalsource.interfaces import IPrincipalTerm
|
||||
|
||||
class PrincipalTerm(object):
|
||||
"""Simple tokenized term used by SimpleVocabulary."""
|
||||
|
||||
implements(IPrincipalTerm)
|
||||
|
||||
def __init__(self, value, type, token=None, title=None):
|
||||
"""Create a term. If token is omitted, str(value) is used.
|
||||
If title is provided, the term object provides ITitledTokenizedTerm.
|
||||
"""
|
||||
self.value = value
|
||||
self.type = type
|
||||
if token is None:
|
||||
token = value
|
||||
self.token = str(token)
|
||||
self.title = title
|
||||
if title is not None:
|
||||
alsoProvides(self, ITitledTokenizedTerm)
|
|
@ -0,0 +1,360 @@
|
|||
from unittest import defaultTestLoader
|
||||
|
||||
from Products.Five import zcml
|
||||
from Products.PloneTestCase import PloneTestCase as ptc
|
||||
from Products.PloneTestCase.layer import onsetup
|
||||
|
||||
from zope.component import getUtility
|
||||
from zope.schema.interfaces import IVocabularyFactory
|
||||
|
||||
from plone.principalsource.interfaces import IPrincipalTerm
|
||||
|
||||
@onsetup
|
||||
def setup_product():
|
||||
import collective.discussionplus
|
||||
zcml.load_config('configure.zcml', collective.discussionplus)
|
||||
|
||||
setup_product()
|
||||
ptc.setupPloneSite()
|
||||
|
||||
class TestSource(ptc.PloneTestCase):
|
||||
|
||||
def afterSetUp(self):
|
||||
|
||||
self.portal.portal_membership.addMember('dummy1', 'secret', ('Member',), (),
|
||||
properties={'fullname': 'Test One'})
|
||||
self.portal.portal_membership.addMember('dummy2', 'secret', ('Member',), (),
|
||||
properties={'fullname': 'Test Two'})
|
||||
self.portal.portal_membership.addMember('member1', 'secret', ('Member',), (),
|
||||
properties={'fullname': 'Another name'})
|
||||
|
||||
self.portal.portal_groups.addGroup('testgroup1', title='Test Group One')
|
||||
self.portal.portal_groups.addGroup('testgroup2', title='Test Group Two')
|
||||
self.portal.portal_groups.addGroup('alpha', title='Some name')
|
||||
|
||||
principals_factory = getUtility(IVocabularyFactory, name=u"plone.principalsource.Principals")
|
||||
self.principals = principals_factory(self.portal)
|
||||
|
||||
users_factory = getUtility(IVocabularyFactory, name=u"plone.principalsource.Users")
|
||||
self.users = users_factory(self.portal)
|
||||
|
||||
groups_factory = getUtility(IVocabularyFactory, name=u"plone.principalsource.Groups")
|
||||
self.groups = groups_factory(self.portal)
|
||||
|
||||
def test_contains(self):
|
||||
self.failUnless('dummy1' in self.users)
|
||||
self.failUnless('dummy2' in self.users)
|
||||
self.failUnless('member1' in self.users)
|
||||
self.failIf('testgroup1' in self.users)
|
||||
self.failIf('testgroup2' in self.users)
|
||||
self.failIf('alpha' in self.users)
|
||||
self.failIf('test' in self.users)
|
||||
|
||||
self.failUnless(u'dummy1' in self.users)
|
||||
self.failIf(u'test' in self.users)
|
||||
|
||||
self.failIf('dummy1' in self.groups)
|
||||
self.failIf('dummy2' in self.groups)
|
||||
self.failIf('member1' in self.groups)
|
||||
self.failUnless('testgroup1' in self.groups)
|
||||
self.failUnless('testgroup2' in self.groups)
|
||||
self.failUnless('alpha' in self.groups)
|
||||
self.failIf('test' in self.groups)
|
||||
|
||||
self.failUnless(u'testgroup1' in self.groups)
|
||||
self.failIf(u'test' in self.groups)
|
||||
|
||||
self.failUnless('dummy1' in self.principals)
|
||||
self.failUnless('dummy2' in self.principals)
|
||||
self.failUnless('member1' in self.principals)
|
||||
self.failUnless('testgroup1' in self.principals)
|
||||
self.failUnless('testgroup2' in self.principals)
|
||||
self.failUnless('alpha' in self.principals)
|
||||
self.failIf('test' in self.principals)
|
||||
|
||||
self.failUnless(u'dummy1' in self.principals)
|
||||
self.failUnless(u'testgroup1' in self.principals)
|
||||
self.failIf(u'test' in self.principals)
|
||||
|
||||
def test_iter(self):
|
||||
|
||||
users = [t.value for t in self.users]
|
||||
groups = [t.value for t in self.groups]
|
||||
principals = [t.value for t in self.principals]
|
||||
|
||||
self.failUnless('dummy1' in users)
|
||||
self.failUnless('dummy2' in users)
|
||||
self.failUnless('member1' in users)
|
||||
self.failIf('testgroup1' in users)
|
||||
self.failIf('testgroup2' in users)
|
||||
self.failIf('alpha' in users)
|
||||
self.failIf('dummy' in users)
|
||||
|
||||
self.failIf('dummy1' in groups)
|
||||
self.failIf('dummy2' in groups)
|
||||
self.failIf('member1' in groups)
|
||||
self.failUnless('testgroup1' in groups)
|
||||
self.failUnless('testgroup2' in groups)
|
||||
self.failUnless('alpha' in groups)
|
||||
self.failIf('test' in groups)
|
||||
|
||||
self.failUnless('dummy1' in principals)
|
||||
self.failUnless('dummy2' in principals)
|
||||
self.failUnless('member1' in principals)
|
||||
self.failUnless('testgroup1' in principals)
|
||||
self.failUnless('testgroup2' in principals)
|
||||
self.failUnless('alpha' in principals)
|
||||
self.failIf('dummy' in principals)
|
||||
self.failIf('test' in principals)
|
||||
|
||||
def test_len(self):
|
||||
# Three test users + the PTC test use
|
||||
self.assertEquals(4, len(self.users))
|
||||
|
||||
# Three test groups + Administrators, Reviewers and AuthenticatedUsers
|
||||
self.assertEquals(6, len(self.groups))
|
||||
|
||||
# Both of the above
|
||||
self.assertEquals(10, len(self.principals))
|
||||
|
||||
def test_get_term_by_value(self):
|
||||
self.assertEquals('Test One', self.users.getTerm('dummy1').title)
|
||||
self.assertEquals('dummy1', self.users.getTerm('dummy1').value)
|
||||
self.assertEquals('dummy1', self.users.getTerm('dummy1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.users.getTerm('dummy1')))
|
||||
self.assertEquals('user', self.users.getTerm('dummy1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.users.getTerm, 'testgroup1')
|
||||
self.assertRaises(LookupError, self.users.getTerm, 'bogus')
|
||||
|
||||
self.assertEquals('Test Group One', self.groups.getTerm('testgroup1').title)
|
||||
self.assertEquals('testgroup1', self.groups.getTerm('testgroup1').value)
|
||||
self.assertEquals('testgroup1', self.groups.getTerm('testgroup1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.groups.getTerm('testgroup1')))
|
||||
self.assertEquals('group', self.groups.getTerm('testgroup1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.groups.getTerm, 'dummy1')
|
||||
self.assertRaises(LookupError, self.groups.getTerm, 'bogus')
|
||||
|
||||
self.assertEquals('Test One', self.principals.getTerm('dummy1').title)
|
||||
self.assertEquals('dummy1', self.principals.getTerm('dummy1').value)
|
||||
self.assertEquals('dummy1', self.principals.getTerm('dummy1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.principals.getTerm('dummy1')))
|
||||
self.assertEquals('user', self.principals.getTerm('dummy1').type)
|
||||
|
||||
self.assertEquals('Test Group One', self.principals.getTerm('testgroup1').title)
|
||||
self.assertEquals('testgroup1', self.principals.getTerm('testgroup1').value)
|
||||
self.assertEquals('testgroup1', self.principals.getTerm('testgroup1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.principals.getTerm('testgroup1')))
|
||||
self.assertEquals('group', self.principals.getTerm('testgroup1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.principals.getTerm, 'bogus')
|
||||
|
||||
def test_get_term_by_value_unicode(self):
|
||||
self.assertEquals('Test One', self.users.getTerm(u'dummy1').title)
|
||||
self.assertEquals('dummy1', self.users.getTerm(u'dummy1').value)
|
||||
self.assertEquals('dummy1', self.users.getTerm(u'dummy1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.users.getTerm(u'dummy1')))
|
||||
self.assertEquals('user', self.users.getTerm(u'dummy1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.users.getTerm, u'testgroup1')
|
||||
self.assertRaises(LookupError, self.users.getTerm, u'bogus')
|
||||
|
||||
self.assertEquals('Test Group One', self.groups.getTerm(u'testgroup1').title)
|
||||
self.assertEquals('testgroup1', self.groups.getTerm(u'testgroup1').value)
|
||||
self.assertEquals('testgroup1', self.groups.getTerm(u'testgroup1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.groups.getTerm(u'testgroup1')))
|
||||
self.assertEquals('group', self.groups.getTerm(u'testgroup1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.groups.getTerm, u'dummy1')
|
||||
self.assertRaises(LookupError, self.groups.getTerm, u'bogus')
|
||||
|
||||
self.assertEquals('Test One', self.principals.getTerm(u'dummy1').title)
|
||||
self.assertEquals('dummy1', self.principals.getTerm(u'dummy1').value)
|
||||
self.assertEquals('dummy1', self.principals.getTerm(u'dummy1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.principals.getTerm(u'dummy1')))
|
||||
self.assertEquals('user', self.principals.getTerm(u'dummy1').type)
|
||||
|
||||
self.assertEquals('Test Group One', self.principals.getTerm(u'testgroup1').title)
|
||||
self.assertEquals('testgroup1', self.principals.getTerm(u'testgroup1').value)
|
||||
self.assertEquals('testgroup1', self.principals.getTerm(u'testgroup1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.principals.getTerm(u'testgroup1')))
|
||||
self.assertEquals('group', self.principals.getTerm(u'testgroup1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.principals.getTerm, u'bogus')
|
||||
|
||||
def test_get_term_by_token(self):
|
||||
|
||||
self.assertEquals('Test One', self.users.getTermByToken('dummy1').title)
|
||||
self.assertEquals('dummy1', self.users.getTermByToken('dummy1').value)
|
||||
self.assertEquals('dummy1', self.users.getTermByToken('dummy1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.users.getTermByToken('dummy1')))
|
||||
self.assertEquals('user', self.users.getTermByToken('dummy1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.users.getTermByToken, 'testgroup1')
|
||||
self.assertRaises(LookupError, self.users.getTermByToken, 'bogus')
|
||||
|
||||
self.assertEquals('Test Group One', self.groups.getTermByToken('testgroup1').title)
|
||||
self.assertEquals('testgroup1', self.groups.getTermByToken('testgroup1').value)
|
||||
self.assertEquals('testgroup1', self.groups.getTermByToken('testgroup1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.groups.getTermByToken('testgroup1')))
|
||||
self.assertEquals('group', self.groups.getTermByToken('testgroup1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.groups.getTermByToken, 'dummy1')
|
||||
self.assertRaises(LookupError, self.groups.getTermByToken, 'bogus')
|
||||
|
||||
self.assertEquals('Test One', self.principals.getTermByToken('dummy1').title)
|
||||
self.assertEquals('dummy1', self.principals.getTermByToken('dummy1').value)
|
||||
self.assertEquals('dummy1', self.principals.getTermByToken('dummy1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.principals.getTermByToken('dummy1')))
|
||||
self.assertEquals('user', self.principals.getTermByToken('dummy1').type)
|
||||
|
||||
self.assertEquals('Test Group One', self.principals.getTermByToken('testgroup1').title)
|
||||
self.assertEquals('testgroup1', self.principals.getTermByToken('testgroup1').value)
|
||||
self.assertEquals('testgroup1', self.principals.getTermByToken('testgroup1').token)
|
||||
|
||||
self.failUnless(IPrincipalTerm.providedBy(self.principals.getTermByToken('testgroup1')))
|
||||
self.assertEquals('group', self.principals.getTermByToken('testgroup1').type)
|
||||
|
||||
self.assertRaises(LookupError, self.principals.getTermByToken, 'bogus')
|
||||
|
||||
# Search users
|
||||
|
||||
def test_search_users_login(self):
|
||||
|
||||
# Search finds multiple users
|
||||
results = list(self.users.search('dummy'))
|
||||
self.assertEquals(2, len(results))
|
||||
|
||||
def test_search_users_login_unicode(self):
|
||||
|
||||
# Search finds multiple users
|
||||
results = list(self.users.search(u'dummy'))
|
||||
self.assertEquals(2, len(results))
|
||||
|
||||
def test_search_users_nogroups(self):
|
||||
|
||||
# User search does not include groups
|
||||
results = list(self.users.search('testgroup'))
|
||||
self.assertEquals(0, len(results))
|
||||
|
||||
def test_search_users_caseinsensitive(self):
|
||||
|
||||
# Search is case insensitive
|
||||
results = list(self.users.search('MEMBER'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
def test_search_users_nomatch(self):
|
||||
|
||||
# Search returns nothing if nothing is matched
|
||||
results = list(self.users.search('bogus'))
|
||||
self.assertEquals(0, len(results))
|
||||
|
||||
def test_search_users_fullname_or_username(self):
|
||||
|
||||
# Search can search full name or username
|
||||
results = list(self.users.search('member'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
results = list(self.users.search('Another name'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
# Search groups
|
||||
|
||||
def test_search_groups_id(self):
|
||||
|
||||
# Search finds multiple users
|
||||
results = list(self.groups.search('testgroup'))
|
||||
self.assertEquals(2, len(results))
|
||||
|
||||
def test_search_groups_id_unicode(self):
|
||||
|
||||
# Search finds multiple users
|
||||
results = list(self.groups.search(u'testgroup'))
|
||||
self.assertEquals(2, len(results))
|
||||
|
||||
def test_search_groups_name_unicode(self):
|
||||
|
||||
# Search finds multiple users
|
||||
results = list(self.groups.search(u'Test Group'))
|
||||
self.assertEquals(2, len(results))
|
||||
|
||||
def test_search_groups_nousers(self):
|
||||
|
||||
# User search does not include groups
|
||||
results = list(self.groups.search('dummy'))
|
||||
self.assertEquals(0, len(results))
|
||||
|
||||
def test_search_groups_caseinsensitive(self):
|
||||
|
||||
# Search is case insensitive
|
||||
results = list(self.groups.search('ALPHA'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
def test_search_groups_nomatch(self):
|
||||
|
||||
# Search returns nothing if nothing is matched
|
||||
results = list(self.groups.search('bogus'))
|
||||
self.assertEquals(0, len(results))
|
||||
|
||||
def test_search_groups_title_or_name(self):
|
||||
# Search can search full name or username
|
||||
results = list(self.groups.search('alpha'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
results = list(self.groups.search('Some name'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
# Search principals
|
||||
|
||||
def test_search_principals_login_or_id(self):
|
||||
|
||||
# Search finds multiple users and groups
|
||||
results = list(self.principals.search('test'))
|
||||
# includes test_user_1_, two test users and two test groups
|
||||
self.assertEquals(5, len(results))
|
||||
|
||||
def test_search_principals_caseinsensitive(self):
|
||||
|
||||
# Search is case insensitive
|
||||
results = list(self.principals.search('ALPHA'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
results = list(self.principals.search('MEMBER'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
def test_search_principals_nomatch(self):
|
||||
|
||||
# Search returns nothing if nothing is matched
|
||||
results = list(self.principals.search('bogus'))
|
||||
self.assertEquals(0, len(results))
|
||||
|
||||
def test_search_principals_title_or_name(self):
|
||||
|
||||
# Search can search full name or username
|
||||
results = list(self.principals.search('member'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
results = list(self.principals.search('Another'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
# or group id or group name
|
||||
results = list(self.principals.search('alpha'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
results = list(self.principals.search('Some name'))
|
||||
self.assertEquals(1, len(results))
|
||||
|
||||
def test_suite():
|
||||
return defaultTestLoader.loadTestsFromName(__name__)
|
|
@ -0,0 +1,7 @@
|
|||
[zopeskel]
|
||||
template = plone
|
||||
|
||||
[egg_info]
|
||||
tag_build = dev
|
||||
tag_svn_revision = true
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
from setuptools import setup, find_packages
|
||||
import os
|
||||
|
||||
version = '1.0b1'
|
||||
|
||||
setup(name='plone.principalsource',
|
||||
version=version,
|
||||
description="A queriable source for accessing users and/or groups",
|
||||
long_description=open("README.txt").read() + "\n" +
|
||||
open(os.path.join("docs", "HISTORY.txt")).read(),
|
||||
# Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
"Framework :: Plone",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
],
|
||||
keywords='plone user group principal source',
|
||||
author='Martin Aspeli',
|
||||
author_email='optilude@gmail.com',
|
||||
url='http://pypi.python.org/pypi/plone.principalsource',
|
||||
license='GPL',
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
namespace_packages=['plone'],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
'setuptools',
|
||||
'z3c.formwidget.query',
|
||||
'zope.schema',
|
||||
'zope.component',
|
||||
'zope.interface',
|
||||
'Products.CMFCore',
|
||||
'Products.PluggableAuthService',
|
||||
],
|
||||
entry_points="""
|
||||
""",
|
||||
)
|
Reference in New Issue