launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02093
[Merge] lp:~sinzui/launchpad/closed-teams-0 into lp:launchpad
Curtis Hovey has proposed merging lp:~sinzui/launchpad/closed-teams-0 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#681035 Need a closed team and user vocabulary and picker
https://bugs.launchpad.net/bugs/681035
Need a closed team and user vocabulary and picker
Launchpad bug: https://bugs.launchpad.net/bugs/681035
Pre-implementation: jcsackett, flacoste, gary_poster
Test command: ./bin/test -vv \
-t test_popup -t test_team -t test_person_vocabularies
There are several cases where Launchpad must prevent users from selecting a
team with an open subscription policy because it compromises security or
discloses information. The person picker and forms need a vocabulary that is
composed on closed teams (restricted and moderated), and users. The
picker/vocabulary is needed is needed in:
1. closed team owner role
2. closed team +addmember
3. moderated team +proposemember
------------------------------------------------------------------------------
RULES
* Update the ValidTeamMemberVocabulary and ValidTeamOwnerVocabulary
to exclude open teams when the context team is closed.
* Update IHugeVocabulary, <intermediate classes>, VocabularyPickerWidget
to use the vocabs step_title so that users understand what can and
cannot be searched for. This issue was discovered in user testing.
* Update all IHugeVocabularies to have a step_title
* Update the add member ajax feature to also use the vocabulary's
step_title
QA
Using a moderated or restricted team to test.
* Verify that open teams to not appear in the person picker to add
a new member from the team's front page.
* Verify that open teams to not appear in the person picker to add
a new member from the teams +addmember page.
* Verify that open teams to not appear in the person picker to change
the team owner
LINT
lib/canonical/launchpad/vocabularies/dbobjects.py
lib/canonical/launchpad/webapp/vocabulary.py
lib/canonical/widgets/popup.py
lib/lp/answers/vocabulary.py
lib/lp/app/widgets/
lib/lp/app/widgets/__init__.py
lib/lp/app/widgets/tests/
lib/lp/app/widgets/tests/__init__.py
lib/lp/app/widgets/tests/test_popup.py
lib/lp/blueprints/vocabularies/specificationdependency.py
lib/lp/code/vocabularies/branch.py
lib/lp/registry/vocabularies.py
lib/lp/registry/browser/configure.zcml
lib/lp/registry/browser/person.py
lib/lp/registry/browser/tests/test_team.py
lib/lp/registry/javascript/team.js
lib/lp/registry/templates/team-portlet-membership.pt
lib/lp/registry/tests/test_person_vocabularies.py
IMPLEMENTATION
Updated ValidTeamMemberVocabulary and ValidTeamOwnerVocabulary to exclude
open teams when the context team is closed.
lib/lp/registry/vocabularies.py
lib/lp/registry/tests/test_person_vocabularies.py
Added step_title to the vocabulary. The value is the same that appears in
the UI now.
lib/canonical/launchpad/webapp/vocabulary.py
lib/canonical/launchpad/vocabularies/dbobjects.py
lib/lp/answers/vocabulary.py
lib/lp/blueprints/vocabularies/specificationdependency.py
lib/lp/code/vocabularies/branch.py
lib/lp/registry/vocabularies.py
Updated VocabularyPickerWidget to use the vocab's step_title. There are
no direct tests of this widget. Moving the whole module would make the diff
hard to read so I create a test where the module should be moved.
lib/canonical/widgets/popup.py
lib/lp/app/widgets/
lib/lp/app/widgets/__init__.py
lib/lp/app/widgets/tests/
lib/lp/app/widgets/tests/__init__.py
lib/lp/app/widgets/tests/test_popup.py
Updated the TeamIndexView and team membership portlet to use the step_title
from the vocabulary.
lib/lp/registry/browser/configure.zcml
lib/lp/registry/browser/person.py
lib/lp/registry/browser/tests/test_team.py
lib/lp/registry/javascript/team.js
lib/lp/registry/templates/team-portlet-membership.pt
--
https://code.launchpad.net/~sinzui/launchpad/closed-teams-0/+merge/42500
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/closed-teams-0 into lp:launchpad.
=== modified file 'lib/canonical/launchpad/vocabularies/dbobjects.py'
--- lib/canonical/launchpad/vocabularies/dbobjects.py 2010-11-09 08:38:23 +0000
+++ lib/canonical/launchpad/vocabularies/dbobjects.py 2010-12-02 16:11:34 +0000
@@ -141,6 +141,7 @@
class BugTrackerVocabulary(SQLObjectVocabularyBase):
"""All web and email based external bug trackers."""
displayname = 'Select a bug tracker'
+ step_title = 'Search'
implements(IHugeVocabulary)
_table = BugTracker
_filter = True
@@ -401,6 +402,7 @@
Person.q.id == Archive.q.ownerID,
Archive.q.purpose == ArchivePurpose.PPA)
displayname = 'Select a PPA'
+ step_title = 'Search'
def toTerm(self, archive):
"""See `IVocabulary`."""
=== modified file 'lib/canonical/launchpad/webapp/vocabulary.py'
--- lib/canonical/launchpad/webapp/vocabulary.py 2010-11-09 08:38:23 +0000
+++ lib/canonical/launchpad/webapp/vocabulary.py 2010-12-02 16:11:34 +0000
@@ -70,7 +70,10 @@
"""
displayname = Attribute(
- 'A name for this vocabulary, to be displayed in the popup window.')
+ 'A name for this vocabulary, to be displayed in the picker window.')
+
+ step_title = Attribute(
+ 'The search step title in the picker window.')
def searchForTerms(query=None):
"""Return a `CountableIterator` of `SimpleTerm`s that match the query.
@@ -365,6 +368,7 @@
implements(IHugeVocabulary)
_orderBy = 'name'
displayname = None
+ step_title = 'Search'
# The iterator class will be used to wrap the results; its iteration
# methods should return SimpleTerms, as the reference implementation
# CountableIterator does.
=== modified file 'lib/canonical/widgets/popup.py'
--- lib/canonical/widgets/popup.py 2010-08-24 10:45:57 +0000
+++ lib/canonical/widgets/popup.py 2010-12-02 16:11:34 +0000
@@ -37,7 +37,7 @@
style = ''
cssClass = ''
- step_title = 'Search'
+ step_title = None
# Defaults to self.vocabulary.displayname.
header = None
@@ -79,19 +79,20 @@
def inputField(self):
d = {
- 'formToken' : cgi.escape(self.formToken, quote=True),
+ 'formToken': cgi.escape(self.formToken, quote=True),
'name': self.name,
'displayWidth': self.displayWidth,
'displayMaxWidth': self.displayMaxWidth,
'onKeyPress': self.onKeyPress,
'style': self.style,
- 'cssClass': self.cssClass
- }
+ 'cssClass': self.cssClass,
+ }
return """<input type="text" value="%(formToken)s" id="%(name)s"
name="%(name)s" size="%(displayWidth)s"
maxlength="%(displayMaxWidth)s"
onKeyPress="%(onKeyPress)s" style="%(style)s"
class="%(cssClass)s" />""" % d
+
@property
def suffix(self):
return self.name.replace('.', '-')
@@ -126,23 +127,31 @@
% (choice.context, choice.__name__))
return choice.vocabularyName
- def chooseLink(self):
- js_file = os.path.join(os.path.dirname(__file__),
- 'templates/vocabulary-picker.js.template')
- js_template = open(js_file).read()
-
+ def js_template_args(self):
+ """return a dict of args to configure the picker javascript."""
if self.header is None:
header = self.vocabulary.displayname
else:
header = self.header
- args = dict(
+ if self.step_title is None:
+ step_title = self.vocabulary.step_title
+ else:
+ step_title = self.step_title
+
+ return dict(
vocabulary=self.vocabulary_name,
header=header,
- step_title=self.step_title,
+ step_title=step_title,
show_widget_id=self.show_widget_id,
input_id=self.name,
extra_no_results_message=self.extra_no_results_message)
+
+ def chooseLink(self):
+ js_file = os.path.join(os.path.dirname(__file__),
+ 'templates/vocabulary-picker.js.template')
+ js_template = open(js_file).read()
+ args = self.js_template_args()
js = js_template % simplejson.dumps(args)
# If the YUI widget or javascript is not supported in the browser,
# it will degrade to being this "Find..." link instead of the
@@ -154,8 +163,7 @@
css = ''
return ('<span class="%s">(<a id="%s" href="/people/">'
'Find…</a>)</span>'
- '\n<script>\n%s\n</script>'
- ) % (css, self.show_widget_id, js)
+ '\n<script>\n%s\n</script>') % (css, self.show_widget_id, js)
@property
def nonajax_uri(self):
=== modified file 'lib/lp/answers/vocabulary.py'
--- lib/lp/answers/vocabulary.py 2010-08-20 20:31:18 +0000
+++ lib/lp/answers/vocabulary.py 2010-12-02 16:11:34 +0000
@@ -24,6 +24,7 @@
implements(IHugeVocabulary)
displayname = 'Select a FAQ'
+ step_title = 'Search'
def __init__(self, context):
"""Create a new vocabulary for the context.
@@ -72,5 +73,3 @@
"""See `IHugeVocabulary`."""
results = self.context.findSimilarFAQs(query)
return CountableIterator(results.count(), results, self.toTerm)
-
-
=== added directory 'lib/lp/app/widgets'
=== added file 'lib/lp/app/widgets/__init__.py'
=== added directory 'lib/lp/app/widgets/tests'
=== added file 'lib/lp/app/widgets/tests/__init__.py'
=== added file 'lib/lp/app/widgets/tests/test_popup.py'
--- lib/lp/app/widgets/tests/test_popup.py 1970-01-01 00:00:00 +0000
+++ lib/lp/app/widgets/tests/test_popup.py 2010-12-02 16:11:34 +0000
@@ -0,0 +1,43 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+from zope.schema.vocabulary import getVocabularyRegistry
+
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.testing.layers import DatabaseFunctionalLayer
+from canonical.widgets.popup import VocabularyPickerWidget
+from lp.registry.interfaces.person import ITeam
+from lp.testing import TestCaseWithFactory
+
+
+class TestVocabularyPickerWidget(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestVocabularyPickerWidget, self).setUp()
+ context = self.factory.makeTeam()
+ field = ITeam['teamowner']
+ self.bound_field = field.bind(context)
+ vocabulary_registry = getVocabularyRegistry()
+ self.vocabulary = vocabulary_registry.get(context, 'ValidTeamOwner')
+ self.request = LaunchpadTestRequest()
+
+ def test_js_template_args(self):
+ picker_widget = VocabularyPickerWidget(
+ self.bound_field, self.vocabulary, self.request)
+ js_template_args = picker_widget.js_template_args()
+ self.assertEqual(
+ 'ValidTeamOwner', js_template_args['vocabulary'])
+ self.assertEqual(
+ self.vocabulary.displayname, js_template_args['header'])
+ self.assertEqual(
+ self.vocabulary.step_title, js_template_args['step_title'])
+ self.assertEqual(
+ 'show-widget-field-teamowner', js_template_args['show_widget_id'])
+ self.assertEqual(
+ 'field.teamowner', js_template_args['input_id'])
+ self.assertEqual(
+ None, js_template_args['extra_no_results_message'])
=== modified file 'lib/lp/blueprints/vocabularies/specificationdependency.py'
--- lib/lp/blueprints/vocabularies/specificationdependency.py 2010-11-01 03:36:04 +0000
+++ lib/lp/blueprints/vocabularies/specificationdependency.py 2010-12-02 16:11:34 +0000
@@ -58,6 +58,7 @@
_table = Specification
_orderBy = 'name'
displayname = 'Select a blueprint'
+ step_title = 'Search'
def _is_valid_candidate(self, spec, check_target=False):
"""Is `spec` a valid candidate spec for self.context?
=== modified file 'lib/lp/code/vocabularies/branch.py'
--- lib/lp/code/vocabularies/branch.py 2010-09-20 01:06:36 +0000
+++ lib/lp/code/vocabularies/branch.py 2010-12-02 16:11:34 +0000
@@ -44,6 +44,7 @@
_table = Branch
_orderBy = ['name', 'id']
displayname = 'Select a branch'
+ step_title = 'Search'
def toTerm(self, branch):
"""The display should include the URL if there is one."""
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2010-11-30 20:34:31 +0000
+++ lib/lp/registry/browser/configure.zcml 2010-12-02 16:11:34 +0000
@@ -1127,7 +1127,7 @@
template="../templates/team-portlet-map.pt"/>
<browser:page
for="lp.registry.interfaces.person.ITeam"
- class="lp.registry.browser.person.PersonIndexView"
+ class="lp.registry.browser.person.TeamIndexView"
permission="zope.Public"
name="+portlet-membership"
template="../templates/team-portlet-membership.pt"/>
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2010-11-30 12:53:20 +0000
+++ lib/lp/registry/browser/person.py 2010-12-02 16:11:34 +0000
@@ -3567,6 +3567,12 @@
return 'portlet'
return 'portlet private'
+ @property
+ def add_member_step_title(self):
+ vocabulary_registry = getVocabularyRegistry()
+ vocabulary = vocabulary_registry.get(self.context, 'ValidTeamMember')
+ return vocabulary.step_title
+
class PersonCodeOfConductEditView(LaunchpadView):
"""View for the ~person/+codesofconduct pages."""
=== modified file 'lib/lp/registry/browser/tests/test_team.py'
--- lib/lp/registry/browser/tests/test_team.py 2010-11-23 04:42:24 +0000
+++ lib/lp/registry/browser/tests/test_team.py 2010-12-02 16:11:34 +0000
@@ -135,3 +135,17 @@
self.assertEqual(
"You can't add a team that doesn't have any active members.",
view.errors[0])
+
+
+class TestTeamIndexView(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestTeamIndexView, self).setUp()
+ self.team = self.factory.makeTeam(name='test-team')
+ login_person(self.team.teamowner)
+
+ def test_add_member_step_title(self):
+ view = create_initialized_view(self.team, '+index')
+ self.assertEqual('Search', view.add_member_step_title)
=== modified file 'lib/lp/registry/javascript/team.js'
--- lib/lp/registry/javascript/team.js 2010-10-21 22:06:57 +0000
+++ lib/lp/registry/javascript/team.js 2010-12-02 16:11:34 +0000
@@ -14,14 +14,14 @@
*
* @method setup_add_member_handler
*/
-module.setup_add_member_handler = function() {
+module.setup_add_member_handler = function(step_title) {
if (Y.UA.ie) {
return;
}
var config = {
header: 'Add a member',
- step_title: 'Search',
+ step_title: step_title,
picker_activator: '.menu-link-add_member'
};
=== modified file 'lib/lp/registry/templates/team-portlet-membership.pt'
--- lib/lp/registry/templates/team-portlet-membership.pt 2010-11-10 15:33:47 +0000
+++ lib/lp/registry/templates/team-portlet-membership.pt 2010-12-02 16:11:34 +0000
@@ -101,14 +101,16 @@
<table style="margin: 0px 0px .5em 0px;">
<tr>
<td style="padding: 0px 1em 1em 0px;"
- tal:define="link context/menu:overview/add_member"
+ tal:define="link context/menu:overview/add_member;
+ step_title view/add_member_step_title;"
tal:condition="link/enabled">
<script type="text/javascript"
tal:content="string:
LPS.use('lp.registry.team', function(Y) {
Y.on('load',
function(e) {
- Y.lp.registry.team.setup_add_member_handler();
+ Y.lp.registry.team.setup_add_member_handler(
+ '${step_title}');
},
window);
});
=== modified file 'lib/lp/registry/tests/test_person_vocabularies.py'
--- lib/lp/registry/tests/test_person_vocabularies.py 2010-10-20 15:45:40 +0000
+++ lib/lp/registry/tests/test_person_vocabularies.py 2010-12-02 16:11:34 +0000
@@ -11,36 +11,117 @@
from canonical.launchpad.ftests import login_person
from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.registry.interfaces.person import PersonVisibility
+from lp.registry.interfaces.person import (
+ PersonVisibility,
+ TeamSubscriptionPolicy,
+ )
from lp.testing import TestCaseWithFactory
-class TestValidTeamMemberVocabulary(TestCaseWithFactory):
+class VocabularyTestBase:
+
+ vocabulary_name = None
+
+ def setUp(self):
+ super(VocabularyTestBase, self).setUp()
+ self.vocabulary_registry = getVocabularyRegistry()
+
+ def getVocabulary(self, context):
+ return self.vocabulary_registry.get(context, self.vocabulary_name)
+
+ def searchVocabulary(self, context, text):
+ Store.of(context).flush()
+ vocabulary = self.getVocabulary(context)
+ return removeSecurityProxy(vocabulary)._doSearch(text)
+
+ def test_open_team_cannot_be_a_member_or_a_closed_team(self):
+ context_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
+ open_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.OPEN)
+ moderated_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
+ restricted_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
+ user = self.factory.makePerson()
+ all_possible_members = self.searchVocabulary(context_team, '')
+ self.assertNotIn(open_team, all_possible_members)
+ self.assertIn(moderated_team, all_possible_members)
+ self.assertIn(restricted_team, all_possible_members)
+ self.assertIn(user, all_possible_members)
+
+ def test_open_team_can_be_a_member_or_an_open_team(self):
+ context_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.OPEN)
+ open_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.OPEN)
+ moderated_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
+ restricted_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
+ user = self.factory.makePerson()
+ all_possible_members = self.searchVocabulary(context_team, '')
+ self.assertIn(open_team, all_possible_members)
+ self.assertIn(moderated_team, all_possible_members)
+ self.assertIn(restricted_team, all_possible_members)
+ self.assertIn(user, all_possible_members)
+
+ def test_vocabulary_displayname(self):
+ context_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.OPEN)
+ vocabulary = self.getVocabulary(context_team)
+ self.assertEqual(
+ 'Select a Team or Person', vocabulary.displayname)
+
+ def test_open_team_vocabulary_step_title(self):
+ context_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.OPEN)
+ vocabulary = self.getVocabulary(context_team)
+ self.assertEqual('Search', vocabulary.step_title)
+
+ def test_closed_team_vocabulary_step_title(self):
+ context_team = self.factory.makeTeam(
+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
+ vocabulary = self.getVocabulary(context_team)
+ self.assertEqual(
+ 'Search for a restricted or moderated team or person',
+ vocabulary.step_title)
+
+
+class TestValidTeamMemberVocabulary(VocabularyTestBase, TestCaseWithFactory):
"""Test that the ValidTeamMemberVocabulary behaves as expected."""
layer = DatabaseFunctionalLayer
-
- def searchVocabulary(self, team, text):
- vocabulary_registry = getVocabularyRegistry()
- naked_vocabulary = removeSecurityProxy(
- vocabulary_registry.get(team, 'ValidTeamMember'))
- return naked_vocabulary._doSearch(text)
+ vocabulary_name = 'ValidTeamMember'
def test_public_team_cannot_be_a_member_of_itself(self):
# A public team should be filtered by the vocab.extra_clause
# when provided a search term.
- team_owner = self.factory.makePerson()
- login_person(team_owner)
- team = self.factory.makeTeam(owner=team_owner)
- Store.of(team).flush()
- self.assertFalse(team in self.searchVocabulary(team, team.name))
+ team = self.factory.makeTeam()
+ self.assertNotIn(team, self.searchVocabulary(team, team.name))
def test_private_team_cannot_be_a_member_of_itself(self):
# A private team should be filtered by the vocab.extra_clause
# when provided a search term.
- team_owner = self.factory.makePerson()
- login_person(team_owner)
team = self.factory.makeTeam(
- owner=team_owner, visibility=PersonVisibility.PRIVATE)
- Store.of(team).flush()
- self.assertFalse(team in self.searchVocabulary(team, team.name))
+ visibility=PersonVisibility.PRIVATE)
+ login_person(team.teamowner)
+ self.assertNotIn(team, self.searchVocabulary(team, team.name))
+
+
+class TestValidTeamOwnerVocabulary(VocabularyTestBase, TestCaseWithFactory):
+ """Test that the ValidTeamOwnerVocabulary behaves as expected."""
+
+ layer = DatabaseFunctionalLayer
+ vocabulary_name = 'ValidTeamOwner'
+
+ def test_team_cannot_own_itself(self):
+ context_team = self.factory.makeTeam()
+ results = self.searchVocabulary(context_team, context_team.name)
+ self.assertNotIn(context_team, results)
+
+ def test_team_cannot_own_its_owner(self):
+ context_team = self.factory.makeTeam()
+ owned_team = self.factory.makeTeam(owner=context_team)
+ results = self.searchVocabulary(context_team, owned_team.name)
+ self.assertNotIn(owned_team, results)
=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py 2010-11-09 08:38:23 +0000
+++ lib/lp/registry/vocabularies.py 2010-12-02 16:11:34 +0000
@@ -47,6 +47,7 @@
'SourcePackageNameVocabulary',
'UserTeamsParticipationVocabulary',
'UserTeamsParticipationPlusSelfVocabulary',
+ 'ValidPersonOrClosedTeamVocabulary',
'ValidPersonOrTeamVocabulary',
'ValidPersonVocabulary',
'ValidTeamMemberVocabulary',
@@ -143,6 +144,7 @@
IPersonSet,
ITeam,
PersonVisibility,
+ TeamSubscriptionPolicy,
)
from lp.registry.interfaces.pillar import IPillarName
from lp.registry.interfaces.product import (
@@ -219,6 +221,7 @@
class ProductVocabulary(SQLObjectVocabularyBase):
"""All `IProduct` objects vocabulary."""
implements(IHugeVocabulary)
+ step_title = 'Search'
_table = Product
_orderBy = 'displayname'
@@ -271,6 +274,7 @@
_table = ProjectGroup
_orderBy = 'displayname'
displayname = 'Select a project group'
+ step_title = 'Search'
def __contains__(self, obj):
where = "active='t' and id=%d"
@@ -361,6 +365,7 @@
_orderBy = ['displayname']
displayname = 'Select a Person or Team'
+ step_title = 'Search'
def __contains__(self, obj):
return obj in self._select()
@@ -391,6 +396,7 @@
_orderBy = ['displayname']
displayname = 'Select a Person to Merge'
+ step_title = 'Search'
must_have_email = True
def __contains__(self, obj):
@@ -439,7 +445,7 @@
implements(IHugeVocabulary)
displayname = 'Select a Person or Team'
-
+ step_title = 'Search'
# This is what subclasses must change if they want any extra filtering of
# results.
extra_clause = True
@@ -722,11 +728,31 @@
cache_table_name = 'ValidPersonCache'
-class ValidTeamMemberVocabulary(ValidPersonOrTeamVocabulary):
+class TeamVocabularyMixin:
+ """Common methods for team vocabularies."""
+
+ displayname = 'Select a Team or Person'
+
+ @property
+ def is_closed_team(self):
+ return self.team.subscriptionpolicy != TeamSubscriptionPolicy.OPEN
+
+ @property
+ def step_title(self):
+ """See `IHugeVocabulary`."""
+ if self.is_closed_team:
+ return 'Search for a restricted or moderated team or person'
+ else:
+ return 'Search'
+
+
+class ValidTeamMemberVocabulary(TeamVocabularyMixin,
+ ValidPersonOrTeamVocabulary):
"""The set of valid members of a given team.
With the exception of all teams that have this team as a member and the
- team itself, all valid persons and teams are valid members.
+ team itself, all valid persons and teams are valid members. Restricted
+ and moderated teams cannot have open teams as members.
"""
def __init__(self, context):
@@ -740,19 +766,29 @@
"Got %s" % str(context))
ValidPersonOrTeamVocabulary.__init__(self, context)
- self.extra_clause = """
+
+ @property
+ def extra_clause(self):
+ clause = SQL("""
Person.id NOT IN (
SELECT team FROM TeamParticipation
WHERE person = %d
)
- """ % self.team.id
-
-
-class ValidTeamOwnerVocabulary(ValidPersonOrTeamVocabulary):
+ """ % self.team.id)
+ if self.is_closed_team:
+ clause = And(
+ clause,
+ Person.subscriptionpolicy != TeamSubscriptionPolicy.OPEN)
+ return clause
+
+
+class ValidTeamOwnerVocabulary(TeamVocabularyMixin,
+ ValidPersonOrTeamVocabulary):
"""The set of Persons/Teams that can be owner of a team.
With the exception of the team itself and all teams owned by that team,
- all valid persons and teams are valid owners for the team.
+ all valid persons and teams are valid owners for the team. Restricted
+ and moderated teams cannot have open teams as members.
"""
def __init__(self, context):
@@ -760,9 +796,7 @@
raise AssertionError('ValidTeamOwnerVocabulary needs a context.')
if IPerson.providedBy(context):
- self.extra_clause = """
- (person.teamowner != %d OR person.teamowner IS NULL) AND
- person.id != %d""" % (context.id, context.id)
+ self.team = context
elif IPersonSet.providedBy(context):
# The context is an IPersonSet, which means we're creating a new
# team and thus we don't need any extra_clause --any valid person
@@ -774,6 +808,17 @@
"or IPersonSet.")
ValidPersonOrTeamVocabulary.__init__(self, context)
+ @property
+ def extra_clause(self):
+ clause = SQL("""
+ (person.teamowner != %d OR person.teamowner IS NULL) AND
+ person.id != %d""" % (self.team.id, self.team.id))
+ if self.is_closed_team:
+ clause = And(
+ clause,
+ Person.subscriptionpolicy != TeamSubscriptionPolicy.OPEN)
+ return clause
+
class AllUserTeamsParticipationVocabulary(ValidTeamVocabulary):
"""The set of teams where the current user is a member.
@@ -844,6 +889,7 @@
implements(IHugeVocabulary)
displayname = 'Select an active mailing list.'
+ step_title = 'Search'
def __init__(self, context):
assert context is None, (
@@ -965,6 +1011,7 @@
implements(IHugeVocabulary)
displayname = 'Select a Product Release'
+ step_title = 'Search'
_table = ProductRelease
# XXX carlos Perello Marin 2005-05-16 bugs=687:
# Sorting by version won't give the expected results, because it's just a
@@ -1032,6 +1079,7 @@
implements(IHugeVocabulary)
displayname = 'Select a Release Series'
+ step_title = 'Search'
_table = ProductSeries
_order_by = [Product.name, ProductSeries.name]
_clauseTables = ['Product']
@@ -1265,6 +1313,7 @@
_table = Product
_orderBy = 'displayname'
+ step_title = 'Search'
@property
def displayname(self):